From a00a1abfa3e64a226bad9aca93d22bcf1fa1330e Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Fri, 26 Aug 2022 18:21:15 -0700 Subject: [PATCH 01/29] Add mTLS support for http backend by way of client cert & key, as well as enterprise cacert. --- internal/backend/remote-state/http/backend.go | 50 +++++++- .../backend/remote-state/http/backend_test.go | 113 ++++++++++++++++++ 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 39b279b7687d..e2af44210663 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -3,14 +3,16 @@ package http import ( "context" "crypto/tls" + "crypto/x509" "fmt" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-retryablehttp" "log" "net/http" "net/url" + "os" "time" - "github.com/hashicorp/go-cleanhttp" - "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/internal/logging" @@ -93,6 +95,24 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MAX", 30), Description: "The maximum time in seconds to wait between HTTP request attempts.", }, + "cacert": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CACERT", ""), + Description: "The cacert to use when validating the address.", + }, + "cert": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CERT", ""), + Description: "The client certificate to present for mTLS access to an address.", + }, + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_KEY", ""), + Description: "The key to use when presenting a client certificate for mTLS access to an address.", + }, }, } @@ -151,11 +171,33 @@ func (b *Backend) configure(ctx context.Context) error { client := cleanhttp.DefaultPooledClient() + var tlsConfig tls.Config if data.Get("skip_cert_verification").(bool) { // ignores TLS verification - client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, + tlsConfig.InsecureSkipVerify = true + client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig + } + cacert := data.Get("cacert").(string) + if cacert != "" { + cacert_data, err := os.ReadFile(cacert) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", cacert, err) + } + tlsConfig.RootCAs = x509.NewCertPool() + if !tlsConfig.RootCAs.AppendCertsFromPEM(cacert_data) { + return fmt.Errorf("failed to append certs from file %s: %w", cacert, err) + } + client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig + } + cert := data.Get("cert").(string) + key := data.Get("key").(string) + if cert != "" && key != "" { + certificate, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return fmt.Errorf("cannot load certifcate from %s and %s: %w", cert, key, err) } + tlsConfig.Certificates = []tls.Certificate{certificate} + client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig } rClient := retryablehttp.NewClient() diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 9f32273cbac7..9c1f00dae7a9 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -1,6 +1,13 @@ package http import ( + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "io" + "net/http" + "net/http/httptest" "os" "testing" "time" @@ -162,3 +169,109 @@ func testWithEnv(t *testing.T, key string, value string) func() { } } } + +const sampleState = ` +{ + "version": 4, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "remote": { + "type": "http", + "config": { + "path": "local-state.tfstate" + } + } +} +` + +func testCerts(t *testing.T, server *httptest.Server) (certFile, keyFile string) { + // Create the CERTIFICATE + f, err := os.CreateTemp(os.TempDir(), "cert.*") + if err != nil { + t.Fatal(err) + } + certFile = f.Name() + t.Cleanup(func() { + _ = os.Remove(certFile) + }) + cert := server.TLS.Certificates[0] + if err != nil { + t.Fatal(err) + } + if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil { + t.Fatal(err) + } + f.Close() + + // Create the RSA PRIVATE KEY + if f, err = os.CreateTemp(os.TempDir(), "key.*"); err != nil { + t.Fatal(err) + } + keyFile = f.Name() + t.Cleanup(func() { + _ = os.Remove(keyFile) + }) + if err = pem.Encode(f, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(cert.PrivateKey.(*rsa.PrivateKey)), + }); err != nil { + t.Fatal(err) + } + return +} + +func TestMTLS(t *testing.T) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + _, _ = io.WriteString(writer, sampleState) + })) + ts.TLS = &tls.Config{ + ClientAuth: tls.RequireAnyClientCert, + } + ts.StartTLS() + defer ts.Close() + + url := ts.URL + "/state" + + t.Run("fail with no client cert", func(t *testing.T) { + conf := map[string]cty.Value{ + "address": cty.StringVal(url), + } + b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) + if b == nil { + t.Fatalf("nil b") + } + sm, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + t.Fatal(err) + } + if err := sm.RefreshState(); err == nil { + t.Fatal("expected error refreshing state because no client cert is passed") + } + }) + + t.Run("pass with cacert and client cert", func(t *testing.T) { + certFile, keyFile := testCerts(t, ts) + + conf := map[string]cty.Value{ + "address": cty.StringVal(url), + "cacert": cty.StringVal(certFile), + "cert": cty.StringVal(certFile), + "key": cty.StringVal(keyFile), + } + b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) + if b == nil { + t.Fatalf("nil b") + } + sm, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + t.Fatal(err) + } + if err = sm.RefreshState(); err != nil { + t.Fatal(err) + } + state := sm.State() + if state == nil { + t.Fatal("nil state") + } + }) +} From 8a7f4e982685a781ac8fb36e3e18b26448490d5a Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Fri, 26 Aug 2022 18:27:29 -0700 Subject: [PATCH 02/29] Fix style. --- internal/backend/remote-state/http/backend.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index e2af44210663..4dc7c7c39d39 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -5,14 +5,15 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "github.com/hashicorp/go-cleanhttp" - "github.com/hashicorp/go-retryablehttp" "log" "net/http" "net/url" "os" "time" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-retryablehttp" + "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/internal/logging" From eab391b1c010ff8e6d3f1056de5bc579047b61d5 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Fri, 26 Aug 2022 18:34:29 -0700 Subject: [PATCH 03/29] Skip cert validation to be sure error is related to missing client cert; not untrusted server cert. --- internal/backend/remote-state/http/backend_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 9c1f00dae7a9..1fad761e9661 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -234,7 +234,8 @@ func TestMTLS(t *testing.T) { t.Run("fail with no client cert", func(t *testing.T) { conf := map[string]cty.Value{ - "address": cty.StringVal(url), + "address": cty.StringVal(url), + "skip_cert_verification": cty.BoolVal(true), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) if b == nil { From cd46c94a0e42b9a9d64fcc3b9592a651f1432915 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Fri, 26 Aug 2022 18:37:49 -0700 Subject: [PATCH 04/29] Remove misplaced err check. --- internal/backend/remote-state/http/backend_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 1fad761e9661..fac3897026d8 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -195,9 +195,6 @@ func testCerts(t *testing.T, server *httptest.Server) (certFile, keyFile string) _ = os.Remove(certFile) }) cert := server.TLS.Certificates[0] - if err != nil { - t.Fatal(err) - } if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil { t.Fatal(err) } From 0223e3cfc46c62760d1eff0a6c7a0ffadf9e1b18 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Fri, 26 Aug 2022 20:58:32 -0700 Subject: [PATCH 05/29] Fix the size of test using http backend. --- internal/command/apply_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/command/apply_test.go b/internal/command/apply_test.go index ef4484c401bb..202bac59ca60 100644 --- a/internal/command/apply_test.go +++ b/internal/command/apply_test.go @@ -802,6 +802,9 @@ func TestApply_plan_remoteState(t *testing.T) { "retry_max": cty.NullVal(cty.String), "retry_wait_min": cty.NullVal(cty.String), "retry_wait_max": cty.NullVal(cty.String), + "cacert": cty.NullVal(cty.String), + "cert": cty.NullVal(cty.String), + "key": cty.NullVal(cty.String), }) backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type()) if err != nil { From ab1dd2b54d63f2ecddd14ac86076dfe048728858 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Sat, 27 Aug 2022 11:23:12 -0700 Subject: [PATCH 06/29] Just for correctness, include all certs in the pem encoded cert - sometimes certs come with a chain of their signers. --- internal/backend/remote-state/http/backend_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index fac3897026d8..37ec77803402 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -195,8 +195,10 @@ func testCerts(t *testing.T, server *httptest.Server) (certFile, keyFile string) _ = os.Remove(certFile) }) cert := server.TLS.Certificates[0] - if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil { - t.Fatal(err) + for _, certData := range cert.Certificate { + if err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certData}); err != nil { + t.Fatal(err) + } } f.Close() From 985bc53dd0461545ea7a573490e417fdc226e6ab Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 11:22:52 -0700 Subject: [PATCH 07/29] Adjusted names as recommended in PR comments. --- internal/backend/remote-state/http/backend.go | 40 +++++++++---------- .../backend/remote-state/http/backend_test.go | 8 ++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 4dc7c7c39d39..4d82b06e199f 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -96,23 +96,23 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MAX", 30), Description: "The maximum time in seconds to wait between HTTP request attempts.", }, - "cacert": &schema.Schema{ + "client_cacert_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CACERT", ""), - Description: "The cacert to use when validating the address.", + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CACERT_PEM", ""), + Description: "The cacert pem file that the client uses to validate the server in TLS handshake", }, - "cert": &schema.Schema{ + "client_cert_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CERT", ""), - Description: "The client certificate to present for mTLS access to an address.", + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERT_PEM", ""), + Description: "The certificate pem file that the client will present to server in TLS handshake", }, - "key": &schema.Schema{ + "client_key_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_KEY", ""), - Description: "The key to use when presenting a client certificate for mTLS access to an address.", + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_KEY_PEM", ""), + Description: "The key pem file that the client uses to encrypt and sign when using the client cert", }, }, } @@ -178,24 +178,24 @@ func (b *Backend) configure(ctx context.Context) error { tlsConfig.InsecureSkipVerify = true client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig } - cacert := data.Get("cacert").(string) - if cacert != "" { - cacert_data, err := os.ReadFile(cacert) + clientCACertPem := data.Get("client_cacert_pem").(string) + if clientCACertPem != "" { + cacertData, err := os.ReadFile(clientCACertPem) if err != nil { - return fmt.Errorf("failed to read file %s: %w", cacert, err) + return fmt.Errorf("failed to read file %s: %w", clientCACertPem, err) } tlsConfig.RootCAs = x509.NewCertPool() - if !tlsConfig.RootCAs.AppendCertsFromPEM(cacert_data) { - return fmt.Errorf("failed to append certs from file %s: %w", cacert, err) + if !tlsConfig.RootCAs.AppendCertsFromPEM(cacertData) { + return fmt.Errorf("failed to append certs from file %s: %w", clientCACertPem, err) } client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig } - cert := data.Get("cert").(string) - key := data.Get("key").(string) - if cert != "" && key != "" { - certificate, err := tls.LoadX509KeyPair(cert, key) + clientCertPem := data.Get("client_cert_pem").(string) + clientKeyPem := data.Get("client_key_pem").(string) + if clientCertPem != "" && clientKeyPem != "" { + certificate, err := tls.LoadX509KeyPair(clientCertPem, clientKeyPem) if err != nil { - return fmt.Errorf("cannot load certifcate from %s and %s: %w", cert, key, err) + return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertPem, clientKeyPem, err) } tlsConfig.Certificates = []tls.Certificate{certificate} client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 37ec77803402..823f18846d6e 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -253,10 +253,10 @@ func TestMTLS(t *testing.T) { certFile, keyFile := testCerts(t, ts) conf := map[string]cty.Value{ - "address": cty.StringVal(url), - "cacert": cty.StringVal(certFile), - "cert": cty.StringVal(certFile), - "key": cty.StringVal(keyFile), + "address": cty.StringVal(url), + "client_cacert_pem": cty.StringVal(certFile), + "client_cert_pem": cty.StringVal(certFile), + "client_key_pem": cty.StringVal(keyFile), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) if b == nil { From 172f95818ea332cdac63edd5d489dc0c0396d7a6 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 11:30:09 -0700 Subject: [PATCH 08/29] Adjusted names to be full-length and more descriptive. --- internal/backend/remote-state/http/backend.go | 34 +++++++++---------- .../backend/remote-state/http/backend_test.go | 8 ++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 4d82b06e199f..d4a99b5b4f9c 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -96,22 +96,22 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MAX", 30), Description: "The maximum time in seconds to wait between HTTP request attempts.", }, - "client_cacert_pem": &schema.Schema{ + "client_ca_certificate_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CACERT_PEM", ""), + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", ""), Description: "The cacert pem file that the client uses to validate the server in TLS handshake", }, - "client_cert_pem": &schema.Schema{ + "client_certificate_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERT_PEM", ""), + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERTIFICATE_PEM", ""), Description: "The certificate pem file that the client will present to server in TLS handshake", }, - "client_key_pem": &schema.Schema{ + "client_private_key_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_KEY_PEM", ""), + DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""), Description: "The key pem file that the client uses to encrypt and sign when using the client cert", }, }, @@ -178,24 +178,24 @@ func (b *Backend) configure(ctx context.Context) error { tlsConfig.InsecureSkipVerify = true client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig } - clientCACertPem := data.Get("client_cacert_pem").(string) - if clientCACertPem != "" { - cacertData, err := os.ReadFile(clientCACertPem) + clientCACertificatePem := data.Get("client_ca_certificate_pem").(string) + if clientCACertificatePem != "" { + caCertificateData, err := os.ReadFile(clientCACertificatePem) if err != nil { - return fmt.Errorf("failed to read file %s: %w", clientCACertPem, err) + return fmt.Errorf("failed to read file %s: %w", clientCACertificatePem, err) } tlsConfig.RootCAs = x509.NewCertPool() - if !tlsConfig.RootCAs.AppendCertsFromPEM(cacertData) { - return fmt.Errorf("failed to append certs from file %s: %w", clientCACertPem, err) + if !tlsConfig.RootCAs.AppendCertsFromPEM(caCertificateData) { + return fmt.Errorf("failed to append certs from file %s: %w", clientCACertificatePem, err) } client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig } - clientCertPem := data.Get("client_cert_pem").(string) - clientKeyPem := data.Get("client_key_pem").(string) - if clientCertPem != "" && clientKeyPem != "" { - certificate, err := tls.LoadX509KeyPair(clientCertPem, clientKeyPem) + clientCertificatePem := data.Get("client_certificate_pem").(string) + clientPrivateKeyPem := data.Get("client_private_key_pem").(string) + if clientCertificatePem != "" && clientPrivateKeyPem != "" { + certificate, err := tls.LoadX509KeyPair(clientCertificatePem, clientPrivateKeyPem) if err != nil { - return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertPem, clientKeyPem, err) + return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) } tlsConfig.Certificates = []tls.Certificate{certificate} client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 823f18846d6e..931b19cf1789 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -253,10 +253,10 @@ func TestMTLS(t *testing.T) { certFile, keyFile := testCerts(t, ts) conf := map[string]cty.Value{ - "address": cty.StringVal(url), - "client_cacert_pem": cty.StringVal(certFile), - "client_cert_pem": cty.StringVal(certFile), - "client_key_pem": cty.StringVal(keyFile), + "address": cty.StringVal(url), + "client_ca_certificate_pem": cty.StringVal(certFile), + "client_certificate_pem": cty.StringVal(certFile), + "client_private_key_pem": cty.StringVal(keyFile), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) if b == nil { From ce5d217d26312542aa4ae5eebcaf3f9e889dde6e Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 15 Dec 2022 11:44:42 -0800 Subject: [PATCH 09/29] Added full-fledged testing with mTLS http server --- go.mod | 3 +- go.sum | 8 +- .../backend/remote-state/http/backend_test.go | 113 ------ .../remote-state/http/mock_server_test.go | 95 +++++ .../backend/remote-state/http/server_test.go | 364 ++++++++++++++++++ .../http/testdata/certs/ca.cert.pem | 29 ++ .../remote-state/http/testdata/certs/ca.key | 52 +++ .../http/testdata/certs/client.crt | 29 ++ .../http/testdata/certs/client.csr | 27 ++ .../http/testdata/certs/client.key | 52 +++ .../http/testdata/certs/server.crt | 29 ++ .../http/testdata/certs/server.csr | 27 ++ .../http/testdata/certs/server.key | 52 +++ .../remote-state/http/testdata/gencerts.sh | 30 ++ 14 files changed, 794 insertions(+), 116 deletions(-) create mode 100644 internal/backend/remote-state/http/mock_server_test.go create mode 100644 internal/backend/remote-state/http/server_test.go create mode 100644 internal/backend/remote-state/http/testdata/certs/ca.cert.pem create mode 100644 internal/backend/remote-state/http/testdata/certs/ca.key create mode 100644 internal/backend/remote-state/http/testdata/certs/client.crt create mode 100644 internal/backend/remote-state/http/testdata/certs/client.csr create mode 100644 internal/backend/remote-state/http/testdata/certs/client.key create mode 100644 internal/backend/remote-state/http/testdata/certs/server.crt create mode 100644 internal/backend/remote-state/http/testdata/certs/server.csr create mode 100644 internal/backend/remote-state/http/testdata/certs/server.key create mode 100755 internal/backend/remote-state/http/testdata/gencerts.sh diff --git a/go.mod b/go.mod index f464a0118995..c1c273e52e9d 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/posener/complete v1.2.3 github.com/spf13/afero v1.2.2 + github.com/stretchr/testify v1.8.1 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.233 github.com/tencentyun/cos-go-sdk-v5 v0.7.29 @@ -164,12 +165,12 @@ require ( github.com/mozillazg/go-httpheader v0.3.0 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/oklog/run v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.1.1 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect diff --git a/go.sum b/go.sum index 106c3630e5e7..f3f92d0f53bc 100644 --- a/go.sum +++ b/go.sum @@ -594,15 +594,19 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= diff --git a/internal/backend/remote-state/http/backend_test.go b/internal/backend/remote-state/http/backend_test.go index 931b19cf1789..9f32273cbac7 100644 --- a/internal/backend/remote-state/http/backend_test.go +++ b/internal/backend/remote-state/http/backend_test.go @@ -1,13 +1,6 @@ package http import ( - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "io" - "net/http" - "net/http/httptest" "os" "testing" "time" @@ -169,109 +162,3 @@ func testWithEnv(t *testing.T, key string, value string) func() { } } } - -const sampleState = ` -{ - "version": 4, - "serial": 0, - "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", - "remote": { - "type": "http", - "config": { - "path": "local-state.tfstate" - } - } -} -` - -func testCerts(t *testing.T, server *httptest.Server) (certFile, keyFile string) { - // Create the CERTIFICATE - f, err := os.CreateTemp(os.TempDir(), "cert.*") - if err != nil { - t.Fatal(err) - } - certFile = f.Name() - t.Cleanup(func() { - _ = os.Remove(certFile) - }) - cert := server.TLS.Certificates[0] - for _, certData := range cert.Certificate { - if err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certData}); err != nil { - t.Fatal(err) - } - } - f.Close() - - // Create the RSA PRIVATE KEY - if f, err = os.CreateTemp(os.TempDir(), "key.*"); err != nil { - t.Fatal(err) - } - keyFile = f.Name() - t.Cleanup(func() { - _ = os.Remove(keyFile) - }) - if err = pem.Encode(f, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(cert.PrivateKey.(*rsa.PrivateKey)), - }); err != nil { - t.Fatal(err) - } - return -} - -func TestMTLS(t *testing.T) { - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - _, _ = io.WriteString(writer, sampleState) - })) - ts.TLS = &tls.Config{ - ClientAuth: tls.RequireAnyClientCert, - } - ts.StartTLS() - defer ts.Close() - - url := ts.URL + "/state" - - t.Run("fail with no client cert", func(t *testing.T) { - conf := map[string]cty.Value{ - "address": cty.StringVal(url), - "skip_cert_verification": cty.BoolVal(true), - } - b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) - if b == nil { - t.Fatalf("nil b") - } - sm, err := b.StateMgr(backend.DefaultStateName) - if err != nil { - t.Fatal(err) - } - if err := sm.RefreshState(); err == nil { - t.Fatal("expected error refreshing state because no client cert is passed") - } - }) - - t.Run("pass with cacert and client cert", func(t *testing.T) { - certFile, keyFile := testCerts(t, ts) - - conf := map[string]cty.Value{ - "address": cty.StringVal(url), - "client_ca_certificate_pem": cty.StringVal(certFile), - "client_certificate_pem": cty.StringVal(certFile), - "client_private_key_pem": cty.StringVal(keyFile), - } - b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) - if b == nil { - t.Fatalf("nil b") - } - sm, err := b.StateMgr(backend.DefaultStateName) - if err != nil { - t.Fatal(err) - } - if err = sm.RefreshState(); err != nil { - t.Fatal(err) - } - state := sm.State() - if state == nil { - t.Fatal("nil state") - } - }) -} diff --git a/internal/backend/remote-state/http/mock_server_test.go b/internal/backend/remote-state/http/mock_server_test.go new file mode 100644 index 000000000000..ac6a3e3950e6 --- /dev/null +++ b/internal/backend/remote-state/http/mock_server_test.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: server_test.go + +// Package http is a generated GoMock package. +package http + +import ( + http "net/http" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockHttpServerCallback is a mock of HttpServerCallback interface. +type MockHttpServerCallback struct { + ctrl *gomock.Controller + recorder *MockHttpServerCallbackMockRecorder +} + +// MockHttpServerCallbackMockRecorder is the mock recorder for MockHttpServerCallback. +type MockHttpServerCallbackMockRecorder struct { + mock *MockHttpServerCallback +} + +// NewMockHttpServerCallback creates a new mock instance. +func NewMockHttpServerCallback(ctrl *gomock.Controller) *MockHttpServerCallback { + mock := &MockHttpServerCallback{ctrl: ctrl} + mock.recorder = &MockHttpServerCallbackMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHttpServerCallback) EXPECT() *MockHttpServerCallbackMockRecorder { + return m.recorder +} + +// StateDELETE mocks base method. +func (m *MockHttpServerCallback) StateDELETE(req *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StateDELETE", req) +} + +// StateDELETE indicates an expected call of StateDELETE. +func (mr *MockHttpServerCallbackMockRecorder) StateDELETE(req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDELETE", reflect.TypeOf((*MockHttpServerCallback)(nil).StateDELETE), req) +} + +// StateGET mocks base method. +func (m *MockHttpServerCallback) StateGET(req *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StateGET", req) +} + +// StateGET indicates an expected call of StateGET. +func (mr *MockHttpServerCallbackMockRecorder) StateGET(req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGET", reflect.TypeOf((*MockHttpServerCallback)(nil).StateGET), req) +} + +// StateLOCK mocks base method. +func (m *MockHttpServerCallback) StateLOCK(req *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StateLOCK", req) +} + +// StateLOCK indicates an expected call of StateLOCK. +func (mr *MockHttpServerCallbackMockRecorder) StateLOCK(req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLOCK", reflect.TypeOf((*MockHttpServerCallback)(nil).StateLOCK), req) +} + +// StatePOST mocks base method. +func (m *MockHttpServerCallback) StatePOST(req *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StatePOST", req) +} + +// StatePOST indicates an expected call of StatePOST. +func (mr *MockHttpServerCallbackMockRecorder) StatePOST(req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatePOST", reflect.TypeOf((*MockHttpServerCallback)(nil).StatePOST), req) +} + +// StateUNLOCK mocks base method. +func (m *MockHttpServerCallback) StateUNLOCK(req *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StateUNLOCK", req) +} + +// StateUNLOCK indicates an expected call of StateUNLOCK. +func (mr *MockHttpServerCallbackMockRecorder) StateUNLOCK(req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateUNLOCK", reflect.TypeOf((*MockHttpServerCallback)(nil).StateUNLOCK), req) +} diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go new file mode 100644 index 000000000000..43669adfba8c --- /dev/null +++ b/internal/backend/remote-state/http/server_test.go @@ -0,0 +1,364 @@ +package http + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + "io" + "net/http" + "net/http/httptest" + "os" + "os/signal" + "path/filepath" + "strings" + "sync" + "syscall" + "testing" +) + +const sampleState = ` +{ + "version": 4, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "remote": { + "type": "http", + "config": { + "path": "local-state.tfstate" + } + } +} +` + +type ( + HttpServerCallback interface { + StateGET(req *http.Request) + StatePOST(req *http.Request) + StateDELETE(req *http.Request) + StateLOCK(req *http.Request) + StateUNLOCK(req *http.Request) + } + httpServer struct { + r *http.ServeMux + data map[string]string + locks map[string]string + lock sync.RWMutex + + httpServerCallback HttpServerCallback + } + httpServerOpt func(*httpServer) +) + +func withHttpServerCallback(callback HttpServerCallback) httpServerOpt { + return func(s *httpServer) { + s.httpServerCallback = callback + } +} + +func newHttpServer(opts ...httpServerOpt) *httpServer { + r := http.NewServeMux() + s := &httpServer{ + r: r, + data: make(map[string]string), + locks: make(map[string]string), + } + for _, opt := range opts { + opt(s) + } + s.data["sample"] = sampleState + r.HandleFunc("/state/", s.handleState) + return s +} + +func (h *httpServer) getResource(req *http.Request) string { + switch pathParts := strings.SplitN(req.URL.Path, string(filepath.Separator), 3); len(pathParts) { + case 3: + return pathParts[2] + default: + return "" + } +} + +func (h *httpServer) handleState(writer http.ResponseWriter, req *http.Request) { + switch req.Method { + case "GET": + h.handleStateGET(writer, req) + case "POST": + h.handleStatePOST(writer, req) + case "DELETE": + h.handleStateDELETE(writer, req) + case "LOCK": + h.handleStateLOCK(writer, req) + case "UNLOCK": + h.handleStateUNLOCK(writer, req) + } +} + +func (h *httpServer) handleStateGET(writer http.ResponseWriter, req *http.Request) { + if h.httpServerCallback != nil { + defer h.httpServerCallback.StateGET(req) + } + resource := h.getResource(req) + + h.lock.RLock() + defer h.lock.RUnlock() + + if state, ok := h.data[resource]; ok { + _, _ = io.WriteString(writer, state) + } else { + writer.WriteHeader(http.StatusNotFound) + } +} + +func (h *httpServer) handleStatePOST(writer http.ResponseWriter, req *http.Request) { + if h.httpServerCallback != nil { + defer h.httpServerCallback.StatePOST(req) + } + defer req.Body.Close() + resource := h.getResource(req) + + data, err := io.ReadAll(req.Body) + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + return + } + + h.lock.RLock() + defer h.lock.RUnlock() + + h.data[resource] = string(data) + writer.WriteHeader(http.StatusOK) +} + +func (h *httpServer) handleStateDELETE(writer http.ResponseWriter, req *http.Request) { + if h.httpServerCallback != nil { + defer h.httpServerCallback.StateDELETE(req) + } + resource := h.getResource(req) + + h.lock.RLock() + defer h.lock.RUnlock() + + delete(h.data, resource) + writer.WriteHeader(http.StatusOK) +} + +func (h *httpServer) handleStateLOCK(writer http.ResponseWriter, req *http.Request) { + if h.httpServerCallback != nil { + defer h.httpServerCallback.StateLOCK(req) + } + defer req.Body.Close() + resource := h.getResource(req) + + data, err := io.ReadAll(req.Body) + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + return + } + + h.lock.Lock() + defer h.lock.Unlock() + + if existingLock, ok := h.locks[resource]; ok { + writer.WriteHeader(http.StatusLocked) + _, _ = io.WriteString(writer, existingLock) + } else { + h.locks[resource] = string(data) + _, _ = io.WriteString(writer, existingLock) + } +} + +func (h *httpServer) handleStateUNLOCK(writer http.ResponseWriter, req *http.Request) { + if h.httpServerCallback != nil { + defer h.httpServerCallback.StateUNLOCK(req) + } + defer req.Body.Close() + resource := h.getResource(req) + + data, err := io.ReadAll(req.Body) + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + return + } + var lockInfo map[string]interface{} + if err = json.Unmarshal(data, &lockInfo); err != nil { + writer.WriteHeader(http.StatusInternalServerError) + return + } + + h.lock.Lock() + defer h.lock.Unlock() + + if existingLock, ok := h.locks[resource]; ok { + var existingLockInfo map[string]interface{} + if err = json.Unmarshal([]byte(existingLock), &existingLockInfo); err != nil { + writer.WriteHeader(http.StatusInternalServerError) + return + } + lockID := lockInfo["ID"].(string) + existingID := existingLockInfo["ID"].(string) + if lockID != existingID { + writer.WriteHeader(http.StatusConflict) + _, _ = io.WriteString(writer, existingLock) + } else { + delete(h.locks, resource) + _, _ = io.WriteString(writer, existingLock) + } + } else { + writer.WriteHeader(http.StatusConflict) + } +} + +func (h *httpServer) handler() http.Handler { + return h.r +} + +func NewHttpTestServer(opts ...httpServerOpt) (*httptest.Server, error) { + clientCAData, err := os.ReadFile("testdata/certs/ca.cert.pem") + if err != nil { + return nil, err + } + clientCAs := x509.NewCertPool() + clientCAs.AppendCertsFromPEM(clientCAData) + + cert, err := tls.LoadX509KeyPair("testdata/certs/server.crt", "testdata/certs/server.key") + if err != nil { + return nil, err + } + + h := newHttpServer(opts...) + s := httptest.NewUnstartedServer(h.handler()) + s.TLS = &tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCAs, + Certificates: []tls.Certificate{cert}, + } + + s.StartTLS() + return s, nil +} + +func TestMTLSServer_NoCertFails(t *testing.T) { + // Ensure that no calls are made to the server - everything is blocked by the tls.RequireAndVerifyClientCert + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCallback := NewMockHttpServerCallback(ctrl) + + // Fire up a test server + ts, err := NewHttpTestServer(withHttpServerCallback(mockCallback)) + require.NoError(t, err) + defer ts.Close() + + // Configure the backend to the pre-populated sample state + url := ts.URL + "/state/sample" + conf := map[string]cty.Value{ + "address": cty.StringVal(url), + "skip_cert_verification": cty.BoolVal(true), + } + b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) + require.NotNil(t, b, "nil backend") + + // Now get a state manager and check that it fails to refresh the state + sm, err := b.StateMgr(backend.DefaultStateName) + require.NoError(t, err) + err = sm.RefreshState() + require.Error(t, err) +} + +func TestMTLSServer_WithCertPasses(t *testing.T) { + // Ensure that the expected amount of calls is made to the server + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCallback := NewMockHttpServerCallback(ctrl) + + // Two or three (not testing the caching here) calls to GET + mockCallback.EXPECT(). + StateGET(gomock.Any()). + MinTimes(2). + MaxTimes(3) + // One call to the POST to write the data + mockCallback.EXPECT(). + StatePOST(gomock.Any()) + + // Fire up a test server + ts, err := NewHttpTestServer(withHttpServerCallback(mockCallback)) + require.NoError(t, err) + defer ts.Close() + + // Configure the backend to the pre-populated sample state, and with all the test certs lined up + url := ts.URL + "/state/sample" + conf := map[string]cty.Value{ + "address": cty.StringVal(url), + "lock_address": cty.StringVal(url), + "unlock_address": cty.StringVal(url), + "client_ca_certificate_pem": cty.StringVal("testdata/certs/ca.cert.pem"), + "client_certificate_pem": cty.StringVal("testdata/certs/client.crt"), + "client_private_key_pem": cty.StringVal("testdata/certs/client.key"), + } + b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) + require.NotNil(t, b, "nil backend") + + // Now get a state manager, fetch the state, and ensure that the "foo" output is not set + sm, err := b.StateMgr(backend.DefaultStateName) + require.NoError(t, err) + require.NoError(t, sm.RefreshState()) + state := sm.State() + require.NotNil(t, state, "nil state") + stateFoo := state.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) + assert.Nil(t, stateFoo) + + // Create a new state that has "foo" set to "bar" and ensure that state is as expected + state = states.BuildState(func(ss *states.SyncState) { + ss.SetOutputValue( + addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance), + cty.StringVal("bar"), + false) + }) + stateFoo = state.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) + require.NotNil(t, stateFoo) + assert.Equal(t, "bar", stateFoo.Value.AsString()) + + // Ensure the change hasn't altered the current state manager state by checking "foo" and comparing states + curState := sm.State() + curStateFoo := curState.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) + assert.Nil(t, curStateFoo) + assert.NotEqual(t, state, curState) + + // Write the new state, persist, and refresh + assert.NoError(t, sm.WriteState(state)) + assert.NoError(t, sm.PersistState(nil)) + assert.NoError(t, sm.RefreshState()) + + // Get the state again and verify that is now the same as state and has the "foo" value set to "bar" + curState = sm.State() + assert.Equal(t, state, curState) + curStateFoo = curState.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) + require.NotNil(t, curStateFoo) + assert.Equal(t, "bar", curStateFoo.Value.AsString()) +} + +// TestRunServer allows running the server for local debugging; it runs until ctl-c is received +func TestRunServer(t *testing.T) { + if _, ok := os.LookupEnv("TEST_RUN_SERVER"); !ok { + t.Skip("TEST_RUN_SERVER not set") + } + s, err := NewHttpTestServer() + require.NoError(t, err) + defer s.Close() + + t.Log(s.URL) + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + // wait until signal + <-ctx.Done() +} diff --git a/internal/backend/remote-state/http/testdata/certs/ca.cert.pem b/internal/backend/remote-state/http/testdata/certs/ca.cert.pem new file mode 100644 index 000000000000..81e6201e91fa --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/ca.cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFBzCCAu+gAwIBAgIUFPfAxSWlzjWAdQAW+uDbciQm3SowDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHdGVzdC5jYTAgFw0yMjEwMTMyMTE4MTlaGA8zMDIyMDIx +MzIxMTgxOVowEjEQMA4GA1UEAwwHdGVzdC5jYTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJUdKvIM9q7H8TLqj0O6qHUnbE0N3dnNNGVtyO7Nkn4t7urx +X4qmQ6nMzKlC5YhGIlOKO4X0kPXf623+bP+jUf9qAFLkx5SK9TDerhh3e9y9+0YY +C+CM8bQdJD7jFN1oOcKTJipNbjVXCqWqrBXJg91v3p4kyUvGUv05d3pU9nQvKd7R +BGdWh68hjPFqdFso+A1ggxwJ4pEQCllxLu60RpRFwPoup/BeblPz9f3voeqhxT1J +RLviG6HhpMxh44qNh8UrWGyaAk2C5c0rghBUHdfx/RgP2cYuUo5fhPYOHhO0lX80 +0LebXA6nwOhVeHNvrRfjEJS3tTWaFXyaOUiJT2QX2nG0i6cx6pS8dLMMSFLjMSX6 +bTH3KtTR+UrOfC3B47FOO5U++EnBg3WiZCKp+i8+5Sc3MjTw4B8cmydYr59hNWrk +8zrfG1uE6WvxKg1bRc1FcixERcLnIbRH6LE3hHXzYlLoJ8+q9zP0EGqGHycSlv+C +E+6QMMKU0u2tHnixqhlt79ad6bpC52VS3lFt3Fh/TEKWjS1rn2hYZKGSymJpbPFn +q1RQZcxZWjKjqi5UEuAVGfBc4+HLZHq2Vq9umjLn0nuVixjBeBsCBaFC/amksFEJ +fAmMXDERO7Hb4vePq1t9iusWrRPhkvZt6R1Pozg1Ls+xSJQE09n3jWd0/fMhAgMB +AAGjUzBRMB0GA1UdDgQWBBSe+CLJRDjlHurYVRcXvhXyohcVdDAfBgNVHSMEGDAW +gBSe+CLJRDjlHurYVRcXvhXyohcVdDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4ICAQBluWhlAuG7CfMP31aJzl9AucHHLwfAECKg35XTPiF+YrL7rcbQ +0dQyErCXyx7lLGEMqfNxVW48JtLCAATAZk3PwaQdU5OTcKA6Q/mQJwagfgmCVC5+ +Y4fdc7HhfOkGCOQ7aqyJ/EmygafgShreNimRDgIFomEs2hEEKAfvq2YBKcfcDyS7 +vCJZgzKoDmFe4DJjnYN/Gmj/4ak1kwtkoTkwdBlK+zWfbWHSUweXjCvbPPhKCPfy +3Vu++BIW7402aLsP4xyQY/HPGErV3l1TpY3FdCENGQXANF/gPDWj/Q92OdTMRL0U +XXSshNT3YjCxUH3M4A07A11TQwXZRFs2AkZyjJ6M5XNd36FswHh7fSjNLThU6h2V +dI0y/rU4y24KG7KeUayTE1HLGGDskZdXSOL2vH/MTvpheKnLE8fQrKb/SgY+l9RA +fIKwjDfMSL11luuSUIdevt5CEGFms8hpLU1RG2z/qSYz3If/dhN6YdiFJ54Qhjw9 +J5UO4eucsCm3MmsX2jUsDUIjHu92Rt7a3N21lVwzAifwwUzlDrY5xFrtpdhiSEAd +HFmIQOEr3C9xqD3v3b/4N9SoOjZS2j4xk+GQ8XZeTDYf8ZlkXvXHWwEHbVqj0toe +WDooC6oivNJAEs2GxJpyLmmfxIbRjE1sdmVZtmlSb3hY0Rme1SF9FoyZDw== +-----END CERTIFICATE----- diff --git a/internal/backend/remote-state/http/testdata/certs/ca.key b/internal/backend/remote-state/http/testdata/certs/ca.key new file mode 100644 index 000000000000..e297d3aa8ca3 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +g3yddc0Zj29o1jw56J043a37q1CmmsI= +-----END PRIVATE KEY----- diff --git a/internal/backend/remote-state/http/testdata/certs/client.crt b/internal/backend/remote-state/http/testdata/certs/client.crt new file mode 100644 index 000000000000..1d5a74459c82 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/client.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIUJsntRGo85J+ZJAb73snhKsM1oVowDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHdGVzdC5jYTAgFw0yMjEwMTMyMTE4NDBaGA8zMDIyMDIx +MzIxMTg0MFowFjEUMBIGA1UEAwwLdGVzdC5jbGllbnQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCUQebHvDL2ksHcNh6hw0xMCbPxwrBd+qQVFGf/2wL3 +Dk8Ls/NgKQqkoG4WPi4vIuu277+7ngqUZcFbDL/MO7uAWlzthFkhI8IyXeB8t+cj +liqwdgfRFLvoae1PG6ZoFrTXgOsW3tW8SRC8Kax7RdJjEMU1yWEC6OwiH/gabqZM ++i2qSwOgPnxAalljbWDJU0kj+zRfw35W4L/Q9quMid8KQYE71wQoiiBFYFcx552c +kL30xEpKat5ffB42sBpDzO3S/dM0k36im3wEFJHaEW2q4+0Ns9/PQ2OxIfoRC+lD +qYVPeljNSK2n+PSZDjswpZtqK68RD0AM0PmuPqV7Q2DPGoCXpwcq3lczlFH69T7z +s7izG8cnmyi9eWiXgPFWG5JzyeQi2P5fMumF1UVpG2NHyDyEGfXME8dh4S6BpAUJ +9BAXjatjzA5bq4CGS1w/pFrUvhiVQY7byGDqrtTiDa1f48T2CkvVmRUIiKlrvnDe +ezCnJ6P28D0yISyJLN45sQhuyw5idaXHl2AsvDRDFj2iZ/WhY7tCf0O/DSCuI1uZ +WcFXHdRFn9RoGuUqy97+6rPZaB+xNnx83O9pG+Hrx2iz2pSD/pb0b9xKH5VvN1pN +JjtaoMXod1+2z8XdTUzPvkeZyDXIasaZwmSEOOZmGgoRe+KE4ZBlk4XBlm5p2Q6U +RwIDAQABo1MwUTAPBgNVHREECDAGhwR/AAABMB0GA1UdDgQWBBQ/5KcOS58ZKYth +wRpJ+VKCcwJdpTAfBgNVHSMEGDAWgBSe+CLJRDjlHurYVRcXvhXyohcVdDANBgkq +hkiG9w0BAQsFAAOCAgEABiy0c+7T7dam8IjejbDlamAMvDCWFoVW+mLjsGwaS7vx +jmtGig5E08q7axf32iAkfwzi/vEwt66uWGVctUm6/EqH2XvlqZXcsMGiAuWYwJ2Q +DXowHlcIoIRC958qA+6cCAdxoUnTpYSdWWMR+QZ9XDB9MaAZJ+zKhb8nEETl9jGR +Z9iaSEnupposxt5NMvNUU8dTjjjv430WvZnvZaTvegLIQ5QaHeECUQ61Nm18tEey +cPiMu2TN8uO4m67lj4kyXaS3wD7zNuZph55g4vNbQrffTEHUZSFqrr1fyG+7Y+fb +F9hzbhqBgCnYQ5JaxtVbqFAvwDFWRoq2G9gARi/Yuf34djoP09IZvbRymZWJ5857 +KRCT6mBestfOzu2oIz6lDO44fFiejOTDCSDHZ2Try3xAsqS4LAZjWNSqfBIJwABi +bNTWV2yxtlnqEkaPtGYSwQLdF8MTBRbxzsiELktgdgt7XcfarhEKj9iHWirEt0Cw +POnl8S8GzwpsSAomijlLhfyU0J1+p6UP0zJE4YOjKZFv5ddmBCeSTwj0gwVSsSNg +ff7T7IvkTcIMZUlrskeMY4svXpI5FeG+sXXNp2J/iz4XIQdcdpB3t+fDCUcic9Fq +ILJKT1sQpjv4gyAO2BJd4D7clUJwDC059+dh3dDC9d51uHvCra2F/+FGeodQRuU= +-----END CERTIFICATE----- diff --git a/internal/backend/remote-state/http/testdata/certs/client.csr b/internal/backend/remote-state/http/testdata/certs/client.csr new file mode 100644 index 000000000000..ec0f313710e7 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/client.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEfTCCAmUCAQAwFjEUMBIGA1UEAwwLdGVzdC5jbGllbnQwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCUQebHvDL2ksHcNh6hw0xMCbPxwrBd+qQVFGf/ +2wL3Dk8Ls/NgKQqkoG4WPi4vIuu277+7ngqUZcFbDL/MO7uAWlzthFkhI8IyXeB8 +t+cjliqwdgfRFLvoae1PG6ZoFrTXgOsW3tW8SRC8Kax7RdJjEMU1yWEC6OwiH/ga +bqZM+i2qSwOgPnxAalljbWDJU0kj+zRfw35W4L/Q9quMid8KQYE71wQoiiBFYFcx +552ckL30xEpKat5ffB42sBpDzO3S/dM0k36im3wEFJHaEW2q4+0Ns9/PQ2OxIfoR +C+lDqYVPeljNSK2n+PSZDjswpZtqK68RD0AM0PmuPqV7Q2DPGoCXpwcq3lczlFH6 +9T7zs7izG8cnmyi9eWiXgPFWG5JzyeQi2P5fMumF1UVpG2NHyDyEGfXME8dh4S6B +pAUJ9BAXjatjzA5bq4CGS1w/pFrUvhiVQY7byGDqrtTiDa1f48T2CkvVmRUIiKlr +vnDeezCnJ6P28D0yISyJLN45sQhuyw5idaXHl2AsvDRDFj2iZ/WhY7tCf0O/DSCu +I1uZWcFXHdRFn9RoGuUqy97+6rPZaB+xNnx83O9pG+Hrx2iz2pSD/pb0b9xKH5Vv +N1pNJjtaoMXod1+2z8XdTUzPvkeZyDXIasaZwmSEOOZmGgoRe+KE4ZBlk4XBlm5p +2Q6URwIDAQABoCIwIAYJKoZIhvcNAQkOMRMwETAPBgNVHREECDAGhwR/AAABMA0G +CSqGSIb3DQEBCwUAA4ICAQAFUKmXAcULGC1idSXVWRnhzzr6qnl4K2QZVse8kNsk +BD+ePZp7/jc9URP+ykFhVHc1gOy0VgvNm6qePS9ccPTQrRxmXUmMrV2ead9z4h4O +OnnIyfxLxO+Kd1lJ/1UU8CNs3tDQnxEvtx1hYBIDNsyB4bAsfGVBGzBrsoHEjZOg +zTvvPEnH/GpnEITTwK9J6tZ2zanE0K5z2NcSHPjzO0z92sAkcfTIZovcsVCGR3j4 +UDBMWAgK9vybG5G6taQyducU7/kMLcEP5ayG0qIeIrS2GRmOqSixAQQ+Qk6Ucs4w +HD3/9oue5vWJEG0j86jEchdg3OCbHbQEje8Bf39xhpICel45EdGsxc61kiB/c5Lu +8kYQTXDr9P1wtAag5XLmv/nf6pzlQ+LthU/2/EH0r948Rj2Yz4HOOHsfPuB/izF8 +NTAH/VBgp2c/VRjEYd0YQ4X3AS+Q8BwBeR8+OUJu97AIWnM8kjTcRa1ybCGMkQ3L +IjGWgIYnICEmiEJhLo/y7jMSdRwUT9g5zz3koqChzeFSU1LuH/yE2B6GfneblDK+ +B7WDOkUEbHfJ5q0TZwWEgQdpcY5OH+o78NfJpTvgNtPV3B83+g+DdAW2jtgMZ6do +Rb7V+uPvbU9VC2Ng7jacewMtfM3PKugIZ034UUjebQ7/N5ZD01xuJKOG/w2LuUGh +GQ== +-----END CERTIFICATE REQUEST----- diff --git a/internal/backend/remote-state/http/testdata/certs/client.key b/internal/backend/remote-state/http/testdata/certs/client.key new file mode 100644 index 000000000000..392a800712b3 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/client.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCUQebHvDL2ksHc +Nh6hw0xMCbPxwrBd+qQVFGf/2wL3Dk8Ls/NgKQqkoG4WPi4vIuu277+7ngqUZcFb +DL/MO7uAWlzthFkhI8IyXeB8t+cjliqwdgfRFLvoae1PG6ZoFrTXgOsW3tW8SRC8 +Kax7RdJjEMU1yWEC6OwiH/gabqZM+i2qSwOgPnxAalljbWDJU0kj+zRfw35W4L/Q +9quMid8KQYE71wQoiiBFYFcx552ckL30xEpKat5ffB42sBpDzO3S/dM0k36im3wE +FJHaEW2q4+0Ns9/PQ2OxIfoRC+lDqYVPeljNSK2n+PSZDjswpZtqK68RD0AM0Pmu +PqV7Q2DPGoCXpwcq3lczlFH69T7zs7izG8cnmyi9eWiXgPFWG5JzyeQi2P5fMumF +1UVpG2NHyDyEGfXME8dh4S6BpAUJ9BAXjatjzA5bq4CGS1w/pFrUvhiVQY7byGDq +rtTiDa1f48T2CkvVmRUIiKlrvnDeezCnJ6P28D0yISyJLN45sQhuyw5idaXHl2As +vDRDFj2iZ/WhY7tCf0O/DSCuI1uZWcFXHdRFn9RoGuUqy97+6rPZaB+xNnx83O9p +G+Hrx2iz2pSD/pb0b9xKH5VvN1pNJjtaoMXod1+2z8XdTUzPvkeZyDXIasaZwmSE +OOZmGgoRe+KE4ZBlk4XBlm5p2Q6URwIDAQABAoICAC6TP3l6/bWpqB5SoC/oZzUy +DSZDp912SorWxM9DkfxkMd/20dvhONc8ESmKsj6bpVpsmhrKTP+Osf41FKIIF+D8 +QlpZrBh1n+HrzQTRT1tGJzYVdmIwNdIPSP6DrLThgUF8Xh5qtdG3UHsUSnvVlQEL +OTErCP99hgU4btx662Kea68mbsauKqGf52INcAz/Tahwl+UHyM5pP8lZXM5DV97k +ckGGzGch8X5qBCqI3WJctFhLPB2B0kdD+kfq7e1j2Ujh9bJ8LZnO59huT92mgQHh +Jc0at5Jo1M5GYsVtLQRVIqyzvmcLUIbG9qyIpH6lYBwsCgz9cf00v2OGib0eDzC2 +ZqeiotDiul5f6vtNw1YqDdrZWSxRfwqoqzeZX0/bypw6+UTGri+lU7RSRsA845gd +gMjcAd2WocqSNhPBTVPivIDmzHfSMomfnJHCw+aKcm/o6fxcSp4g8pPzpx52h0Eb +tO7rTKTlmglZ8Cc59CPmRqLq+Pk+lHgxTDOUOxZANCuBih4MrDJ2NFnnZxervjPM +te3VlJu8nE5mNuHhT1czekU01lPHQa2E4f5Q74bWpYg71KntN2Po8oUaQQjcX72N +b9N0TzeBrR2TQD/j2S1Mz4ZoStOwOovHdtPZOmfYN30OMX8JzqZLhF4Dfyx8T1JC +Pd1089N0HbX7XIXuEKJtAoIBAQC8xIVQMqg7A9O3u4i2Afq6nASHDM1tnH1l8Ua+ +T2Z6kmPBgjPb+tBrX6YeD13yDxKvfsEr9GnyuQQJdvqNwjVkGNg3Z6HX/HyHIUij +bub3LvpyQzYNkpb2qcoka+AIWDvbpmstetobQ6OK9F914ur29L9XXSp0diZn+1Ff +JqZFfZwgkhsz0Q8HZxT4FfbV+2k6PWk3RPriyKLgZAd3OXswbx/6K7owdUgrOhYA +vUXRae0UrNi0Y2kanzzoBdBLDS3ChML5VBMPIrHac4A97FJS88aewEgMC0E1wlin +J7nwVAubAG7YzEpQeP/4Wp2j9hfwqtO8JlaJL1vygAgQG42NAoIBAQDJD7v0qDal +cuGaHEQLhEVOu7JtQwHn7LmJQyamqCmlL0mDwQOixhEh58sQFUeNYEGKwEGWF+Tk +hA8sAYk4jagUF5sCkOQoWdFWna4uPqlpwozFc/Wj3jKoiYOGn4SFeEJdgQG3rMDM +oepVvaNOljJnNlntKZHUwOM0F6xxV4dXyqnPn+nXmM/Iywd+LSsMN5w8c4IFE+Da +WKrbKMobdaARtx9Lpv7ESObLX3eCRqL1KbuRN2a000Ojfv4kprH1XxMdCWUxXoLk +ac1I29cvx0FFYJfIr3CScdwaKiwGKguk8IMIih3dLulgnaqJ2vUjI3qyQMEFRMBW +3HxFAk3VU6IjAoIBAFRmMYz3+UvZnDHMAYYPQIFq/IM9cCQQEekghZbVfWZUSZHd +mz5B2CoJ7AYIrOJrZtlcfRYgA7bojiuFLOVw7dpBWXr8NNqTI0Jv2UBpd48RTB0G +fAZ5glHq/FxodxSEDs9YixcclKQYC+k29e+Jc7DTITH4j+DearGXJny6lSEA1muh +p9P1JxkSN8fsWh62eAf4KTDzAJGhT2Gwl73wz2mKZeu+3VKJPalGIUxXU/4btErI +NWQCBp5GkD7VSpoj3E/aeCpuMs9Tnd2kQrRtEynPoQCdzBjGd3OH34dtNa+EhGPb +P7RjMt7kGt559X23rGCIoH7BTXOs3xl/sRsylokCggEBAILXEmEr9iPElrtLGZzE +/rU1v+8KY/shObvxTv21ASTVmOl8eXk7m3qM9MAKmP2PXheE9SlPc0yiA52Hglyj +EnXAxsbsswzvJiNPiUHe1TBVwnXb+EYjGqRCmKzKsdqJX+apRQzaBr0jwPL67YL+ +it5PqEWFf7kLrM8BeN5pL1IaOFc8oVgDwXPRa5bYneLdbXaJVFspjHGKseTcrmkg +KoJcwKjii3gAWPCPt523ieQwvDbL7rJNqP6Eba48LCKZND75FjkCX/t0PnrjVS1q +ZTdYnG2kfYVPQwRj3TJFuj4jpaGw/64oEQcmkwwSyOOM+xN0wCdFjkT4RoZB8ZSZ +UDECggEAQ7nnkDKqL2SGsC2tuXpOO1rn2Ifp71hK9TiSUh6QIEk3parO5ualayH+ +UUsav++GIexHIxH5sxbeO6wCurRbrA/64tTXRYh/T9tIkfI4wstgRoFCMPN5CdIs +Q1s48wH1KfQWz1UiNM0rwJKs2kDIWOj9bZotq9Ir3dXYoKgr4sotQFZUVyq2n5Z7 +jE0/bYPHI8+3WXaZsLEzBA167/6IUzIoM5QEgKYP3999CEu2ZKewjnElPMflDJWm +OGT5JYz9SjwKH/9ngGcpIo8i35LSj5R9cK9Sf6dTKo2YZAU1U8yjfaRXIVAmSBFS +SXbUSo1aOU/ZWOnVKdyjhPBcPZMEqQ== +-----END PRIVATE KEY----- diff --git a/internal/backend/remote-state/http/testdata/certs/server.crt b/internal/backend/remote-state/http/testdata/certs/server.crt new file mode 100644 index 000000000000..d16cc6236843 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/server.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIUEJ4OCw9X1j5TegymXZENMgfdBZcwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHdGVzdC5jYTAgFw0yMjEwMTMyMTE4MzBaGA8zMDIyMDIx +MzIxMTgzMFowFjEUMBIGA1UEAwwLdGVzdC5zZXJ2ZXIwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDBTTBoca0tn2EAxbQLXw1diEH5+YltZUFz5gH3aSDf +H+uKame4iFsPybsUstmUqy3D9ZTzjNcauAhB75+RgePn4D0/qePPjdsFz11jxacA +AMkg/mPLPtrkEAiRzvSXXoYN1Gq6uNdGricyKGtSKzqQh158W2ZfLKUKvgGlQ8RD +3RFsFanQS6aiNwPgFK1SeFFt4wTJbtpvKNpJSe/XDDmkyMIN6/pVRo56v2PvsERA +mUQJ+blyhOy7Egt0/uF7JUklzLIi5eKjv2JpcVwH83OAovKl1vy2mfiCZSjCwQeB +ahnWgAaAaJir7uOVNDNXI4qM/KySKN7Nfyo+9sRuNBjMb5b0N8s70YIAyzoylJ0U +E8x+a7XyMJlPvpARGXuNSDwR9rRytpGZIeMPcO1YQh9+V5k7il6/70thNYW5/Cb0 +PsHU5XOSlmOsOLcr1JaD2YTJJflVyhrPwMSAphRuUCFuyQinnHi59Rk2rNuTj/R3 +dFrSqtcfnvjbc+1KbyDFpnyf40W/EPmS4mF3UPRD4oRsvXpy85E25uh5Q+R4MMdd +g3KHsZ+SiObUBa33kd9rG6peJz0cvkJIhBzbJcXPzN2EMgow2C5MYKjmNXclYWIH +ypkFSo6OxFHhIQdeh3Ga7pZqJaOVUA8wm2olIjRQgQFJjRRc6KU2w95lJlFvHXlW +0QIDAQABo1MwUTAPBgNVHREECDAGhwR/AAABMB0GA1UdDgQWBBQpv1S2rSSjgJ7a +xONcLKxYRE3qJzAfBgNVHSMEGDAWgBSe+CLJRDjlHurYVRcXvhXyohcVdDANBgkq +hkiG9w0BAQsFAAOCAgEASpVE6Pj/sPf5heCDI8miF3Xw65BkLMCCL4ZUOugtK0Hg +dbcnaMd6Hwf+mEs/2jaD+2xah489fX4KynJnQ68VpnTMT4yYcsEfvwmZA7Bqo/Ef +MwyFJe/E+Y1mAu7KQodLZ1E13cGVQKDQVwQ5ueyRD3C0bY3glMKfnXvnIIEMiSCg +UTAstj4Z0h9KYrVSRRVfCGOtlvFPo8jg+yPVPsDqGHn2hOH+FYoHv8V1/gGrXJTe +HcTHFIAIkBefHAXCaCYYq3Qfp/ZBpuT5N4bwQtHKmgv5hhyy0kaZRFfE98WkGdSk +Yg5wZRIX6UbjPdyiEnhQdOrnGDehKf9iwv1q98B9hgXzEzdK0e3bR8UY2MRvs/Vz +L2BBDkJHsTo9P1q6zAsmfVNhQPGrEH2pDir8yYpXPz/ocZa7GghJ/RPrYirVTHZp +fNxoMkNfgfVQpSsFvvI/fMGfhG65TQJdq82rAJ5tRRRs69uA00NCggKRWmEdVYpV +jWuMiLrE5U2tHruMytM/ek6kjhzmNpJgPG2alsJHgVb5G8elcCuC0Dx5HjnwbR60 +8V1v2z5kgU9dkT05vZ5RPmNyuv+VP+8Qx/NPCMrf1SaQffW4PaP3YUaRwzJYzEP/ +ZDUOmPsgUMLwj/jT3sEkSc1qUByui2A0QJk2dQzcbNfvpWoBQ+q7m2OHkmzXZCc= +-----END CERTIFICATE----- diff --git a/internal/backend/remote-state/http/testdata/certs/server.csr b/internal/backend/remote-state/http/testdata/certs/server.csr new file mode 100644 index 000000000000..92d06b74a66e --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/server.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEfTCCAmUCAQAwFjEUMBIGA1UEAwwLdGVzdC5zZXJ2ZXIwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDBTTBoca0tn2EAxbQLXw1diEH5+YltZUFz5gH3 +aSDfH+uKame4iFsPybsUstmUqy3D9ZTzjNcauAhB75+RgePn4D0/qePPjdsFz11j +xacAAMkg/mPLPtrkEAiRzvSXXoYN1Gq6uNdGricyKGtSKzqQh158W2ZfLKUKvgGl +Q8RD3RFsFanQS6aiNwPgFK1SeFFt4wTJbtpvKNpJSe/XDDmkyMIN6/pVRo56v2Pv +sERAmUQJ+blyhOy7Egt0/uF7JUklzLIi5eKjv2JpcVwH83OAovKl1vy2mfiCZSjC +wQeBahnWgAaAaJir7uOVNDNXI4qM/KySKN7Nfyo+9sRuNBjMb5b0N8s70YIAyzoy +lJ0UE8x+a7XyMJlPvpARGXuNSDwR9rRytpGZIeMPcO1YQh9+V5k7il6/70thNYW5 +/Cb0PsHU5XOSlmOsOLcr1JaD2YTJJflVyhrPwMSAphRuUCFuyQinnHi59Rk2rNuT +j/R3dFrSqtcfnvjbc+1KbyDFpnyf40W/EPmS4mF3UPRD4oRsvXpy85E25uh5Q+R4 +MMddg3KHsZ+SiObUBa33kd9rG6peJz0cvkJIhBzbJcXPzN2EMgow2C5MYKjmNXcl +YWIHypkFSo6OxFHhIQdeh3Ga7pZqJaOVUA8wm2olIjRQgQFJjRRc6KU2w95lJlFv +HXlW0QIDAQABoCIwIAYJKoZIhvcNAQkOMRMwETAPBgNVHREECDAGhwR/AAABMA0G +CSqGSIb3DQEBCwUAA4ICAQA5BmbXy/UXXNXe0WHR1gxx5nwmJ1CyNy+efVq4cl8Z +ltxaTWy8IZOGN3YHY2ZhmKccm7ecNq1Kv9FUctPe6+97HXb2rL0rB0gO1AyxWJKU +edzls63/0n+AnQqwnPQdgL9N5vIw/0avLo3U8F+kI5hbYfG7fvw3zHdJIMiLTRsn +qKvkF2TMBxr06nrlJsQqG90k9xS3iX7DqssDq3niVgAwP2NbS2wDXk7/6R40LNx9 +RzFHDyHplF/3ySjctkx7kkAPdamGr8NNs7kQkVZGKmD25V7i5ggoGx9lo3AiBbmT +9Keac43vhlC4Bj9zW2O6Ih9TP9sDhp6iA4NtdNnK9tfn59Av6J4pB6EhMzaLtu4J +jqc5b3+Wvq1xv0Sm2Y+JjuawT7jgrT4vnSEqqkFTTV6igzctatOCxz4ejl3Q2sD0 +OjlArZWX9kY2yyuFt6LhlM3We0IDUQjEf0JtA9EFixbm+ieHbPEFHFiD0w9uN/VI +cYzxnubGgvv2wN1N+YHNRFFOWyT+Ty7Hp0Kz3dh8g+DY4vxvsfG6XfnvPT5StSKd +ACEfl8HoSET/qJZIkuIhErzzUNNK4+4QzQav7auZUQUrdK6P+rryE3lZauZ3rV+9 +ZXWT3PG1qHuWNNriTrC6n4tpa8m5UkZMdeoK2pS3y3SLDCJJV7Q3WHCZEVddIPdV +Ew== +-----END CERTIFICATE REQUEST----- diff --git a/internal/backend/remote-state/http/testdata/certs/server.key b/internal/backend/remote-state/http/testdata/certs/server.key new file mode 100644 index 000000000000..881229b09839 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/certs/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 ++AXVqLYxuaMTIdm5Ahyl1sCNrOl8nQ== +-----END PRIVATE KEY----- diff --git a/internal/backend/remote-state/http/testdata/gencerts.sh b/internal/backend/remote-state/http/testdata/gencerts.sh new file mode 100755 index 000000000000..45a716045cb7 --- /dev/null +++ b/internal/backend/remote-state/http/testdata/gencerts.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# Generates certs required for testing: +# - ca.key and ca.cert.pem are self-signed, used as the source of truth for client and server to verify each other. +# - client.key and client.crt are the client's key and cert (signed by the ca key and cert) +# - server.key and server.crt are the server's key and cert (signed by the ca key and cert) + +set -ex + +# I was doing this on M1 mac and needed newer openssl to add the SAN IP; please export OPENSSL when invoking as needed +OPENSSL="${OPENSSL:-/opt/homebrew/Cellar/openssl@3/3.0.5/bin/openssl}" + +# Nuke and recreate the certs dir +rm -rf certs +mkdir certs +cd certs || exit 1 + +# CA +"$OPENSSL" genrsa -out ca.key 4096 +"$OPENSSL" req -new -x509 -days 365000 -key ca.key -out ca.cert.pem + +# Server +"$OPENSSL" genrsa -out server.key 4096 +"$OPENSSL" req -new -key server.key -out server.csr -addext 'subjectAltName = IP:127.0.0.1' +"$OPENSSL" x509 -req -days 365000 -in server.csr -CA ca.cert.pem -CAkey ca.key -CAcreateserial -out server.crt -copy_extensions copy + +# Client +"$OPENSSL" genrsa -out client.key 4096 +"$OPENSSL" req -new -key client.key -out client.csr -addext 'subjectAltName = IP:127.0.0.1' +"$OPENSSL" x509 -req -days 365000 -in client.csr -CA ca.cert.pem -CAkey ca.key -CAcreateserial -out client.crt -copy_extensions copy From 33031da89a96571393547b4718e81aba05dd2d23 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 17:41:09 -0700 Subject: [PATCH 10/29] Fix goimports. --- .../backend/remote-state/http/server_test.go | 17 +++++++++-------- internal/backend/remote/backend_context_test.go | 5 +++-- .../testdata/login-oauth-server/oauthserver.go | 6 +++--- .../testdata/login-tfe-server/tfeserver.go | 4 ++-- internal/command/views/show.go | 1 + .../plans/internal/planproto/planfile.pb.go | 5 +++-- internal/registry/regsrc/friendly_host.go | 2 +- internal/tfplugin5/tfplugin5.pb.go | 5 +++-- internal/tfplugin6/tfplugin6.pb.go | 5 +++-- 9 files changed, 28 insertions(+), 22 deletions(-) diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index 43669adfba8c..fffe7852564d 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -5,14 +5,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "github.com/golang/mock/gomock" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/backend" - "github.com/hashicorp/terraform/internal/configs" - "github.com/hashicorp/terraform/internal/states" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/zclconf/go-cty/cty" "io" "net/http" "net/http/httptest" @@ -23,6 +15,15 @@ import ( "sync" "syscall" "testing" + + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" ) const sampleState = ` diff --git a/internal/backend/remote/backend_context_test.go b/internal/backend/remote/backend_context_test.go index f3a133421aed..c85a45154dca 100644 --- a/internal/backend/remote/backend_context_test.go +++ b/internal/backend/remote/backend_context_test.go @@ -2,11 +2,12 @@ package remote import ( "context" - "github.com/hashicorp/terraform/internal/terraform" - "github.com/hashicorp/terraform/internal/tfdiags" "reflect" "testing" + "github.com/hashicorp/terraform/internal/terraform" + "github.com/hashicorp/terraform/internal/tfdiags" + tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/command/arguments" diff --git a/internal/command/testdata/login-oauth-server/oauthserver.go b/internal/command/testdata/login-oauth-server/oauthserver.go index 5454a1d8f647..ca76b5c75621 100644 --- a/internal/command/testdata/login-oauth-server/oauthserver.go +++ b/internal/command/testdata/login-oauth-server/oauthserver.go @@ -16,9 +16,9 @@ import ( // Handler is an implementation of net/http.Handler that provides a stub // OAuth server implementation with the following endpoints: // -// /authz - authorization endpoint -// /token - token endpoint -// /revoke - token revocation (logout) endpoint +// /authz - authorization endpoint +// /token - token endpoint +// /revoke - token revocation (logout) endpoint // // The authorization endpoint returns HTML per normal OAuth conventions, but // it also includes an HTTP header X-Redirect-To giving the same URL that the diff --git a/internal/command/testdata/login-tfe-server/tfeserver.go b/internal/command/testdata/login-tfe-server/tfeserver.go index 111c0712ccac..aa69a6855d2f 100644 --- a/internal/command/testdata/login-tfe-server/tfeserver.go +++ b/internal/command/testdata/login-tfe-server/tfeserver.go @@ -17,8 +17,8 @@ const ( // Handler is an implementation of net/http.Handler that provides a stub // TFE API server implementation with the following endpoints: // -// /ping - API existence endpoint -// /account/details - current user endpoint +// /ping - API existence endpoint +// /account/details - current user endpoint var Handler http.Handler type handler struct{} diff --git a/internal/command/views/show.go b/internal/command/views/show.go index 1ab16c2d5125..906c96a670e7 100644 --- a/internal/command/views/show.go +++ b/internal/command/views/show.go @@ -2,6 +2,7 @@ package views import ( "fmt" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/format" "github.com/hashicorp/terraform/internal/command/jsonplan" diff --git a/internal/plans/internal/planproto/planfile.pb.go b/internal/plans/internal/planproto/planfile.pb.go index 3de640b37f14..5cddb630743a 100644 --- a/internal/plans/internal/planproto/planfile.pb.go +++ b/internal/plans/internal/planproto/planfile.pb.go @@ -7,10 +7,11 @@ package planproto import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( diff --git a/internal/registry/regsrc/friendly_host.go b/internal/registry/regsrc/friendly_host.go index c9bc40bee8f3..4ac22db87d13 100644 --- a/internal/registry/regsrc/friendly_host.go +++ b/internal/registry/regsrc/friendly_host.go @@ -4,7 +4,7 @@ import ( "regexp" "strings" - "github.com/hashicorp/terraform-svchost" + svchost "github.com/hashicorp/terraform-svchost" ) var ( diff --git a/internal/tfplugin5/tfplugin5.pb.go b/internal/tfplugin5/tfplugin5.pb.go index eb92a2332011..e4d9fd5c79b6 100644 --- a/internal/tfplugin5/tfplugin5.pb.go +++ b/internal/tfplugin5/tfplugin5.pb.go @@ -27,13 +27,14 @@ package tfplugin5 import ( context "context" + reflect "reflect" + sync "sync" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" ) const ( diff --git a/internal/tfplugin6/tfplugin6.pb.go b/internal/tfplugin6/tfplugin6.pb.go index b66ffec7f85c..03a14257cf1a 100644 --- a/internal/tfplugin6/tfplugin6.pb.go +++ b/internal/tfplugin6/tfplugin6.pb.go @@ -27,13 +27,14 @@ package tfplugin6 import ( context "context" + reflect "reflect" + sync "sync" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" ) const ( From c61a9ed9c65a2eaf5b2d564f23da2a0c3f1dd067 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 17:49:06 -0700 Subject: [PATCH 11/29] Fix the names of the backend config. --- internal/command/apply_test.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/command/apply_test.go b/internal/command/apply_test.go index 202bac59ca60..ba75c5e84a5f 100644 --- a/internal/command/apply_test.go +++ b/internal/command/apply_test.go @@ -790,21 +790,21 @@ func TestApply_plan_remoteState(t *testing.T) { _, snap := testModuleWithSnapshot(t, "apply") backendConfig := cty.ObjectVal(map[string]cty.Value{ - "address": cty.StringVal(srv.URL), - "update_method": cty.NullVal(cty.String), - "lock_address": cty.NullVal(cty.String), - "unlock_address": cty.NullVal(cty.String), - "lock_method": cty.NullVal(cty.String), - "unlock_method": cty.NullVal(cty.String), - "username": cty.NullVal(cty.String), - "password": cty.NullVal(cty.String), - "skip_cert_verification": cty.NullVal(cty.Bool), - "retry_max": cty.NullVal(cty.String), - "retry_wait_min": cty.NullVal(cty.String), - "retry_wait_max": cty.NullVal(cty.String), - "cacert": cty.NullVal(cty.String), - "cert": cty.NullVal(cty.String), - "key": cty.NullVal(cty.String), + "address": cty.StringVal(srv.URL), + "update_method": cty.NullVal(cty.String), + "lock_address": cty.NullVal(cty.String), + "unlock_address": cty.NullVal(cty.String), + "lock_method": cty.NullVal(cty.String), + "unlock_method": cty.NullVal(cty.String), + "username": cty.NullVal(cty.String), + "password": cty.NullVal(cty.String), + "skip_cert_verification": cty.NullVal(cty.Bool), + "retry_max": cty.NullVal(cty.String), + "retry_wait_min": cty.NullVal(cty.String), + "retry_wait_max": cty.NullVal(cty.String), + "client_ca_certificate_pem": cty.NullVal(cty.String), + "client_certificate_pem": cty.NullVal(cty.String), + "client_private_key_pem": cty.NullVal(cty.String), }) backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type()) if err != nil { From fb7e570d35aed35a13f291acf238c520e74408e0 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 18:07:05 -0700 Subject: [PATCH 12/29] Exclusive lock for write and delete. --- internal/backend/remote-state/http/server_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index fffe7852564d..77c7da45518a 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -133,8 +133,8 @@ func (h *httpServer) handleStatePOST(writer http.ResponseWriter, req *http.Reque return } - h.lock.RLock() - defer h.lock.RUnlock() + h.lock.Lock() + defer h.lock.Unlock() h.data[resource] = string(data) writer.WriteHeader(http.StatusOK) @@ -146,8 +146,8 @@ func (h *httpServer) handleStateDELETE(writer http.ResponseWriter, req *http.Req } resource := h.getResource(req) - h.lock.RLock() - defer h.lock.RUnlock() + h.lock.Lock() + defer h.lock.Unlock() delete(h.data, resource) writer.WriteHeader(http.StatusOK) From cfd928a1080b904a9eb4eedeb34db7756873e1f5 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 18:27:59 -0700 Subject: [PATCH 13/29] Revert "Fix goimports." This reverts commit 7d40f6099fbbb675fb2e25e35ee40aeafe3d0a22. --- .../backend/remote-state/http/server_test.go | 17 ++++++++--------- internal/backend/remote-state/oss/backend.go | 3 +-- internal/backend/remote/backend_context_test.go | 5 ++--- .../testdata/login-oauth-server/oauthserver.go | 6 +++--- .../testdata/login-tfe-server/tfeserver.go | 4 ++-- internal/command/views/show.go | 1 - .../plans/internal/planproto/planfile.pb.go | 5 ++--- internal/registry/regsrc/friendly_host.go | 2 +- internal/tfplugin5/tfplugin5.pb.go | 5 ++--- internal/tfplugin6/tfplugin6.pb.go | 5 ++--- 10 files changed, 23 insertions(+), 30 deletions(-) diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index 77c7da45518a..f8b802537cf4 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -5,6 +5,14 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" "io" "net/http" "net/http/httptest" @@ -15,15 +23,6 @@ import ( "sync" "syscall" "testing" - - "github.com/golang/mock/gomock" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/backend" - "github.com/hashicorp/terraform/internal/configs" - "github.com/hashicorp/terraform/internal/states" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/zclconf/go-cty/cty" ) const sampleState = ` diff --git a/internal/backend/remote-state/oss/backend.go b/internal/backend/remote-state/oss/backend.go index 468a883298c8..9487e14a5eca 100644 --- a/internal/backend/remote-state/oss/backend.go +++ b/internal/backend/remote-state/oss/backend.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" "io/ioutil" "log" "net/http" @@ -15,8 +16,6 @@ import ( "strings" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" diff --git a/internal/backend/remote/backend_context_test.go b/internal/backend/remote/backend_context_test.go index c85a45154dca..f3a133421aed 100644 --- a/internal/backend/remote/backend_context_test.go +++ b/internal/backend/remote/backend_context_test.go @@ -2,11 +2,10 @@ package remote import ( "context" - "reflect" - "testing" - "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" + "reflect" + "testing" tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform/internal/backend" diff --git a/internal/command/testdata/login-oauth-server/oauthserver.go b/internal/command/testdata/login-oauth-server/oauthserver.go index ca76b5c75621..5454a1d8f647 100644 --- a/internal/command/testdata/login-oauth-server/oauthserver.go +++ b/internal/command/testdata/login-oauth-server/oauthserver.go @@ -16,9 +16,9 @@ import ( // Handler is an implementation of net/http.Handler that provides a stub // OAuth server implementation with the following endpoints: // -// /authz - authorization endpoint -// /token - token endpoint -// /revoke - token revocation (logout) endpoint +// /authz - authorization endpoint +// /token - token endpoint +// /revoke - token revocation (logout) endpoint // // The authorization endpoint returns HTML per normal OAuth conventions, but // it also includes an HTTP header X-Redirect-To giving the same URL that the diff --git a/internal/command/testdata/login-tfe-server/tfeserver.go b/internal/command/testdata/login-tfe-server/tfeserver.go index aa69a6855d2f..111c0712ccac 100644 --- a/internal/command/testdata/login-tfe-server/tfeserver.go +++ b/internal/command/testdata/login-tfe-server/tfeserver.go @@ -17,8 +17,8 @@ const ( // Handler is an implementation of net/http.Handler that provides a stub // TFE API server implementation with the following endpoints: // -// /ping - API existence endpoint -// /account/details - current user endpoint +// /ping - API existence endpoint +// /account/details - current user endpoint var Handler http.Handler type handler struct{} diff --git a/internal/command/views/show.go b/internal/command/views/show.go index 906c96a670e7..1ab16c2d5125 100644 --- a/internal/command/views/show.go +++ b/internal/command/views/show.go @@ -2,7 +2,6 @@ package views import ( "fmt" - "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/format" "github.com/hashicorp/terraform/internal/command/jsonplan" diff --git a/internal/plans/internal/planproto/planfile.pb.go b/internal/plans/internal/planproto/planfile.pb.go index 5cddb630743a..3de640b37f14 100644 --- a/internal/plans/internal/planproto/planfile.pb.go +++ b/internal/plans/internal/planproto/planfile.pb.go @@ -7,11 +7,10 @@ package planproto import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( diff --git a/internal/registry/regsrc/friendly_host.go b/internal/registry/regsrc/friendly_host.go index 4ac22db87d13..c9bc40bee8f3 100644 --- a/internal/registry/regsrc/friendly_host.go +++ b/internal/registry/regsrc/friendly_host.go @@ -4,7 +4,7 @@ import ( "regexp" "strings" - svchost "github.com/hashicorp/terraform-svchost" + "github.com/hashicorp/terraform-svchost" ) var ( diff --git a/internal/tfplugin5/tfplugin5.pb.go b/internal/tfplugin5/tfplugin5.pb.go index e4d9fd5c79b6..eb92a2332011 100644 --- a/internal/tfplugin5/tfplugin5.pb.go +++ b/internal/tfplugin5/tfplugin5.pb.go @@ -27,14 +27,13 @@ package tfplugin5 import ( context "context" - reflect "reflect" - sync "sync" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( diff --git a/internal/tfplugin6/tfplugin6.pb.go b/internal/tfplugin6/tfplugin6.pb.go index 03a14257cf1a..b66ffec7f85c 100644 --- a/internal/tfplugin6/tfplugin6.pb.go +++ b/internal/tfplugin6/tfplugin6.pb.go @@ -27,14 +27,13 @@ package tfplugin6 import ( context "context" - reflect "reflect" - sync "sync" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( From bbc0478e0beb9b7ee407cf98817d0a79c89e6547 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 18:29:45 -0700 Subject: [PATCH 14/29] goimports just for server test. --- .../backend/remote-state/http/server_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index f8b802537cf4..77c7da45518a 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -5,14 +5,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "github.com/golang/mock/gomock" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/backend" - "github.com/hashicorp/terraform/internal/configs" - "github.com/hashicorp/terraform/internal/states" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/zclconf/go-cty/cty" "io" "net/http" "net/http/httptest" @@ -23,6 +15,15 @@ import ( "sync" "syscall" "testing" + + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" ) const sampleState = ` From 6208901bd5bedfa8114eb3694f78a6bd251b918e Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 13 Oct 2022 18:32:30 -0700 Subject: [PATCH 15/29] Added the go:generation for the mock. --- internal/backend/remote-state/http/server_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index 77c7da45518a..d15a4ef98785 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -1,5 +1,7 @@ package http +//go:generate go run github.com/golang/mock/mockgen -package $GOPACKAGE -source $GOFILE -destination mock_$GOFILE + import ( "context" "crypto/tls" From 6328481d0457bb7d27814a8f9fed054a49f56853 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Mon, 5 Dec 2022 10:35:12 -0800 Subject: [PATCH 16/29] Move the TLS configuration out to make it more readable - don't replace the HTTPClient as the retryablehttp already creates one - just configure its TLS. --- internal/backend/remote-state/http/backend.go | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index d4a99b5b4f9c..b108168600cc 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -11,7 +11,6 @@ import ( "os" "time" - "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform/internal/backend" @@ -128,6 +127,48 @@ type Backend struct { client *httpClient } +// configureTLS configures TLS when needed; if there are no conditions requiring TLS, no change is made. +func (b *Backend) configureTLS(data *schema.ResourceData, client *retryablehttp.Client) error { + // If there are no conditions needing to configure TLS, leave the client untouched + skipCertVerification := data.Get("skip_cert_verification").(bool) + clientCACertificatePem := data.Get("client_ca_certificate_pem").(string) + clientCertificatePem := data.Get("client_certificate_pem").(string) + clientPrivateKeyPem := data.Get("client_private_key_pem").(string) + if !skipCertVerification && clientCACertificatePem == "" && clientCertificatePem == "" && clientPrivateKeyPem == "" { + return nil + } + + // TLS configuration is needed; create an object and configure it + var tlsConfig tls.Config + client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = &tlsConfig + + if skipCertVerification { + // ignores TLS verification + tlsConfig.InsecureSkipVerify = true + } + if clientCACertificatePem != "" { + // trust servers based on a CA + caCertificateData, err := os.ReadFile(clientCACertificatePem) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", clientCACertificatePem, err) + } + tlsConfig.RootCAs = x509.NewCertPool() + if !tlsConfig.RootCAs.AppendCertsFromPEM(caCertificateData) { + return fmt.Errorf("failed to append certs from file %s: %w", clientCACertificatePem, err) + } + } + if clientCertificatePem != "" && clientPrivateKeyPem != "" { + // attach a client certificate to the TLS handshake (aka mTLS) + certificate, err := tls.LoadX509KeyPair(clientCertificatePem, clientPrivateKeyPem) + if err != nil { + return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) + } + tlsConfig.Certificates = []tls.Certificate{certificate} + } + + return nil +} + func (b *Backend) configure(ctx context.Context) error { data := schema.FromContextBackendConfig(ctx) @@ -170,43 +211,14 @@ func (b *Backend) configure(ctx context.Context) error { unlockMethod := data.Get("unlock_method").(string) - client := cleanhttp.DefaultPooledClient() - - var tlsConfig tls.Config - if data.Get("skip_cert_verification").(bool) { - // ignores TLS verification - tlsConfig.InsecureSkipVerify = true - client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig - } - clientCACertificatePem := data.Get("client_ca_certificate_pem").(string) - if clientCACertificatePem != "" { - caCertificateData, err := os.ReadFile(clientCACertificatePem) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", clientCACertificatePem, err) - } - tlsConfig.RootCAs = x509.NewCertPool() - if !tlsConfig.RootCAs.AppendCertsFromPEM(caCertificateData) { - return fmt.Errorf("failed to append certs from file %s: %w", clientCACertificatePem, err) - } - client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig - } - clientCertificatePem := data.Get("client_certificate_pem").(string) - clientPrivateKeyPem := data.Get("client_private_key_pem").(string) - if clientCertificatePem != "" && clientPrivateKeyPem != "" { - certificate, err := tls.LoadX509KeyPair(clientCertificatePem, clientPrivateKeyPem) - if err != nil { - return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) - } - tlsConfig.Certificates = []tls.Certificate{certificate} - client.Transport.(*http.Transport).TLSClientConfig = &tlsConfig - } - rClient := retryablehttp.NewClient() - rClient.HTTPClient = client rClient.RetryMax = data.Get("retry_max").(int) rClient.RetryWaitMin = time.Duration(data.Get("retry_wait_min").(int)) * time.Second rClient.RetryWaitMax = time.Duration(data.Get("retry_wait_max").(int)) * time.Second rClient.Logger = log.New(logging.LogOutput(), "", log.Flags()) + if err = b.configureTLS(data, rClient); err != nil { + return err + } b.client = &httpClient{ URL: updateURL, From 2ab36d6a5a537d28767c9d87284bc755287ce050 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Mon, 5 Dec 2022 10:44:49 -0800 Subject: [PATCH 17/29] Just switch the client/data params - felt more natural this way. --- internal/backend/remote-state/http/backend.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index b108168600cc..6c9a606840c9 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -128,7 +128,7 @@ type Backend struct { } // configureTLS configures TLS when needed; if there are no conditions requiring TLS, no change is made. -func (b *Backend) configureTLS(data *schema.ResourceData, client *retryablehttp.Client) error { +func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.ResourceData) error { // If there are no conditions needing to configure TLS, leave the client untouched skipCertVerification := data.Get("skip_cert_verification").(bool) clientCACertificatePem := data.Get("client_ca_certificate_pem").(string) @@ -216,7 +216,7 @@ func (b *Backend) configure(ctx context.Context) error { rClient.RetryWaitMin = time.Duration(data.Get("retry_wait_min").(int)) * time.Second rClient.RetryWaitMax = time.Duration(data.Get("retry_wait_max").(int)) * time.Second rClient.Logger = log.New(logging.LogOutput(), "", log.Flags()) - if err = b.configureTLS(data, rClient); err != nil { + if err = b.configureTLS(rClient, data); err != nil { return err } From 6f05da8ab89d4fbf4fd2762cceca8f134debdd9a Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:06:34 -0800 Subject: [PATCH 18/29] Update internal/backend/remote-state/http/backend.go Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 6c9a606840c9..0b8b0cfc2869 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -161,7 +161,7 @@ func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.Resour // attach a client certificate to the TLS handshake (aka mTLS) certificate, err := tls.LoadX509KeyPair(clientCertificatePem, clientPrivateKeyPem) if err != nil { - return fmt.Errorf("cannot load certifcate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) + return fmt.Errorf("cannot load certificate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) } tlsConfig.Certificates = []tls.Certificate{certificate} } From 441edac964dec44ca5e2c4fa35b4351bd9afb21f Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:06:43 -0800 Subject: [PATCH 19/29] Update internal/backend/remote-state/http/testdata/gencerts.sh Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/testdata/gencerts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/testdata/gencerts.sh b/internal/backend/remote-state/http/testdata/gencerts.sh index 45a716045cb7..7a5b82312f6b 100755 --- a/internal/backend/remote-state/http/testdata/gencerts.sh +++ b/internal/backend/remote-state/http/testdata/gencerts.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Generates certs required for testing: +# Generates certs required for mTLS testing: # - ca.key and ca.cert.pem are self-signed, used as the source of truth for client and server to verify each other. # - client.key and client.crt are the client's key and cert (signed by the ca key and cert) # - server.key and server.crt are the server's key and cert (signed by the ca key and cert) From 91baacc45c68ae9dfe40dfc67afa8b4458b2740e Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:24:17 -0800 Subject: [PATCH 20/29] Update internal/backend/remote-state/http/backend.go Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 0b8b0cfc2869..96d473d0fb08 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -105,7 +105,7 @@ func New() backend.Backend { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERTIFICATE_PEM", ""), - Description: "The certificate pem file that the client will present to server in TLS handshake", + Description: "A path to a PEM-encoded certificate file used by the server to verify the client during mutual TLS (mTLS) authentication.", }, "client_private_key_pem": &schema.Schema{ Type: schema.TypeString, From 666d7fcb867d475f2910ec55a2e825538e437333 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:25:39 -0800 Subject: [PATCH 21/29] Update internal/backend/remote-state/http/backend.go Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 96d473d0fb08..ab03bed09b30 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -99,7 +99,7 @@ func New() backend.Backend { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", ""), - Description: "The cacert pem file that the client uses to validate the server in TLS handshake", + Description: "A path to a PEM-encoded CA certificate file used by the client to verify server certificates during TLS authentication.", }, "client_certificate_pem": &schema.Schema{ Type: schema.TypeString, From 6d6bf19228bb1d2abcdddc69f09cc1c56430ceca Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:25:45 -0800 Subject: [PATCH 22/29] Update internal/backend/remote-state/http/backend.go Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index ab03bed09b30..8783c1c2e043 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -111,7 +111,7 @@ func New() backend.Backend { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""), - Description: "The key pem file that the client uses to encrypt and sign when using the client cert", + Description: "A path to a PEM-encoded private key, required if client_certificate_pem is specified.", }, }, } From f8a3b8b941578214412cfac87abe5b4bc293f061 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins <41922797+scr-oath@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:25:51 -0800 Subject: [PATCH 23/29] Update internal/backend/remote-state/http/backend.go Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com> --- internal/backend/remote-state/http/backend.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 8783c1c2e043..893cd65e0b8f 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -110,6 +110,7 @@ func New() backend.Backend { "client_private_key_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, + Sensitive: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""), Description: "A path to a PEM-encoded private key, required if client_certificate_pem is specified.", }, From 481ad34fbaf0f1cabe1739960d62e54667e2e700 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 8 Dec 2022 11:26:55 -0800 Subject: [PATCH 24/29] the location of the file name is not sensitive. --- internal/backend/remote-state/http/backend.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 893cd65e0b8f..8783c1c2e043 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -110,7 +110,6 @@ func New() backend.Backend { "client_private_key_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, - Sensitive: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""), Description: "A path to a PEM-encoded private key, required if client_certificate_pem is specified.", }, From 13bbe7c45e3aff9ac96aba7f6eade4c2e7c80dd3 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 8 Dec 2022 11:34:17 -0800 Subject: [PATCH 25/29] Added error if only one of client_certificate_pem and client_private_key_pem are set. --- internal/backend/remote-state/http/backend.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index 8783c1c2e043..e45cdf9f5996 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -137,6 +137,12 @@ func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.Resour if !skipCertVerification && clientCACertificatePem == "" && clientCertificatePem == "" && clientPrivateKeyPem == "" { return nil } + if clientCertificatePem != "" && clientPrivateKeyPem == "" { + return fmt.Errorf("client_certificate_pem is set but client_private_key_pem is not") + } + if clientPrivateKeyPem != "" && clientCertificatePem == "" { + return fmt.Errorf("client_private_key_pem is set but client_certificate_pem is not") + } // TLS configuration is needed; create an object and configure it var tlsConfig tls.Config From d4218d10a9c55d4e36b750710c532e1de0017afc Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 15 Dec 2022 12:02:17 -0800 Subject: [PATCH 26/29] Remove testify from test cases; use t.Error* for assert and t.Fatal* for require. --- go.mod | 3 +- go.sum | 1 - .../backend/remote-state/http/server_test.go | 89 ++++++++++++++----- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index c1c273e52e9d..3ef53e412dfd 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/posener/complete v1.2.3 github.com/spf13/afero v1.2.2 - github.com/stretchr/testify v1.8.1 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.233 github.com/tencentyun/cos-go-sdk-v5 v0.7.29 @@ -165,12 +164,12 @@ require ( github.com/mozillazg/go-httpheader v0.3.0 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/oklog/run v1.0.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect diff --git a/go.sum b/go.sum index f3f92d0f53bc..f9f7939d3f46 100644 --- a/go.sum +++ b/go.sum @@ -606,7 +606,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index d15a4ef98785..b7d84d34960d 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -13,6 +13,7 @@ import ( "os" "os/signal" "path/filepath" + "reflect" "strings" "sync" "syscall" @@ -23,8 +24,6 @@ import ( "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/states" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" ) @@ -258,7 +257,9 @@ func TestMTLSServer_NoCertFails(t *testing.T) { // Fire up a test server ts, err := NewHttpTestServer(withHttpServerCallback(mockCallback)) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error creating test server: %v", err) + } defer ts.Close() // Configure the backend to the pre-populated sample state @@ -268,13 +269,21 @@ func TestMTLSServer_NoCertFails(t *testing.T) { "skip_cert_verification": cty.BoolVal(true), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) - require.NotNil(t, b, "nil backend") + if nil == b { + t.Fatal("nil backend") + } // Now get a state manager and check that it fails to refresh the state sm, err := b.StateMgr(backend.DefaultStateName) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error fetching StateMgr with %s: %v", backend.DefaultStateName, err) + } err = sm.RefreshState() - require.Error(t, err) + if nil == err { + t.Error("expected error when refreshing state without a client cert") + } else if !strings.Contains(err.Error(), "remote error: tls: bad certificate") { + t.Errorf("expected the error to report missing tls credentials: %v", err) + } } func TestMTLSServer_WithCertPasses(t *testing.T) { @@ -294,7 +303,9 @@ func TestMTLSServer_WithCertPasses(t *testing.T) { // Fire up a test server ts, err := NewHttpTestServer(withHttpServerCallback(mockCallback)) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error creating test server: %v", err) + } defer ts.Close() // Configure the backend to the pre-populated sample state, and with all the test certs lined up @@ -308,16 +319,26 @@ func TestMTLSServer_WithCertPasses(t *testing.T) { "client_private_key_pem": cty.StringVal("testdata/certs/client.key"), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) - require.NotNil(t, b, "nil backend") + if nil == b { + t.Fatal("nil backend") + } // Now get a state manager, fetch the state, and ensure that the "foo" output is not set sm, err := b.StateMgr(backend.DefaultStateName) - require.NoError(t, err) - require.NoError(t, sm.RefreshState()) + if err != nil { + t.Fatalf("unexpected error fetching StateMgr with %s: %v", backend.DefaultStateName, err) + } + if err = sm.RefreshState(); err != nil { + t.Fatalf("unexpected error calling RefreshState: %v", err) + } state := sm.State() - require.NotNil(t, state, "nil state") + if nil == state { + t.Fatal("nil state") + } stateFoo := state.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) - assert.Nil(t, stateFoo) + if stateFoo != nil { + t.Errorf("expected nil foo from state; got %v", stateFoo) + } // Create a new state that has "foo" set to "bar" and ensure that state is as expected state = states.BuildState(func(ss *states.SyncState) { @@ -327,26 +348,46 @@ func TestMTLSServer_WithCertPasses(t *testing.T) { false) }) stateFoo = state.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) - require.NotNil(t, stateFoo) - assert.Equal(t, "bar", stateFoo.Value.AsString()) + if nil == stateFoo { + t.Fatal("nil foo after building state with foo populated") + } + if foo := stateFoo.Value.AsString(); foo != "bar" { + t.Errorf("Expected built state foo value to be bar; got %s", foo) + } // Ensure the change hasn't altered the current state manager state by checking "foo" and comparing states curState := sm.State() curStateFoo := curState.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) - assert.Nil(t, curStateFoo) - assert.NotEqual(t, state, curState) + if curStateFoo != nil { + t.Errorf("expected session manager state to be unaltered and still nil, but got: %v", curStateFoo) + } + if reflect.DeepEqual(state, curState) { + t.Errorf("expected %v != %v; but they were equal", state, curState) + } // Write the new state, persist, and refresh - assert.NoError(t, sm.WriteState(state)) - assert.NoError(t, sm.PersistState(nil)) - assert.NoError(t, sm.RefreshState()) + if err = sm.WriteState(state); err != nil { + t.Errorf("error writing state: %v", err) + } + if err = sm.PersistState(nil); err != nil { + t.Errorf("error persisting state: %v", err) + } + if err = sm.RefreshState(); err != nil { + t.Errorf("error refreshing state: %v", err) + } // Get the state again and verify that is now the same as state and has the "foo" value set to "bar" curState = sm.State() - assert.Equal(t, state, curState) + if !reflect.DeepEqual(state, curState) { + t.Errorf("expected %v == %v; but they were unequal", state, curState) + } curStateFoo = curState.OutputValue(addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) - require.NotNil(t, curStateFoo) - assert.Equal(t, "bar", curStateFoo.Value.AsString()) + if nil == curStateFoo { + t.Fatal("nil foo") + } + if foo := curStateFoo.Value.AsString(); foo != "bar" { + t.Errorf("expected foo to be bar, but got: %s", foo) + } } // TestRunServer allows running the server for local debugging; it runs until ctl-c is received @@ -355,7 +396,9 @@ func TestRunServer(t *testing.T) { t.Skip("TEST_RUN_SERVER not set") } s, err := NewHttpTestServer() - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error creating test server: %v", err) + } defer s.Close() t.Log(s.URL) From 0f7b3c7f7a423e91565fc8e481d8fe388e8e1756 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 15 Dec 2022 12:05:11 -0800 Subject: [PATCH 27/29] Fixed import consistency --- internal/backend/remote-state/oss/backend.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/backend/remote-state/oss/backend.go b/internal/backend/remote-state/oss/backend.go index 9487e14a5eca..468a883298c8 100644 --- a/internal/backend/remote-state/oss/backend.go +++ b/internal/backend/remote-state/oss/backend.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" "io/ioutil" "log" "net/http" @@ -16,6 +15,8 @@ import ( "strings" "time" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" From 96dc440d5da3f4d90d272a588209c1f19407675b Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 15 Dec 2022 17:39:41 -0800 Subject: [PATCH 28/29] Just use default openssl. --- internal/backend/remote-state/http/testdata/gencerts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/remote-state/http/testdata/gencerts.sh b/internal/backend/remote-state/http/testdata/gencerts.sh index 7a5b82312f6b..ebb987d8de55 100755 --- a/internal/backend/remote-state/http/testdata/gencerts.sh +++ b/internal/backend/remote-state/http/testdata/gencerts.sh @@ -8,7 +8,7 @@ set -ex # I was doing this on M1 mac and needed newer openssl to add the SAN IP; please export OPENSSL when invoking as needed -OPENSSL="${OPENSSL:-/opt/homebrew/Cellar/openssl@3/3.0.5/bin/openssl}" +OPENSSL="${OPENSSL:-openssl}" # Nuke and recreate the certs dir rm -rf certs From ba0844a9329c44f2cb3c4709ea767e3a54e5ce0c Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Thu, 15 Dec 2022 18:39:42 -0800 Subject: [PATCH 29/29] Since file(...) is so trivial to use, changed the client cert, key, and ca cert to be the data. See also https://github.com/hashicorp/terraform-provider-http/pull/211 --- internal/backend/remote-state/http/backend.go | 20 ++++++++----------- .../backend/remote-state/http/server_test.go | 18 ++++++++++++++--- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/internal/backend/remote-state/http/backend.go b/internal/backend/remote-state/http/backend.go index e45cdf9f5996..2048294cc36a 100644 --- a/internal/backend/remote-state/http/backend.go +++ b/internal/backend/remote-state/http/backend.go @@ -4,11 +4,11 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "log" "net/http" "net/url" - "os" "time" "github.com/hashicorp/go-retryablehttp" @@ -99,19 +99,19 @@ func New() backend.Backend { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", ""), - Description: "A path to a PEM-encoded CA certificate file used by the client to verify server certificates during TLS authentication.", + Description: "A PEM-encoded CA certificate chain used by the client to verify server certificates during TLS authentication.", }, "client_certificate_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERTIFICATE_PEM", ""), - Description: "A path to a PEM-encoded certificate file used by the server to verify the client during mutual TLS (mTLS) authentication.", + Description: "A PEM-encoded certificate used by the server to verify the client during mutual TLS (mTLS) authentication.", }, "client_private_key_pem": &schema.Schema{ Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""), - Description: "A path to a PEM-encoded private key, required if client_certificate_pem is specified.", + Description: "A PEM-encoded private key, required if client_certificate_pem is specified.", }, }, } @@ -154,20 +154,16 @@ func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.Resour } if clientCACertificatePem != "" { // trust servers based on a CA - caCertificateData, err := os.ReadFile(clientCACertificatePem) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", clientCACertificatePem, err) - } tlsConfig.RootCAs = x509.NewCertPool() - if !tlsConfig.RootCAs.AppendCertsFromPEM(caCertificateData) { - return fmt.Errorf("failed to append certs from file %s: %w", clientCACertificatePem, err) + if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(clientCACertificatePem)) { + return errors.New("failed to append certs") } } if clientCertificatePem != "" && clientPrivateKeyPem != "" { // attach a client certificate to the TLS handshake (aka mTLS) - certificate, err := tls.LoadX509KeyPair(clientCertificatePem, clientPrivateKeyPem) + certificate, err := tls.X509KeyPair([]byte(clientCertificatePem), []byte(clientPrivateKeyPem)) if err != nil { - return fmt.Errorf("cannot load certificate from %s and %s: %w", clientCertificatePem, clientPrivateKeyPem, err) + return fmt.Errorf("cannot load client certificate: %w", err) } tlsConfig.Certificates = []tls.Certificate{certificate} } diff --git a/internal/backend/remote-state/http/server_test.go b/internal/backend/remote-state/http/server_test.go index b7d84d34960d..ff793708aa08 100644 --- a/internal/backend/remote-state/http/server_test.go +++ b/internal/backend/remote-state/http/server_test.go @@ -310,13 +310,25 @@ func TestMTLSServer_WithCertPasses(t *testing.T) { // Configure the backend to the pre-populated sample state, and with all the test certs lined up url := ts.URL + "/state/sample" + caData, err := os.ReadFile("testdata/certs/ca.cert.pem") + if err != nil { + t.Fatalf("error reading ca certs: %v", err) + } + clientCertData, err := os.ReadFile("testdata/certs/client.crt") + if err != nil { + t.Fatalf("error reading client cert: %v", err) + } + clientKeyData, err := os.ReadFile("testdata/certs/client.key") + if err != nil { + t.Fatalf("error reading client key: %v", err) + } conf := map[string]cty.Value{ "address": cty.StringVal(url), "lock_address": cty.StringVal(url), "unlock_address": cty.StringVal(url), - "client_ca_certificate_pem": cty.StringVal("testdata/certs/ca.cert.pem"), - "client_certificate_pem": cty.StringVal("testdata/certs/client.crt"), - "client_private_key_pem": cty.StringVal("testdata/certs/client.key"), + "client_ca_certificate_pem": cty.StringVal(string(caData)), + "client_certificate_pem": cty.StringVal(string(clientCertData)), + "client_private_key_pem": cty.StringVal(string(clientKeyData)), } b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend) if nil == b {