diff --git a/wrappers/azurekeyvault/azurekeyvault.go b/wrappers/azurekeyvault/azurekeyvault.go index 4b4acd85..c18cb991 100644 --- a/wrappers/azurekeyvault/azurekeyvault.go +++ b/wrappers/azurekeyvault/azurekeyvault.go @@ -5,12 +5,19 @@ package azurekeyvault import ( "context" + "crypto/tls" + "crypto/x509" "encoding/base64" "errors" "fmt" + "net" + "net/http" "os" "strings" "sync/atomic" + "time" + + "golang.org/x/net/http2" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest" @@ -159,7 +166,7 @@ func (v *Wrapper) SetConfig(_ context.Context, opt ...wrapping.Option) (*wrappin v.baseURL = v.buildBaseURL() if v.client == nil { - client, err := v.getKeyVaultClient() + client, err := v.getKeyVaultClient(nil) if err != nil { return nil, fmt.Errorf("error initializing Azure Key Vault wrapper client: %w", err) } @@ -295,7 +302,7 @@ func (v *Wrapper) buildBaseURL() string { return fmt.Sprintf("https://%s.%s/", v.vaultName, v.environment.KeyVaultDNSSuffix) } -func (v *Wrapper) getKeyVaultClient() (*keyvault.BaseClient, error) { +func (v *Wrapper) getKeyVaultClient(withCertPool *x509.CertPool) (*keyvault.BaseClient, error) { var authorizer autorest.Authorizer var err error @@ -321,8 +328,42 @@ func (v *Wrapper) getKeyVaultClient() (*keyvault.BaseClient, error) { } } + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + customTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: dialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + Renegotiation: tls.RenegotiateFreelyAsClient, + RootCAs: withCertPool, + }, + } + if http2Transport, err := http2.ConfigureTransports(customTransport); err == nil { + // if the connection has been idle for 10 seconds, send a ping frame for a health check + http2Transport.ReadIdleTimeout = 10 * time.Second + // if there's no response to the ping within 2 seconds, close the connection + http2Transport.PingTimeout = 2 * time.Second + } + client := keyvault.New() client.Authorizer = authorizer + client.SendDecorators = append(client.SendDecorators, func(s autorest.Sender) autorest.Sender { + if ar, ok := s.(autorest.Client); ok { + ar.Sender = &http.Client{ + Transport: customTransport, + } + return ar + } + return s + }) return &client, nil } diff --git a/wrappers/azurekeyvault/azurekeyvault_acc_test.go b/wrappers/azurekeyvault/azurekeyvault_acc_test.go index 2d726ac3..1ba45886 100644 --- a/wrappers/azurekeyvault/azurekeyvault_acc_test.go +++ b/wrappers/azurekeyvault/azurekeyvault_acc_test.go @@ -5,11 +5,17 @@ package azurekeyvault import ( "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "net/http/httptest" "os" "reflect" "testing" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" + "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/stretchr/testify/assert" @@ -102,3 +108,64 @@ func TestAzureKeyVault_Lifecycle(t *testing.T) { t.Fatalf("expected %s, got %s", input, pt) } } + +func Test_getKeyVaultClient(t *testing.T) { + t.Parallel() + config := map[string]string{ + "disallow_env_vars": "true", + "tenant_id": "a-tenant-id", + "client_id": "a-client-id", + "client_secret": "a-client-secret", + "environment": azure.PublicCloud.Name, + "resource": "a-resource", + "vault_name": "a-vault-name", + "key_name": "a-key-name", + } + s := NewWrapper() + _, err := s.SetConfig( + context.Background(), + wrapping.WithConfigMap(config), + WithKeyNotRequired(true), + ) + require.NoError(t, err) + t.Run("send-decorators-set", func(t *testing.T) { + // let's at least ensure that the custom SendDecorator is being properly + // set. + t.Parallel() + got, err := s.getKeyVaultClient(nil) + require.NoError(t, err) + assert.NotEmpty(t, got.SendDecorators) + }) + t.Run("force-tls-error", func(t *testing.T) { + // not great, but this test will at least ensure that the client's + // custom TLS transport is being used + t.Parallel() + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(fmt.Sprintf("version: %s", tls.VersionName(r.TLS.Version)))) + })) + ts.TLS = &tls.Config{ + MinVersion: tls.VersionTLS10, + MaxVersion: tls.VersionTLS10, + } + ts.StartTLS() + defer ts.Close() + + certPool := x509.NewCertPool() + certPool.AddCert(ts.Certificate()) + + assert.NoError(t, err) + client, err := s.getKeyVaultClient(certPool) + require.NoError(t, err) + assert.NotEmpty(t, client.SendDecorators) + client.Authorizer = &authorizer{} + _, err = client.GetKey(context.Background(), ts.URL, "global", "1") + require.Error(t, err) + assert.ErrorContains(t, err, "tls: protocol version not supported") + }) +} + +type authorizer struct{} + +func (*authorizer) WithAuthorization() autorest.PrepareDecorator { + return autorest.WithNothing() +} diff --git a/wrappers/azurekeyvault/go.mod b/wrappers/azurekeyvault/go.mod index 1c9a786a..f456bd8c 100644 --- a/wrappers/azurekeyvault/go.mod +++ b/wrappers/azurekeyvault/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/go-hclog v1.4.0 github.com/hashicorp/go-kms-wrapping/v2 v2.0.9-0.20230228100945-740d2999c798 github.com/stretchr/testify v1.8.2 + golang.org/x/net v0.6.0 ) require ( @@ -33,6 +34,7 @@ require ( github.com/rogpeppe/go-internal v1.6.1 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/wrappers/azurekeyvault/go.sum b/wrappers/azurekeyvault/go.sum index bdbc20ee..89dcc9c6 100644 --- a/wrappers/azurekeyvault/go.sum +++ b/wrappers/azurekeyvault/go.sum @@ -85,6 +85,8 @@ golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -99,6 +101,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=