diff --git a/cmd/vulcan-aws-trusted-advisor/main.go b/cmd/vulcan-aws-trusted-advisor/main.go index f457ab548..d57498f49 100644 --- a/cmd/vulcan-aws-trusted-advisor/main.go +++ b/cmd/vulcan-aws-trusted-advisor/main.go @@ -1,7 +1,6 @@ /* Copyright 2019 Adevinta */ - package main import ( @@ -17,20 +16,19 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws/awserr" - - "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/iam" - "github.com/aws/aws-sdk-go/service/support" - "github.com/sirupsen/logrus" - check "github.com/adevinta/vulcan-check-sdk" "github.com/adevinta/vulcan-check-sdk/helpers" checkstate "github.com/adevinta/vulcan-check-sdk/state" report "github.com/adevinta/vulcan-report" - "github.com/aws/aws-sdk-go/aws/arn" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/aws/aws-sdk-go-v2/service/support" + "github.com/sirupsen/logrus" ) const ( @@ -107,39 +105,50 @@ func extractLinesFromHTML(htmlText string) []string { return result } -func scanAccount(opt options, target, assetType string, logger *logrus.Entry, state checkstate.State) error { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String("us-east-1"), - }) - if err != nil { - return err - } - +func scanAccount(opt options, target, _ string, logger *logrus.Entry, state checkstate.State) error { assumeRoleEndpoint := os.Getenv("VULCAN_ASSUME_ROLE_ENDPOINT") role := os.Getenv("ROLE_NAME") - isReachable, err := helpers.IsReachable(target, assetType, - helpers.NewAWSCreds(assumeRoleEndpoint, role)) - if err != nil { - logger.Warnf("Can not check asset reachability: %v", err) - } - if !isReachable { - return checkstate.ErrAssetUnreachable - } - parsedARN, err := arn.Parse(target) if err != nil { return err } - creds, err := getCredentials(assumeRoleEndpoint, parsedARN.AccountID, role, logger) - if err != nil { - return err + var cfg aws.Config + if assumeRoleEndpoint != "" { + creds, err := getCredentials(assumeRoleEndpoint, parsedARN.AccountID, role, logger) + if err != nil { + if errors.Is(err, errNoCredentials) { + return checkstate.ErrAssetUnreachable + } + return err + } + credsProvider := credentials.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken) + cfg, err = config.LoadDefaultConfig(context.Background(), + config.WithRegion("us-east-1"), + config.WithCredentialsProvider(credsProvider), + ) + if err != nil { + return fmt.Errorf("unable to create AWS config: %w", err) + } + } else { + // try to access with the default credentials + cfg, err = config.LoadDefaultConfig(context.Background(), config.WithRegion("us-east-1")) + if err != nil { + return fmt.Errorf("unable to create AWS config: %w", err) + } } - s := support.New(sess, &aws.Config{Credentials: creds}) + // Validate that the account id in the target ARN matches the account id in the credentials + if req, err := sts.NewFromConfig(cfg).GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}); err != nil { + return fmt.Errorf("unable to get caller identity: %w", err) + } else if *req.Account != parsedARN.AccountID { + return fmt.Errorf("account id in target ARN does not match the account id in the credentials (target ARN: %s, credentials account id: %s)", parsedARN.AccountID, *req.Account) + } + s := support.NewFromConfig(cfg) // Retrieve checks list checks, err := s.DescribeTrustedAdvisorChecks( + context.TODO(), &support.DescribeTrustedAdvisorChecksInput{ Language: aws.String("en"), }) @@ -161,16 +170,18 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st continue } checkIds = append(checkIds, check.Id) - refreshed, err := s.RefreshTrustedAdvisorCheck(&support.RefreshTrustedAdvisorCheckInput{CheckId: check.Id}) + refreshed, err := s.RefreshTrustedAdvisorCheck(context.Background(), &support.RefreshTrustedAdvisorCheckInput{CheckId: check.Id}) if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == "InvalidParameterValueException" { - logger.Printf("check '%s' is not refreshable\n", *check.Name) - continue - } + // Haven't found a more elegant way to check for an + // InvalidParameterValueException. This error type is not defined in the + // support/types package as it is for other services. + if strings.Contains(err.Error(), "InvalidParameterValueException") { + logger.Printf("check '%s' is not refreshable\n", *check.Name) + continue } return err } + logger.Printf("check '%s' is refreshed with status: '%s'\n", *check.Name, *refreshed.Status.Status) if *refreshed.Status.Status == "enqueued" { enqueued++ @@ -190,16 +201,18 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st break LOOP default: checkStatus, err := s.DescribeTrustedAdvisorCheckRefreshStatuses( + context.Background(), &support.DescribeTrustedAdvisorCheckRefreshStatusesInput{ CheckIds: checkIds, }, ) - if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() != "InvalidParameterValueException" { - return err - } - } + // Haven't found a more elegant way to check for an + // InvalidParameterValueException. This error type is not + // defined in the support/types package as it is for other + // services. + if err != nil && !strings.Contains(err.Error(), "InvalidParameterValueException") { + return fmt.Errorf("unable to check the refresh statuses: %w", err) + } var pending bool for _, cs := range checkStatus.Statuses { @@ -242,34 +255,32 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st var checkSummaries *support.DescribeTrustedAdvisorCheckSummariesOutput checkSummaries, err = s.DescribeTrustedAdvisorCheckSummaries( - &support.DescribeTrustedAdvisorCheckSummariesInput{ + context.Background(), &support.DescribeTrustedAdvisorCheckSummariesInput{ CheckIds: []*string{v.Id}}) if err != nil { return err } for _, summary := range checkSummaries.Summaries { - // Only process summaries that has flagged resources - if summary.HasFlaggedResources == nil { + // Only process summaries that has flagged resources. + if !summary.HasFlaggedResources { continue } - if summary.HasFlaggedResources != nil && *summary.HasFlaggedResources == false { - continue - } - - description := "" + action := "" recommendedActions := []string{} additionalResources := []string{} // Avoid nil pointer dereference when reading *v.Description // description, recommendedActions and additionalResources will be - // considered as empty + // considered empty. if v.Description != nil { iRecommendedAction := strings.Index(*v.Description, tagRecommendedAction) + if iRecommendedAction < 0 { + // No recommended actions + continue + } iAdditionalResources := strings.Index(*v.Description, tagAdditionalResources) - description = string(*v.Description)[:iRecommendedAction] - // Extract recommendedActions if iAdditionalResources >= iRecommendedAction+len(tagRecommendedAction) { recommendedActions = extractLinesFromHTML(string(*v.Description)[iRecommendedAction+len(tagRecommendedAction) : iAdditionalResources]) @@ -287,26 +298,20 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st } var checkResults *support.DescribeTrustedAdvisorCheckResultOutput - checkResults, err = s.DescribeTrustedAdvisorCheckResult(&support.DescribeTrustedAdvisorCheckResultInput{CheckId: v.Id}) + checkResults, err = s.DescribeTrustedAdvisorCheckResult(context.Background(), &support.DescribeTrustedAdvisorCheckResultInput{CheckId: v.Id}) if err != nil { return err } for _, fr := range checkResults.Result.FlaggedResources { - // Unable to retrieve flagged resource information - if fr == nil { - logger.Warnf("result with CheckID: %s does not contain flagged resource information", *checkResults.Result.CheckId) - continue - } - // PTVUL-860 - // Ignore resources that have been marked as supressed/excluded - if *fr.IsSuppressed { + // Ignore resources that have been marked as suppressed/excluded + if fr.IsSuppressed { logger.Debugf("resource with ResourceID: %s have been marked as excluded", *fr.ResourceId) continue } // Get the alias of the account only if we did not get previously. if alias == nil { - res, err := accountAlias(creds) + res, err := accountAlias(cfg) if err != nil { return err } @@ -359,10 +364,13 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st if v.Name != nil { summary = "AWS " + *v.Name } - + resourceID := "" + if fr.ResourceId != nil { + resourceID = *fr.ResourceId + } vuln := report.Vulnerability{ Summary: summary, - Description: description, + Description: action, Score: score, // AWS Trusted Advisor provides already an ID generated by // them, that seems the best option to indicate which is @@ -371,7 +379,7 @@ func scanAccount(opt options, target, assetType string, logger *logrus.Entry, st // therefore we are using a set of the metadata values // provided by their checks in the AffectedResourceString // attribute. - AffectedResource: aws.StringValue(fr.ResourceId), + AffectedResource: resourceID, AffectedResourceString: affectedResourceStr, Labels: []string{"issue", "aws"}, Resources: []report.ResourcesGroup{occurrences}, @@ -397,13 +405,21 @@ type AssumeRoleResponse struct { SessionToken string `json:"session_token"` } -func getCredentials(url string, accountID, role string, logger *logrus.Entry) (*credentials.Credentials, error) { - m := map[string]string{"account_id": accountID} +var errNoCredentials = errors.New("unable to decode credentials") + +func getCredentials(url string, accountID, role string, logger *logrus.Entry) (*aws.Credentials, error) { + m := map[string]any{"account_id": accountID, "duration": 3600} if role != "" { m["role"] = role } jsonBody, err := json.Marshal(m) + if err != nil { + return nil, fmt.Errorf("unable to marshal assume role request body for account %s: %w", accountID, err) + } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody)) + if err != nil { + return nil, fmt.Errorf("unable to create request for the assume role service: %w", err) + } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) @@ -411,33 +427,33 @@ func getCredentials(url string, accountID, role string, logger *logrus.Entry) (* logger.Errorf("cannot do request: %s", err.Error()) return nil, err } - defer resp.Body.Close() + defer resp.Body.Close() // nolint assumeRoleResponse := AssumeRoleResponse{} buf, err := io.ReadAll(resp.Body) if err != nil { - logger.Errorf("Cannot read request body %s", err.Error()) + logger.Errorf("can not read request body %s", err.Error()) return nil, err } err = json.Unmarshal(buf, &assumeRoleResponse) if err != nil { - logger.Errorf("Cannot decode request %s", err.Error()) - logger.Errorf("RequestBody: %s", string(buf)) - return nil, err + logger.Errorf("Cannot decode request: %s", err.Error()) + logger.Errorf("ResponseBody: %s", string(buf)) + return nil, errNoCredentials } - - return credentials.NewStaticCredentials( - assumeRoleResponse.AccessKey, - assumeRoleResponse.SecretAccessKey, - assumeRoleResponse.SessionToken), nil + return &aws.Credentials{ + AccessKeyID: assumeRoleResponse.AccessKey, + SecretAccessKey: assumeRoleResponse.SecretAccessKey, + SessionToken: assumeRoleResponse.SessionToken, + }, nil } // accountAlias gets one of the current aliases of the account that the // credentials passed belong to. -func accountAlias(creds *credentials.Credentials) (string, error) { - svc := iam.New(session.New(&aws.Config{Credentials: creds})) - resp, err := svc.ListAccountAliases(&iam.ListAccountAliasesInput{}) +func accountAlias(cfg aws.Config) (string, error) { + svc := iam.NewFromConfig(cfg) + resp, err := svc.ListAccountAliases(context.Background(), &iam.ListAccountAliasesInput{}) if err != nil { return "", err } @@ -445,9 +461,9 @@ func accountAlias(creds *credentials.Credentials) (string, error) { // No aliases found for the aws account. return "", nil } - a := resp.AccountAliases[0] - if a == nil { - return "", errors.New("unexpected nil getting aliases for aws account") + if len(resp.AccountAliases) < 1 { + return "", errors.New("no result getting aliases for aws account") } - return *a, nil + a := resp.AccountAliases[0] + return a, nil } diff --git a/cmd/vulcan-aws-trusted-advisor/severity.go b/cmd/vulcan-aws-trusted-advisor/severity.go index b45aedb74..18a3ef2a1 100644 --- a/cmd/vulcan-aws-trusted-advisor/severity.go +++ b/cmd/vulcan-aws-trusted-advisor/severity.go @@ -12,99 +12,99 @@ import ( var severityMap = map[string]map[string]float32{ // Security Groups - Unrestricted Access - "1iG5NDGVre": map[string]float32{ + "1iG5NDGVre": { "Red": report.SeverityThresholdMedium, }, // Amazon S3 Bucket Permissions - "Pfx0RwqBli": map[string]float32{ + "Pfx0RwqBli": { "Yellow": report.SeverityThresholdMedium, "Red": report.SeverityThresholdHigh, }, // ELB Listener Security - "a2sEc6ILx": map[string]float32{ + "a2sEc6ILx": { "Yellow": report.SeverityThresholdMedium, "Red": report.SeverityThresholdHigh, }, // ELB Security Groups - "xSqX82fQu": map[string]float32{ + "xSqX82fQu": { "Yellow": report.SeverityThresholdNone, "Red": report.SeverityThresholdNone, }, // IAM Access Key Rotation - "DqdJqYeRm5": map[string]float32{ + "DqdJqYeRm5": { "Green": report.SeverityThresholdNone, "Yellow": report.SeverityThresholdLow, "Red": report.SeverityThresholdMedium, }, // Security Groups - Specific Ports Unrestricted - "HCP4007jGY": map[string]float32{ + "HCP4007jGY": { "Green": report.SeverityThresholdNone, "Yellow": report.SeverityThresholdLow, "Red": report.SeverityThresholdMedium, }, // Amazon EBS Public Snapshots - "ePs02jT06w": map[string]float32{ + "ePs02jT06w": { "Red": report.SeverityThresholdMedium, }, // Amazon RDS Public Snapshots - "rSs93HQwa1": map[string]float32{ + "rSs93HQwa1": { "Red": report.SeverityThresholdHigh, }, // Amazon RDS Security Group Access Risk - "nNauJisYIT": map[string]float32{ + "nNauJisYIT": { "Yellow": report.SeverityThresholdLow, "Red": report.SeverityThresholdMedium, }, // Amazon Route 53 MX and SPF Resource Record Sets - "c9D319e7sG": map[string]float32{ + "c9D319e7sG": { "Yellow": report.SeverityThresholdMedium, }, // AWS CloudTrail Logging - "vjafUGJ9H0": map[string]float32{ + "vjafUGJ9H0": { "Yellow": report.SeverityThresholdLow, "Red": report.SeverityThresholdLow, }, // CloudFront Custom SSL Certificates in the IAM Certificate Store - "N425c450f2": map[string]float32{ + "N425c450f2": { "Yellow": report.SeverityThresholdMedium, "Red": report.SeverityThresholdMedium, }, // CloudFront SSL Certificate on the Origin Server - "N430c450f2": map[string]float32{ + "N430c450f2": { "Yellow": report.SeverityThresholdMedium, "Red": report.SeverityThresholdMedium, }, // Exposed Access Keys - "12Fnkpl8Y5": map[string]float32{ + "12Fnkpl8Y5": { "Red": report.SeverityThresholdCritical, }, // IAM Password Policy - "Yw2K9puPzl": map[string]float32{ + "Yw2K9puPzl": { "Yellow": report.SeverityThresholdNone, "Red": report.SeverityThresholdNone, }, // IAM Use - "zXCkfM1nI3": map[string]float32{ + "zXCkfM1nI3": { "Yellow": report.SeverityThresholdNone, }, // MFA on Root Account - "7DAFEmoDos": map[string]float32{ + "7DAFEmoDos": { "Red": report.SeverityThresholdLow, }, } diff --git a/go.mod b/go.mod index d1a623b30..65503f315 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,11 @@ require ( github.com/adevinta/vulcan-types v1.2.19 github.com/avast/retry-go v3.0.0+incompatible github.com/aws/aws-sdk-go v1.54.0 + github.com/aws/aws-sdk-go-v2 v1.24.1 + github.com/aws/aws-sdk-go-v2/config v1.26.6 + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 + github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 + github.com/aws/aws-sdk-go-v2/service/support v1.19.6 github.com/cenkalti/backoff/v4 v4.3.0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.7.0 @@ -31,6 +36,16 @@ require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/distribution/reference v0.6.0 // indirect diff --git a/go.sum b/go.sum index a42f6642a..a65e3f607 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,36 @@ github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHS github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.54.0 h1:tGCQ6YS2TepzKtbl+ddXnLIoV8XvWdxMKtuMxdrsa4U= github.com/aws/aws-sdk-go v1.54.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= +github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 h1:FKPRDYZOO0Eur19vWUL1B40Op0j89KQj3kARjrszMK8= +github.com/aws/aws-sdk-go-v2/service/iam v1.28.7/go.mod h1:YzMYyQ7S4twfYzLjwP24G1RAxypozVZeNaG1r2jxRms= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/aws-sdk-go-v2/service/support v1.19.6 h1:ZD8OSo915ouOvw9JIjT0pccHwubdLoHmQYjAXIxFA0I= +github.com/aws/aws-sdk-go-v2/service/support v1.19.6/go.mod h1:Mzty8X8zv84IXyvPJ0nI1gZhurnKgrD46J6MRgJsGGk= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=