diff --git a/docs/content/dns/zz_gen_lightsail.md b/docs/content/dns/zz_gen_lightsail.md index b32ee1b3..d5bd035b 100644 --- a/docs/content/dns/zz_gen_lightsail.md +++ b/docs/content/dns/zz_gen_lightsail.md @@ -95,7 +95,7 @@ Alternatively, you can also set the `Resource` to `*` (wildcard), which allow to ## More information -- [Go client](https://github.com/aws/aws-sdk-go/) +- [Go client](https://github.com/aws/aws-sdk-go-v2) diff --git a/docs/content/dns/zz_gen_route53.md b/docs/content/dns/zz_gen_route53.md index fcc7c242..cccd1d36 100644 --- a/docs/content/dns/zz_gen_route53.md +++ b/docs/content/dns/zz_gen_route53.md @@ -178,7 +178,7 @@ Replace `Z11111112222222333333` with your hosted zone ID and `example.com` with ## More information - [API documentation](https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html) -- [Go client](https://github.com/aws/aws-sdk-go/aws) +- [Go client](https://github.com/aws/aws-sdk-go-v2) diff --git a/go.mod b/go.mod index b3d2534f..dd44dc41 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/go-acme/lego/v4 go 1.19 // github.com/exoscale/egoscale v1.19.0 => It is an error, please don't use it. + require ( cloud.google.com/go/compute/metadata v0.2.3 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible @@ -17,7 +18,12 @@ require ( github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 - github.com/aws/aws-sdk-go v1.39.0 + github.com/aws/aws-sdk-go-v2 v1.19.0 + github.com/aws/aws-sdk-go-v2/config v1.18.28 + github.com/aws/aws-sdk-go-v2/credentials v1.13.27 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 + github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 + github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 github.com/cenkalti/backoff/v4 v4.2.1 github.com/civo/civogo v0.3.11 github.com/cloudflare/cloudflare-go v0.70.0 @@ -89,6 +95,14 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect + github.com/aws/smithy-go v1.13.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index bf4f5f61..1b701724 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,34 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo= -github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k= +github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw= +github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco= +github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY= +github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -220,6 +246,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= diff --git a/platform/tester/env.go b/platform/tester/env.go index 1cbfd279..54890873 100644 --- a/platform/tester/env.go +++ b/platform/tester/env.go @@ -51,6 +51,11 @@ func (e *EnvTest) WithLiveTestRequirements(keys ...string) *EnvTest { panic(fmt.Sprintf("Unauthorized action, the env var %s is not managed or it's not the key of the domain.", key)) } + if e.domainKey == key { + countValuedVars++ + continue + } + if _, ok := e.values[key]; ok { countValuedVars++ } diff --git a/platform/tester/env_test.go b/platform/tester/env_test.go index 23980a1f..25748f8f 100644 --- a/platform/tester/env_test.go +++ b/platform/tester/env_test.go @@ -148,6 +148,22 @@ func TestEnvTest(t *testing.T) { assert.Equal(t, "", envTest.GetDomain()) }, }, + { + desc: "WithLiveTestRequirements with domain as requirement", + envVars: map[string]string{ + envVar01: "A", + envVar02: "B", + }, + envTestSetup: func() *tester.EnvTest { + return tester.NewEnvTest(envVar01, envVar02).WithDomain(envVarDomain).WithLiveTestRequirements(envVar02, envVarDomain) + }, + expected: func(t *testing.T, envTest *tester.EnvTest) { + assert.True(t, envTest.IsLiveTest()) + assert.Equal(t, "A", envTest.GetValue(envVar01)) + assert.Equal(t, "B", envTest.GetValue(envVar02)) + assert.Equal(t, "", envTest.GetDomain()) + }, + }, { desc: "WithLiveTestRequirements non required var missing", envVars: map[string]string{ diff --git a/providers/dns/lightsail/lightsail.go b/providers/dns/lightsail/lightsail.go index e08a1f18..125b1aa6 100644 --- a/providers/dns/lightsail/lightsail.go +++ b/providers/dns/lightsail/lightsail.go @@ -2,17 +2,18 @@ package lightsail import ( + "context" "errors" "fmt" "math/rand" "strconv" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/client" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/lightsail" + awstypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" ) @@ -32,27 +33,6 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) -// customRetryer implements the client.Retryer interface by composing the DefaultRetryer. -// It controls the logic for retrying recoverable request errors (e.g. when rate limits are exceeded). -type customRetryer struct { - client.DefaultRetryer -} - -// RetryRules overwrites the DefaultRetryer's method. -// It uses a basic exponential backoff algorithm that returns an initial -// delay of ~400ms with an upper limit of ~30 seconds which should prevent -// causing a high number of consecutive throttling errors. -// For reference: Route 53 enforces an account-wide(!) 5req/s query limit. -func (c customRetryer) RetryRules(r *request.Request) time.Duration { - retryCount := r.RetryCount - if retryCount > 7 { - retryCount = 7 - } - - delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) - return time.Duration(delay) * time.Millisecond -} - // Config is used to configure the creation of the DNSProvider. type Config struct { DNSZone string @@ -71,7 +51,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - client *lightsail.Lightsail + client *lightsail.Client config *Config } @@ -102,35 +82,55 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("lightsail: the configuration of the DNS provider is nil") } - retryer := customRetryer{} - retryer.NumMaxRetries = maxRetries + ctx := context.Background() - conf := aws.NewConfig().WithRegion(config.Region) - sess, err := session.NewSession(request.WithRetryer(conf, retryer)) + cfg, err := awsconfig.LoadDefaultConfig(ctx, + awsconfig.WithRegion(config.Region), + awsconfig.WithRetryer(func() aws.Retryer { + return retry.NewStandard(func(options *retry.StandardOptions) { + options.MaxAttempts = maxRetries + + // It uses a basic exponential backoff algorithm that returns an initial + // delay of ~400ms with an upper limit of ~30 seconds which should prevent + // causing a high number of consecutive throttling errors. + // For reference: Route 53 enforces an account-wide(!) 5req/s query limit. + options.Backoff = retry.BackoffDelayerFunc(func(attempt int, err error) (time.Duration, error) { + retryCount := attempt + if retryCount > 7 { + retryCount = 7 + } + + delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) + return time.Duration(delay) * time.Millisecond, nil + }) + }) + }), + ) if err != nil { return nil, err } return &DNSProvider{ config: config, - client: lightsail.New(sess), + client: lightsail.NewFromConfig(cfg), }, nil } // Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + ctx := context.Background() info := dns01.GetChallengeInfo(domain, keyAuth) params := &lightsail.CreateDomainEntryInput{ DomainName: aws.String(d.config.DNSZone), - DomainEntry: &lightsail.DomainEntry{ + DomainEntry: &awstypes.DomainEntry{ Name: aws.String(info.EffectiveFQDN), Target: aws.String(strconv.Quote(info.Value)), Type: aws.String("TXT"), }, } - _, err := d.client.CreateDomainEntry(params) + _, err := d.client.CreateDomainEntry(ctx, params) if err != nil { return fmt.Errorf("lightsail: %w", err) } @@ -139,19 +139,20 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } // CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + ctx := context.Background() info := dns01.GetChallengeInfo(domain, keyAuth) params := &lightsail.DeleteDomainEntryInput{ DomainName: aws.String(d.config.DNSZone), - DomainEntry: &lightsail.DomainEntry{ + DomainEntry: &awstypes.DomainEntry{ Name: aws.String(info.EffectiveFQDN), Type: aws.String("TXT"), Target: aws.String(strconv.Quote(info.Value)), }, } - _, err := d.client.DeleteDomainEntry(params) + _, err := d.client.DeleteDomainEntry(ctx, params) if err != nil { return fmt.Errorf("lightsail: %w", err) } diff --git a/providers/dns/lightsail/lightsail.toml b/providers/dns/lightsail/lightsail.toml index fab06ef7..4ade894d 100644 --- a/providers/dns/lightsail/lightsail.toml +++ b/providers/dns/lightsail/lightsail.toml @@ -56,4 +56,4 @@ Alternatively, you can also set the `Resource` to `*` (wildcard), which allow to LIGHTSAIL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" [Links] - GoClient = "https://github.com/aws/aws-sdk-go/" + GoClient = "https://github.com/aws/aws-sdk-go-v2" diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index 4eb79976..20e45ee2 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -1,11 +1,12 @@ package lightsail import ( + "context" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/lightsail" "github.com/stretchr/testify/require" ) @@ -24,13 +25,15 @@ func TestLiveTTL(t *testing.T) { err = provider.Present(domain, "foo", "bar") require.NoError(t, err) - // we need a separate Lightsail client here as the one in the DNS provider is - // unexported. + // we need a separate Lightsail client here as the one in the DNS provider is unexported. fqdn := "_acme-challenge." + domain - sess, err := session.NewSession() + + ctx := context.Background() + + cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) - svc := lightsail.New(sess) + svc := lightsail.NewFromConfig(cfg) require.NoError(t, err) defer func() { @@ -44,15 +47,24 @@ func TestLiveTTL(t *testing.T) { DomainName: aws.String(domain), } - resp, err := svc.GetDomain(params) + resp, err := svc.GetDomain(ctx, params) require.NoError(t, err) entries := resp.Domain.DomainEntries for _, entry := range entries { - if aws.StringValue(entry.Type) == "TXT" && aws.StringValue(entry.Name) == fqdn { + if deref(entry.Type) == "TXT" && deref(entry.Name) == fqdn { return } } t.Fatalf("Could not find a TXT record for _acme-challenge.%s", domain) } + +func deref[T string | int | int32 | int64 | bool](v *T) T { + if v == nil { + var zero T + return zero + } + + return *v +} diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index f385745a..8ff60c11 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -1,14 +1,16 @@ package lightsail import ( + "context" "os" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/lightsail" "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -29,23 +31,26 @@ var envTest = tester.NewEnvTest( WithDomain(EnvDNSZone). WithLiveTestRequirements(envAwsAccessKeyID, envAwsSecretAccessKey, EnvDNSZone) -func makeProvider(serverURL string) (*DNSProvider, error) { - config := &aws.Config{ - Credentials: credentials.NewStaticCredentials("abc", "123", " "), - Endpoint: aws.String(serverURL), - Region: aws.String("mock-region"), - MaxRetries: aws.Int(1), +type endpointResolverMock struct { + endpoint string +} + +func (e endpointResolverMock) ResolveEndpoint(_, _ string, _ ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: e.endpoint}, nil +} + +func makeProvider(serverURL string) *DNSProvider { + config := aws.Config{ + Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), + Region: "mock-region", + EndpointResolverWithOptions: endpointResolverMock{endpoint: serverURL}, + RetryMaxAttempts: 1, } - sess, err := session.NewSession(config) - if err != nil { - return nil, err + return &DNSProvider{ + client: lightsail.NewFromConfig(config), + config: NewDefaultConfig(), } - - conf := NewDefaultConfig() - - client := lightsail.New(sess) - return &DNSProvider{client: client, config: conf}, nil } func TestCredentialsFromEnv(t *testing.T) { @@ -56,15 +61,19 @@ func TestCredentialsFromEnv(t *testing.T) { _ = os.Setenv(envAwsSecretAccessKey, "123") _ = os.Setenv(envAwsRegion, "us-east-1") - config := &aws.Config{ - CredentialsChainVerboseErrors: aws.Bool(true), - } - - sess, err := session.NewSession(config) + ctx := context.Background() + cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) - _, err = sess.Config.Credentials.Get() + cs, err := cfg.Credentials.Retrieve(ctx) require.NoError(t, err, "Expected credentials to be set from environment") + + expected := aws.Credentials{ + AccessKeyID: "123", + SecretAccessKey: "123", + Source: "EnvConfigCredentials", + } + assert.Equal(t, expected, cs) } func TestDNSProvider_Present(t *testing.T) { @@ -74,12 +83,11 @@ func TestDNSProvider_Present(t *testing.T) { serverURL := newMockServer(t, mockResponses) - provider, err := makeProvider(serverURL) - require.NoError(t, err) + provider := makeProvider(serverURL) domain := "example.com" keyAuth := "123456d==" - err = provider.Present(domain, "", keyAuth) + err := provider.Present(domain, "", keyAuth) require.NoError(t, err, "Expected Present to return no error") } diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 3a69b2ef..b6e23757 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -2,19 +2,21 @@ package route53 import ( + "context" "errors" "fmt" "math/rand" "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/client" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/credentials/stscreds" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/route53" + awstypes "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" @@ -55,7 +57,7 @@ type Config struct { PropagationTimeout time.Duration PollingInterval time.Duration - Client *route53.Route53 + Client *route53.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -74,31 +76,10 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - client *route53.Route53 + client *route53.Client config *Config } -// customRetryer implements the client.Retryer interface by composing the DefaultRetryer. -// It controls the logic for retrying recoverable request errors (e.g. when rate limits are exceeded). -type customRetryer struct { - client.DefaultRetryer -} - -// RetryRules overwrites the DefaultRetryer's method. -// It uses a basic exponential backoff algorithm: -// that returns an initial delay of ~400ms with an upper limit of ~30 seconds, -// which should prevent causing a high number of consecutive throttling errors. -// For reference: Route 53 enforces an account-wide(!) 5req/s query limit. -func (d customRetryer) RetryRules(r *request.Request) time.Duration { - retryCount := r.RetryCount - if retryCount > 7 { - retryCount = 7 - } - - delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) - return time.Duration(delay) * time.Millisecond -} - // NewDNSProvider returns a DNSProvider instance configured for the AWS Route 53 service. // // AWS Credentials are automatically detected in the following locations and prioritized in the following order: @@ -124,13 +105,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return &DNSProvider{client: config.Client, config: config}, nil } - sess, err := createSession(config) + ctx := context.Background() + + cfg, err := createAWSConfig(ctx, config) if err != nil { return nil, err } return &DNSProvider{ - client: route53.New(sess), + client: route53.NewFromConfig(cfg), config: config, }, nil } @@ -142,14 +125,15 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() info := dns01.GetChallengeInfo(domain, keyAuth) - hostedZoneID, err := d.getHostedZoneID(info.EffectiveFQDN) + hostedZoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("route53: failed to determine hosted zone ID: %w", err) } - records, err := d.getExistingRecordSets(hostedZoneID, info.EffectiveFQDN) + records, err := d.getExistingRecordSets(ctx, hostedZoneID, info.EffectiveFQDN) if err != nil { return fmt.Errorf("route53: %w", err) } @@ -158,39 +142,41 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { var found bool for _, record := range records { - if aws.StringValue(record.Value) == realValue { + if deref(record.Value) == realValue { found = true } } if !found { - records = append(records, &route53.ResourceRecord{Value: aws.String(realValue)}) + records = append(records, awstypes.ResourceRecord{Value: aws.String(realValue)}) } - recordSet := &route53.ResourceRecordSet{ + recordSet := &awstypes.ResourceRecordSet{ Name: aws.String(info.EffectiveFQDN), - Type: aws.String("TXT"), + Type: "TXT", TTL: aws.Int64(int64(d.config.TTL)), ResourceRecords: records, } - err = d.changeRecord(route53.ChangeActionUpsert, hostedZoneID, recordSet) + err = d.changeRecord(ctx, awstypes.ChangeActionUpsert, hostedZoneID, recordSet) if err != nil { return fmt.Errorf("route53: %w", err) } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() info := dns01.GetChallengeInfo(domain, keyAuth) - hostedZoneID, err := d.getHostedZoneID(info.EffectiveFQDN) + hostedZoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("failed to determine Route 53 hosted zone ID: %w", err) } - records, err := d.getExistingRecordSets(hostedZoneID, info.EffectiveFQDN) + records, err := d.getExistingRecordSets(ctx, hostedZoneID, info.EffectiveFQDN) if err != nil { return fmt.Errorf("route53: %w", err) } @@ -199,33 +185,33 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - recordSet := &route53.ResourceRecordSet{ + recordSet := &awstypes.ResourceRecordSet{ Name: aws.String(info.EffectiveFQDN), - Type: aws.String("TXT"), + Type: "TXT", TTL: aws.Int64(int64(d.config.TTL)), ResourceRecords: records, } - err = d.changeRecord(route53.ChangeActionDelete, hostedZoneID, recordSet) + err = d.changeRecord(ctx, awstypes.ChangeActionDelete, hostedZoneID, recordSet) if err != nil { return fmt.Errorf("route53: %w", err) } return nil } -func (d *DNSProvider) changeRecord(action, hostedZoneID string, recordSet *route53.ResourceRecordSet) error { +func (d *DNSProvider) changeRecord(ctx context.Context, action awstypes.ChangeAction, hostedZoneID string, recordSet *awstypes.ResourceRecordSet) error { recordSetInput := &route53.ChangeResourceRecordSetsInput{ HostedZoneId: aws.String(hostedZoneID), - ChangeBatch: &route53.ChangeBatch{ + ChangeBatch: &awstypes.ChangeBatch{ Comment: aws.String("Managed by Lego"), - Changes: []*route53.Change{{ - Action: aws.String(action), + Changes: []awstypes.Change{{ + Action: action, ResourceRecordSet: recordSet, }}, }, } - resp, err := d.client.ChangeResourceRecordSets(recordSetInput) + resp, err := d.client.ChangeResourceRecordSets(ctx, recordSetInput) if err != nil { return fmt.Errorf("failed to change record set: %w", err) } @@ -235,26 +221,26 @@ func (d *DNSProvider) changeRecord(action, hostedZoneID string, recordSet *route return wait.For("route53", d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { reqParams := &route53.GetChangeInput{Id: changeID} - resp, err := d.client.GetChange(reqParams) + resp, err := d.client.GetChange(ctx, reqParams) if err != nil { return false, fmt.Errorf("failed to query change status: %w", err) } - if aws.StringValue(resp.ChangeInfo.Status) == route53.ChangeStatusInsync { + if resp.ChangeInfo.Status == awstypes.ChangeStatusInsync { return true, nil } - return false, fmt.Errorf("unable to retrieve change: ID=%s", aws.StringValue(changeID)) + return false, fmt.Errorf("unable to retrieve change: ID=%s", deref(changeID)) }) } -func (d *DNSProvider) getExistingRecordSets(hostedZoneID, fqdn string) ([]*route53.ResourceRecord, error) { +func (d *DNSProvider) getExistingRecordSets(ctx context.Context, hostedZoneID, fqdn string) ([]awstypes.ResourceRecord, error) { listInput := &route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(hostedZoneID), StartRecordName: aws.String(fqdn), - StartRecordType: aws.String("TXT"), + StartRecordType: "TXT", } - recordSetsOutput, err := d.client.ListResourceRecordSets(listInput) + recordSetsOutput, err := d.client.ListResourceRecordSets(ctx, listInput) if err != nil { return nil, err } @@ -263,10 +249,10 @@ func (d *DNSProvider) getExistingRecordSets(hostedZoneID, fqdn string) ([]*route return nil, nil } - var records []*route53.ResourceRecord + var records []awstypes.ResourceRecord for _, recordSet := range recordSetsOutput.ResourceRecordSets { - if aws.StringValue(recordSet.Name) == fqdn { + if deref(recordSet.Name) == fqdn { records = append(records, recordSet.ResourceRecords...) } } @@ -274,7 +260,7 @@ func (d *DNSProvider) getExistingRecordSets(hostedZoneID, fqdn string) ([]*route return records, nil } -func (d *DNSProvider) getHostedZoneID(fqdn string) (string, error) { +func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) { if d.config.HostedZoneID != "" { return d.config.HostedZoneID, nil } @@ -288,7 +274,7 @@ func (d *DNSProvider) getHostedZoneID(fqdn string) (string, error) { reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(dns01.UnFqdn(authZone)), } - resp, err := d.client.ListHostedZonesByName(reqParams) + resp, err := d.client.ListHostedZonesByName(ctx, reqParams) if err != nil { return "", err } @@ -296,8 +282,8 @@ func (d *DNSProvider) getHostedZoneID(fqdn string) (string, error) { var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot - if !aws.BoolValue(hostedZone.Config.PrivateZone) && aws.StringValue(hostedZone.Name) == authZone { - hostedZoneID = aws.StringValue(hostedZone.Id) + if !hostedZone.Config.PrivateZone && deref(hostedZone.Name) == authZone { + hostedZoneID = deref(hostedZone.Id) break } } @@ -311,45 +297,60 @@ func (d *DNSProvider) getHostedZoneID(fqdn string) (string, error) { return hostedZoneID, nil } -func createSession(config *Config) (*session.Session, error) { - if err := createSessionCheckParams(config); err != nil { - return nil, err +func createAWSConfig(ctx context.Context, config *Config) (aws.Config, error) { + if err := createAWSConfigCheckParams(config); err != nil { + return aws.Config{}, err } - retry := customRetryer{} - retry.NumMaxRetries = config.MaxRetries + optFns := []func(options *awsconfig.LoadOptions) error{ + awsconfig.WithRetryer(func() aws.Retryer { + return retry.NewStandard(func(options *retry.StandardOptions) { + options.MaxAttempts = config.MaxRetries + + // It uses a basic exponential backoff algorithm that returns an initial + // delay of ~400ms with an upper limit of ~30 seconds which should prevent + // causing a high number of consecutive throttling errors. + // For reference: Route 53 enforces an account-wide(!) 5req/s query limit. + options.Backoff = retry.BackoffDelayerFunc(func(attempt int, err error) (time.Duration, error) { + retryCount := attempt + if retryCount > 7 { + retryCount = 7 + } + + delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) + return time.Duration(delay) * time.Millisecond, nil + }) + }) + }), + } - awsConfig := aws.NewConfig() if config.AccessKeyID != "" && config.SecretAccessKey != "" { - awsConfig = awsConfig.WithCredentials(credentials.NewStaticCredentials(config.AccessKeyID, config.SecretAccessKey, config.SessionToken)) + optFns = append(optFns, + awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(config.AccessKeyID, config.SecretAccessKey, config.SessionToken)), + ) } if config.Region != "" { - awsConfig = awsConfig.WithRegion(config.Region) + optFns = append(optFns, awsconfig.WithRegion(config.Region)) } - sessionCfg := request.WithRetryer(awsConfig, retry) - - sess, err := session.NewSessionWithOptions(session.Options{Config: *sessionCfg}) + cfg, err := awsconfig.LoadDefaultConfig(ctx, optFns...) if err != nil { - return nil, err + return aws.Config{}, err } - if config.AssumeRoleArn == "" { - return sess, nil - } - - return session.NewSession(&aws.Config{ - Region: sess.Config.Region, - Credentials: stscreds.NewCredentials(sess, config.AssumeRoleArn, func(arp *stscreds.AssumeRoleProvider) { + if config.AssumeRoleArn != "" { + cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), config.AssumeRoleArn, func(options *stscreds.AssumeRoleOptions) { if config.ExternalID != "" { - arp.ExternalID = &config.ExternalID + options.ExternalID = &config.ExternalID } - }), - }) + }) + } + + return cfg, nil } -func createSessionCheckParams(config *Config) error { +func createAWSConfigCheckParams(config *Config) error { if config == nil { return errors.New("config is nil") } @@ -364,3 +365,12 @@ func createSessionCheckParams(config *Config) error { return nil } + +func deref[T string | int | int32 | int64 | bool](v *T) T { + if v == nil { + var zero T + return zero + } + + return *v +} diff --git a/providers/dns/route53/route53.toml b/providers/dns/route53/route53.toml index 07e10d9c..5f3309e5 100644 --- a/providers/dns/route53/route53.toml +++ b/providers/dns/route53/route53.toml @@ -140,4 +140,4 @@ Replace `Z11111112222222333333` with your hosted zone ID and `example.com` with [Links] API = "https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html" - GoClient = "https://github.com/aws/aws-sdk-go/aws" + GoClient = "https://github.com/aws/aws-sdk-go-v2" diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go index acc301bc..2fbcf520 100644 --- a/providers/dns/route53/route53_integration_test.go +++ b/providers/dns/route53/route53_integration_test.go @@ -1,11 +1,12 @@ package route53 import ( + "context" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/stretchr/testify/require" ) @@ -26,9 +27,13 @@ func TestLiveTTL(t *testing.T) { // we need a separate R53 client here as the one in the DNS provider is unexported. fqdn := "_acme-challenge." + domain + "." - sess, err := session.NewSession() + + ctx := context.Background() + + cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) - svc := route53.New(sess) + + svc := route53.NewFromConfig(cfg) defer func() { errC := provider.CleanUp(domain, "foo", "bar") @@ -37,17 +42,17 @@ func TestLiveTTL(t *testing.T) { } }() - zoneID, err := provider.getHostedZoneID(fqdn) + zoneID, err := provider.getHostedZoneID(context.Background(), fqdn) require.NoError(t, err) params := &route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(zoneID), } - resp, err := svc.ListResourceRecordSets(params) + resp, err := svc.ListResourceRecordSets(ctx, params) require.NoError(t, err) for _, v := range resp.ResourceRecordSets { - if aws.StringValue(v.Name) == fqdn && aws.StringValue(v.Type) == "TXT" && aws.Int64Value(v.TTL) == 10 { + if deref(v.Name) == fqdn && v.Type == "TXT" && deref(v.TTL) == 10 { return } } diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index f1dd1a29..1c8e5f5f 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -1,14 +1,15 @@ package route53 import ( + "context" "os" "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/go-acme/lego/v4/platform/tester" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,21 +29,26 @@ var envTest = tester.NewEnvTest( WithDomain(envDomain). WithLiveTestRequirements(EnvAccessKeyID, EnvSecretAccessKey, EnvRegion, envDomain) +type endpointResolverMock struct { + endpoint string +} + +func (e endpointResolverMock) ResolveEndpoint(_, _ string, _ ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: e.endpoint}, nil +} + func makeTestProvider(t *testing.T, serverURL string) *DNSProvider { t.Helper() - config := &aws.Config{ - Credentials: credentials.NewStaticCredentials("abc", "123", " "), - Endpoint: aws.String(serverURL), - Region: aws.String("mock-region"), - MaxRetries: aws.Int(1), + cfg := aws.Config{ + Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), + Region: "mock-region", + EndpointResolverWithOptions: endpointResolverMock{endpoint: serverURL}, + RetryMaxAttempts: 1, } - sess, err := session.NewSession(config) - require.NoError(t, err) - return &DNSProvider{ - client: route53.New(sess), + client: route53.NewFromConfig(cfg), config: NewDefaultConfig(), } } @@ -55,22 +61,21 @@ func Test_loadCredentials_FromEnv(t *testing.T) { _ = os.Setenv(EnvSecretAccessKey, "456") _ = os.Setenv(EnvRegion, "us-east-1") - config := &aws.Config{ - CredentialsChainVerboseErrors: aws.Bool(true), - } + ctx := context.Background() - sess, err := session.NewSession(config) + cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) - value, err := sess.Config.Credentials.Get() + value, err := cfg.Credentials.Retrieve(ctx) require.NoError(t, err, "Expected credentials to be set from environment") - expected := credentials.Value{ + expected := aws.Credentials{ AccessKeyID: "123", SecretAccessKey: "456", SessionToken: "", - ProviderName: "EnvConfigCredentials", + Source: "EnvConfigCredentials", } + assert.Equal(t, expected, value) } @@ -78,13 +83,12 @@ func Test_loadRegion_FromEnv(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - os.Setenv(EnvRegion, route53.CloudWatchRegionUsEast1) + _ = os.Setenv(EnvRegion, "foo") - sess, err := session.NewSession(aws.NewConfig()) + cfg, err := awsconfig.LoadDefaultConfig(context.Background()) require.NoError(t, err) - region := aws.StringValue(sess.Config.Region) - assert.Equal(t, route53.CloudWatchRegionUsEast1, region, "Region") + assert.Equal(t, "foo", cfg.Region, "Region") } func Test_getHostedZoneID_FromEnv(t *testing.T) { @@ -93,12 +97,12 @@ func Test_getHostedZoneID_FromEnv(t *testing.T) { expectedZoneID := "zoneID" - os.Setenv(EnvHostedZoneID, expectedZoneID) + _ = os.Setenv(EnvHostedZoneID, expectedZoneID) provider, err := NewDNSProvider() require.NoError(t, err) - hostedZoneID, err := provider.getHostedZoneID("whatever") + hostedZoneID, err := provider.getHostedZoneID(context.Background(), "whatever") require.NoError(t, err, "HostedZoneID") assert.Equal(t, expectedZoneID, hostedZoneID) @@ -144,7 +148,7 @@ func TestNewDefaultConfig(t *testing.T) { t.Run(test.desc, func(t *testing.T) { envTest.ClearEnv() for key, value := range test.envVars { - os.Setenv(key, value) + _ = os.Setenv(key, value) } config := NewDefaultConfig() @@ -156,9 +160,9 @@ func TestNewDefaultConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { mockResponses := MockResponseMap{ - "/2013-04-01/hostedzonesbyname": {StatusCode: 200, Body: ListHostedZonesByNameResponse}, - "/2013-04-01/hostedzone/ABCDEFG/rrset/": {StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, - "/2013-04-01/change/123456": {StatusCode: 200, Body: GetChangeResponse}, + "/2013-04-01/hostedzonesbyname": {StatusCode: 200, Body: ListHostedZonesByNameResponse}, + "/2013-04-01/hostedzone/ABCDEFG/rrset": {StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, + "/2013-04-01/change/123456": {StatusCode: 200, Body: GetChangeResponse}, "/2013-04-01/hostedzone/ABCDEFG/rrset?name=_acme-challenge.example.com.&type=TXT": { StatusCode: 200, Body: "", @@ -178,12 +182,12 @@ func TestDNSProvider_Present(t *testing.T) { require.NoError(t, err, "Expected Present to return no error") } -func TestCreateSession(t *testing.T) { +func Test_createAWSConfig(t *testing.T) { testCases := []struct { desc string env map[string]string config *Config - wantCreds credentials.Value + wantCreds aws.Credentials wantDefaultChain bool wantRegion string wantErr string @@ -218,11 +222,11 @@ func TestCreateSession(t *testing.T) { AccessKeyID: "one", SecretAccessKey: "two", }, - wantCreds: credentials.Value{ + wantCreds: aws.Credentials{ AccessKeyID: "one", SecretAccessKey: "two", SessionToken: "", - ProviderName: credentials.StaticProviderName, + Source: credentials.StaticCredentialsName, }, }, { @@ -232,11 +236,11 @@ func TestCreateSession(t *testing.T) { SecretAccessKey: "two", SessionToken: "three", }, - wantCreds: credentials.Value{ + wantCreds: aws.Credentials{ AccessKeyID: "one", SecretAccessKey: "two", SessionToken: "three", - ProviderName: credentials.StaticProviderName, + Source: credentials.StaticCredentialsName, }, }, { @@ -268,24 +272,26 @@ func TestCreateSession(t *testing.T) { envTest.Apply(test.env) - sess, err := createSession(test.config) + ctx := context.Background() + + cfg, err := createAWSConfig(ctx, test.config) requireErr(t, err, test.wantErr) if err != nil { return } - gotCreds, err := sess.Config.Credentials.Get() + gotCreds, err := cfg.Credentials.Retrieve(ctx) if test.wantDefaultChain { - assert.NotEqual(t, credentials.StaticProviderName, gotCreds.ProviderName) + assert.NotEqual(t, credentials.StaticCredentialsName, gotCreds.Source) } else { require.NoError(t, err) assert.Equal(t, test.wantCreds, gotCreds) } if test.wantRegion != "" { - assert.Equal(t, test.wantRegion, aws.StringValue(sess.Config.Region)) + assert.Equal(t, test.wantRegion, cfg.Region) } }) }