diff --git a/aws/config.go b/aws/config.go index 6fffc972c1b..fc649c4bedb 100644 --- a/aws/config.go +++ b/aws/config.go @@ -165,10 +165,14 @@ type Config struct { Region string MaxRetries int - AssumeRoleARN string - AssumeRoleExternalID string - AssumeRoleSessionName string - AssumeRolePolicy string + AssumeRoleARN string + AssumeRoleDurationSeconds int + AssumeRoleExternalID string + AssumeRolePolicy string + AssumeRolePolicyARNs []string + AssumeRoleSessionName string + AssumeRoleTags map[string]string + AssumeRoleTransitiveTagKeys []string AllowedAccountIds []string ForbiddenAccountIds []string @@ -365,26 +369,31 @@ func (c *Config) Client() (interface{}, error) { } } - log.Println("[INFO] Building AWS auth structure") awsbaseConfig := &awsbase.Config{ - AccessKey: c.AccessKey, - AssumeRoleARN: c.AssumeRoleARN, - AssumeRoleExternalID: c.AssumeRoleExternalID, - AssumeRolePolicy: c.AssumeRolePolicy, - AssumeRoleSessionName: c.AssumeRoleSessionName, - CredsFilename: c.CredsFilename, - DebugLogging: logging.IsDebugOrHigher(), - IamEndpoint: c.Endpoints["iam"], - Insecure: c.Insecure, - MaxRetries: c.MaxRetries, - Profile: c.Profile, - Region: c.Region, - SecretKey: c.SecretKey, - SkipCredsValidation: c.SkipCredsValidation, - SkipMetadataApiCheck: c.SkipMetadataApiCheck, - SkipRequestingAccountId: c.SkipRequestingAccountId, - StsEndpoint: c.Endpoints["sts"], - Token: c.Token, + AccessKey: c.AccessKey, + AssumeRoleARN: c.AssumeRoleARN, + AssumeRoleDurationSeconds: c.AssumeRoleDurationSeconds, + AssumeRoleExternalID: c.AssumeRoleExternalID, + AssumeRolePolicy: c.AssumeRolePolicy, + AssumeRolePolicyARNs: c.AssumeRolePolicyARNs, + AssumeRoleSessionName: c.AssumeRoleSessionName, + AssumeRoleTags: c.AssumeRoleTags, + AssumeRoleTransitiveTagKeys: c.AssumeRoleTransitiveTagKeys, + CallerDocumentationURL: "https://registry.terraform.io/providers/hashicorp/aws", + CallerName: "Terraform AWS Provider", + CredsFilename: c.CredsFilename, + DebugLogging: logging.IsDebugOrHigher(), + IamEndpoint: c.Endpoints["iam"], + Insecure: c.Insecure, + MaxRetries: c.MaxRetries, + Profile: c.Profile, + Region: c.Region, + SecretKey: c.SecretKey, + SkipCredsValidation: c.SkipCredsValidation, + SkipMetadataApiCheck: c.SkipMetadataApiCheck, + SkipRequestingAccountId: c.SkipRequestingAccountId, + StsEndpoint: c.Endpoints["sts"], + Token: c.Token, UserAgentProducts: []*awsbase.UserAgentProduct{ {Name: "APN", Version: "1.0"}, {Name: "HashiCorp", Version: "1.0"}, @@ -395,7 +404,7 @@ func (c *Config) Client() (interface{}, error) { sess, accountID, partition, err := awsbase.GetSessionWithAccountIDAndPartition(awsbaseConfig) if err != nil { - return nil, err + return nil, fmt.Errorf("error configuring Terraform AWS Provider: %w", err) } if accountID == "" { diff --git a/aws/provider.go b/aws/provider.go index 0940f772bb7..7f8b23a3c82 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/mutexkv" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" - homedir "github.com/mitchellh/go-homedir" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) @@ -1019,18 +1018,6 @@ func init() { "i.e., http://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\n" + "use virtual hosted bucket addressing when possible\n" + "(http://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", - - "assume_role_role_arn": "The ARN of an IAM role to assume prior to making API calls.", - - "assume_role_session_name": "The session name to use when assuming the role. If omitted," + - " no session name is passed to the AssumeRole call.", - - "assume_role_external_id": "The external ID to use when assuming the role. If omitted," + - " no external ID is passed to the AssumeRole call.", - - "assume_role_policy": "The permissions applied when assuming a role. You cannot use," + - " this policy to grant further permissions that are in excess to those of the, " + - " role that is being assumed.", } endpointServiceNames = []string{ @@ -1185,6 +1172,7 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa Profile: d.Get("profile").(string), Token: d.Get("token").(string), Region: d.Get("region").(string), + CredsFilename: d.Get("shared_credentials_file").(string), Endpoints: make(map[string]string), MaxRetries: d.Get("max_retries").(int), IgnoreTagsConfig: expandProviderIgnoreTags(d.Get("ignore_tags").([]interface{})), @@ -1198,32 +1186,68 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa terraformVersion: terraformVersion, } - // Set CredsFilename, expanding home directory - credsPath, err := homedir.Expand(d.Get("shared_credentials_file").(string)) - if err != nil { - return nil, err - } - config.CredsFilename = credsPath - - assumeRoleList := d.Get("assume_role").([]interface{}) - if len(assumeRoleList) == 1 { - if assumeRoleList[0] != nil { - assumeRole := assumeRoleList[0].(map[string]interface{}) - config.AssumeRoleARN = assumeRole["role_arn"].(string) - config.AssumeRoleSessionName = assumeRole["session_name"].(string) - config.AssumeRoleExternalID = assumeRole["external_id"].(string) - - if v := assumeRole["policy"].(string); v != "" { - config.AssumeRolePolicy = v + if l, ok := d.Get("assume_role").([]interface{}); ok && len(l) > 0 && l[0] != nil { + m := l[0].(map[string]interface{}) + + if v, ok := m["duration_seconds"].(int); ok && v != 0 { + config.AssumeRoleDurationSeconds = v + } + + if v, ok := m["external_id"].(string); ok && v != "" { + config.AssumeRoleExternalID = v + } + + if v, ok := m["policy"].(string); ok && v != "" { + config.AssumeRolePolicy = v + } + + if policyARNSet, ok := m["policy_arns"].(*schema.Set); ok && policyARNSet.Len() > 0 { + for _, policyARNRaw := range policyARNSet.List() { + policyARN, ok := policyARNRaw.(string) + + if !ok { + continue + } + + config.AssumeRolePolicyARNs = append(config.AssumeRolePolicyARNs, policyARN) + } + } + + if v, ok := m["role_arn"].(string); ok && v != "" { + config.AssumeRoleARN = v + } + + if v, ok := m["session_name"].(string); ok && v != "" { + config.AssumeRoleSessionName = v + } + + if tagMapRaw, ok := m["tags"].(map[string]interface{}); ok && len(tagMapRaw) > 0 { + config.AssumeRoleTags = make(map[string]string) + + for k, vRaw := range tagMapRaw { + v, ok := vRaw.(string) + + if !ok { + continue + } + + config.AssumeRoleTags[k] = v } + } + + if transitiveTagKeySet, ok := m["transitive_tag_keys"].(*schema.Set); ok && transitiveTagKeySet.Len() > 0 { + for _, transitiveTagKeyRaw := range transitiveTagKeySet.List() { + transitiveTagKey, ok := transitiveTagKeyRaw.(string) + + if !ok { + continue + } - log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q, Policy: %q)", - config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID, config.AssumeRolePolicy) - } else { - log.Printf("[INFO] Empty assume_role block read from configuration") + config.AssumeRoleTransitiveTagKeys = append(config.AssumeRoleTransitiveTagKeys, transitiveTagKey) + } } - } else { - log.Printf("[INFO] No assume_role block read from configuration") + + log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)", config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID) } endpointsSet := d.Get("endpoints").(*schema.Set) @@ -1260,28 +1284,48 @@ func assumeRoleSchema() *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "role_arn": { + "duration_seconds": { + Type: schema.TypeInt, + Optional: true, + Description: "Seconds to restrict the assume role session duration.", + }, + "external_id": { Type: schema.TypeString, Optional: true, - Description: descriptions["assume_role_role_arn"], + Description: "Unique identifier that might be required for assuming a role in another account.", }, - - "session_name": { + "policy": { Type: schema.TypeString, Optional: true, - Description: descriptions["assume_role_session_name"], + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", }, - - "external_id": { + "policy_arns": { + Type: schema.TypeSet, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "role_arn": { Type: schema.TypeString, Optional: true, - Description: descriptions["assume_role_external_id"], + Description: "Amazon Resource Name of an IAM Role to assume prior to making API calls.", }, - - "policy": { + "session_name": { Type: schema.TypeString, Optional: true, - Description: descriptions["assume_role_policy"], + Description: "Identifier for the assumed role session.", + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Description: "Assume role session tags.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "transitive_tag_keys": { + Type: schema.TypeSet, + Optional: true, + Description: "Assume role session tag keys to pass to any subsequent sessions.", + Elem: &schema.Schema{Type: schema.TypeString}, }, }, }, diff --git a/go.mod b/go.mod index dace921f82e..cce30ee2666 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bflad/tfproviderlint v0.14.0 github.com/client9/misspell v0.3.4 github.com/golangci/golangci-lint v1.26.0 - github.com/hashicorp/aws-sdk-go-base v0.4.0 + github.com/hashicorp/aws-sdk-go-base v0.5.0 github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-version v1.2.1 diff --git a/go.sum b/go.sum index 5fc60359053..f24bcaceb51 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.32.12 h1:l/djCeLI4ggBFWLlYUGTqkHraoLnVMubNlLXPdEtoYc= github.com/aws/aws-sdk-go v1.32.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -217,8 +218,8 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.m github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/aws-sdk-go-base v0.4.0 h1:zH9hNUdsS+2G0zJaU85ul8D59BGnZBaKM+KMNPAHGwk= -github.com/hashicorp/aws-sdk-go-base v0.4.0/go.mod h1:eRhlz3c4nhqxFZJAahJEFL7gh6Jyj5rQmQc7F9eHFyQ= +github.com/hashicorp/aws-sdk-go-base v0.5.0 h1:fk7ID0v3PWL/KNL8FvkBPu8Sm93EPUCCmtZCiTXLySE= +github.com/hashicorp/aws-sdk-go-base v0.5.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/.golangci.yml b/vendor/github.com/hashicorp/aws-sdk-go-base/.golangci.yml index 59ef674ca41..687b0c780dc 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/.golangci.yml +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/.golangci.yml @@ -6,14 +6,21 @@ linters: disable-all: true enable: - deadcode + - dogsled - errcheck + - goconst - gofmt + - gomnd - gosimple - ineffassign + - interfacer - misspell + - scopelint - staticcheck - structcheck - unconvert + - unparam - unused + - typecheck - varcheck - vet diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/.travis.yml b/vendor/github.com/hashicorp/aws-sdk-go-base/.travis.yml deleted file mode 100644 index f1aaa658b82..00000000000 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -dist: xenial -language: go -go: -- "1.13.x" - -matrix: - fast_finish: true - allow_failures: - - go: tip - -install: -- make tools - -script: -- make lint -- go test -timeout=30s -parallel=4 -v ./... - -branches: - only: - - master diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/CHANGELOG.md b/vendor/github.com/hashicorp/aws-sdk-go-base/CHANGELOG.md index 558e7e39322..fd1b523fa7b 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/CHANGELOG.md +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/CHANGELOG.md @@ -1,3 +1,25 @@ +# v0.5.0 (June 4, 2020) + +BREAKING CHANGES + +* Credential ordering has changed from static, environment, shared credentials, EC2 metadata, default AWS Go SDK (shared configuration, web identity, ECS, EC2 Metadata) to static, environment, shared credentials, default AWS Go SDK (shared configuration, web identity, ECS, EC2 Metadata). #20 +* The `AWS_METADATA_TIMEOUT` environment variable no longer has any effect as we now depend on the default AWS Go SDK EC2 Metadata client timeout of one second with two retries. #20 / #44 + +ENHANCEMENTS + +* Always enable AWS shared configuration file support (no longer require `AWS_SDK_LOAD_CONFIG` environment variable) #38 +* Automatically expand `~` prefix for home directories in shared credentials filename handling #40 +* Support assume role duration, policy ARNs, tags, and transitive tag keys via configuration #39 +* Add `CannotAssumeRoleError` and `NoValidCredentialSourcesError` error types with helpers #42 + +BUG FIXES + +* Properly use custom STS endpoint during AssumeRole API calls triggered by Terraform AWS Provider and S3 Backend configurations #32 +* Properly use custom EC2 metadata endpoint during API calls triggered by fallback credentials lookup #32 +* Prefer shared configuration handling over EC2 metadata #20 +* Prefer ECS credentials over EC2 metadata #20 +* Remove hardcoded AWS Provider messaging in error messages #31 / #42 + # v0.4.0 (October 3, 2019) BUG FIXES diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/awsauth.go b/vendor/github.com/hashicorp/aws-sdk-go-base/awsauth.go index 5311620386a..3363b1d5be0 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/awsauth.go +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/awsauth.go @@ -13,29 +13,21 @@ import ( awsCredentials "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" - "github.com/aws/aws-sdk-go/aws/defaults" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/sts" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-multierror" + homedir "github.com/mitchellh/go-homedir" ) const ( - // errMsgNoValidCredentialSources error getting credentials - errMsgNoValidCredentialSources = `No valid credential sources found for AWS Provider. - Please see https://terraform.io/docs/providers/aws/index.html for more information on - providing credentials for the AWS Provider` + // Default amount of time for EC2/ECS metadata client operations. + // Keep this value low to prevent long delays in non-EC2/ECS environments. + DefaultMetadataClientTimeout = 100 * time.Millisecond ) -var ( - // ErrNoValidCredentialSources indicates that no credentials source could be found - ErrNoValidCredentialSources = errNoValidCredentialSources() -) - -func errNoValidCredentialSources() error { return errors.New(errMsgNoValidCredentialSources) } - // GetAccountIDAndPartition gets the account ID and associated partition. func GetAccountIDAndPartition(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (string, string, error) { var accountID, partition string @@ -75,7 +67,7 @@ func GetAccountIDAndPartitionFromEC2Metadata() (string, string, error) { setOptionalEndpoint(cfg) sess, err := session.NewSession(cfg) if err != nil { - return "", "", fmt.Errorf("error creating EC2 Metadata session: %s", err) + return "", "", fmt.Errorf("error creating EC2 Metadata session: %w", err) } metadataClient := ec2metadata.New(sess) @@ -84,7 +76,7 @@ func GetAccountIDAndPartitionFromEC2Metadata() (string, string, error) { // We can end up here if there's an issue with the instance metadata service // or if we're getting credentials from AdRoll's Hologram (in which case IAMInfo will // error out). - err = fmt.Errorf("failed getting account information via EC2 Metadata IAM information: %s", err) + err = fmt.Errorf("failed getting account information via EC2 Metadata IAM information: %w", err) log.Printf("[DEBUG] %s", err) return "", "", err } @@ -107,7 +99,7 @@ func GetAccountIDAndPartitionFromIAMGetUser(iamconn *iam.IAM) (string, string, e return "", "", nil } } - err = fmt.Errorf("failed getting account information via iam:GetUser: %s", err) + err = fmt.Errorf("failed getting account information via iam:GetUser: %w", err) log.Printf("[DEBUG] %s", err) return "", "", err } @@ -130,7 +122,7 @@ func GetAccountIDAndPartitionFromIAMListRoles(iamconn *iam.IAM) (string, string, MaxItems: aws.Int64(int64(1)), }) if err != nil { - err = fmt.Errorf("failed getting account information via iam:ListRoles: %s", err) + err = fmt.Errorf("failed getting account information via iam:ListRoles: %w", err) log.Printf("[DEBUG] %s", err) return "", "", err } @@ -151,7 +143,7 @@ func GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsconn *sts.STS) (string, output, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{}) if err != nil { - return "", "", fmt.Errorf("error calling sts:GetCallerIdentity: %s", err) + return "", "", fmt.Errorf("error calling sts:GetCallerIdentity: %w", err) } if output == nil || output.Arn == nil { @@ -177,37 +169,30 @@ func parseAccountIDAndPartitionFromARN(inputARN string) (string, string, error) func GetCredentialsFromSession(c *Config) (*awsCredentials.Credentials, error) { log.Printf("[INFO] Attempting to use session-derived credentials") - var sess *session.Session - var err error - if c.Profile == "" { - sess, err = session.NewSession() - if err != nil { - return nil, ErrNoValidCredentialSources - } - } else { - options := &session.Options{ - Config: aws.Config{ - HTTPClient: cleanhttp.DefaultClient(), - MaxRetries: aws.Int(0), - Region: aws.String(c.Region), - }, - } - options.Profile = c.Profile - options.SharedConfigState = session.SharedConfigEnable + // Avoid setting HTTPClient here as it will prevent the ec2metadata + // client from automatically lowering the timeout to 1 second. + options := &session.Options{ + Config: aws.Config{ + EndpointResolver: c.EndpointResolver(), + MaxRetries: aws.Int(0), + Region: aws.String(c.Region), + }, + Profile: c.Profile, + SharedConfigState: session.SharedConfigEnable, + } - sess, err = session.NewSessionWithOptions(*options) - if err != nil { - if IsAWSErr(err, "NoCredentialProviders", "") { - return nil, ErrNoValidCredentialSources - } - return nil, fmt.Errorf("Error creating AWS session: %s", err) + sess, err := session.NewSessionWithOptions(*options) + if err != nil { + if IsAWSErr(err, "NoCredentialProviders", "") { + return nil, c.NewNoValidCredentialSourcesError(err) } + return nil, fmt.Errorf("Error creating AWS session: %w", err) } creds := sess.Config.Credentials cp, err := sess.Config.Credentials.Get() if err != nil { - return nil, ErrNoValidCredentialSources + return nil, c.NewNoValidCredentialSourcesError(err) } log.Printf("[INFO] Successfully derived credentials from session") @@ -216,10 +201,16 @@ func GetCredentialsFromSession(c *Config) (*awsCredentials.Credentials, error) { } // GetCredentials gets credentials from the environment, shared credentials, -// or the session (which may include a credential process). GetCredentials also -// validates the credentials and the ability to assume a role or will return an -// error if unsuccessful. +// the session (which may include a credential process), or ECS/EC2 metadata endpoints. +// GetCredentials also validates the credentials and the ability to assume a role +// or will return an error if unsuccessful. func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { + sharedCredentialsFilename, err := homedir.Expand(c.CredsFilename) + + if err != nil { + return nil, fmt.Errorf("error expanding shared credentials filename: %w", err) + } + // build a chain provider, lazy-evaluated by aws-sdk providers := []awsCredentials.Provider{ &awsCredentials.StaticProvider{Value: awsCredentials.Value{ @@ -229,70 +220,11 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { }}, &awsCredentials.EnvProvider{}, &awsCredentials.SharedCredentialsProvider{ - Filename: c.CredsFilename, + Filename: sharedCredentialsFilename, Profile: c.Profile, }, } - // Build isolated HTTP client to avoid issues with globally-shared settings - client := cleanhttp.DefaultClient() - - // Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments - client.Timeout = 100 * time.Millisecond - - const userTimeoutEnvVar = "AWS_METADATA_TIMEOUT" - userTimeout := os.Getenv(userTimeoutEnvVar) - if userTimeout != "" { - newTimeout, err := time.ParseDuration(userTimeout) - if err == nil { - if newTimeout.Nanoseconds() > 0 { - client.Timeout = newTimeout - } else { - log.Printf("[WARN] Non-positive value of %s (%s) is meaningless, ignoring", userTimeoutEnvVar, newTimeout.String()) - } - } else { - log.Printf("[WARN] Error converting %s to time.Duration: %s", userTimeoutEnvVar, err) - } - } - - log.Printf("[INFO] Setting AWS metadata API timeout to %s", client.Timeout.String()) - cfg := &aws.Config{ - HTTPClient: client, - } - usedEndpoint := setOptionalEndpoint(cfg) - - // Add the default AWS provider for ECS Task Roles if the relevant env variable is set - if uri := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); len(uri) > 0 { - providers = append(providers, defaults.RemoteCredProvider(*cfg, defaults.Handlers())) - log.Print("[INFO] ECS container credentials detected, RemoteCredProvider added to auth chain") - } - - if !c.SkipMetadataApiCheck { - // Real AWS should reply to a simple metadata request. - // We check it actually does to ensure something else didn't just - // happen to be listening on the same IP:Port - ec2Session, err := session.NewSession(cfg) - - if err != nil { - return nil, fmt.Errorf("error creating EC2 Metadata session: %s", err) - } - - metadataClient := ec2metadata.New(ec2Session) - if metadataClient.Available() { - providers = append(providers, &ec2rolecreds.EC2RoleProvider{ - Client: metadataClient, - }) - log.Print("[INFO] AWS EC2 instance detected via default metadata" + - " API endpoint, EC2RoleProvider added to the auth chain") - } else { - if usedEndpoint == "" { - usedEndpoint = "default location" - } - log.Printf("[INFO] Ignoring AWS metadata API endpoint at %s "+ - "as it doesn't return any instance-id", usedEndpoint) - } - } - // Validate the credentials before returning them creds := awsCredentials.NewChainCredentials(providers) cp, err := creds.Get() @@ -303,7 +235,7 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { return nil, err } } else { - return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err) + return nil, fmt.Errorf("Error loading credentials for AWS Provider: %w", err) } } else { log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName) @@ -316,20 +248,21 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { // Otherwise we need to construct an STS client with the main credentials, and verify // that we can assume the defined role. - log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q, Policy: %q)", - c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID, c.AssumeRolePolicy) + log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q)", + c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID) awsConfig := &aws.Config{ - Credentials: creds, - Region: aws.String(c.Region), - MaxRetries: aws.Int(c.MaxRetries), - HTTPClient: cleanhttp.DefaultClient(), + Credentials: creds, + EndpointResolver: c.EndpointResolver(), + Region: aws.String(c.Region), + MaxRetries: aws.Int(c.MaxRetries), + HTTPClient: cleanhttp.DefaultClient(), } assumeRoleSession, err := session.NewSession(awsConfig) if err != nil { - return nil, fmt.Errorf("error creating assume role session: %s", err) + return nil, fmt.Errorf("error creating assume role session: %w", err) } stsclient := sts.New(assumeRoleSession) @@ -337,31 +270,60 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { Client: stsclient, RoleARN: c.AssumeRoleARN, } - if c.AssumeRoleSessionName != "" { - assumeRoleProvider.RoleSessionName = c.AssumeRoleSessionName + + if c.AssumeRoleDurationSeconds > 0 { + assumeRoleProvider.Duration = time.Duration(c.AssumeRoleDurationSeconds) * time.Second } + if c.AssumeRoleExternalID != "" { assumeRoleProvider.ExternalID = aws.String(c.AssumeRoleExternalID) } + if c.AssumeRolePolicy != "" { assumeRoleProvider.Policy = aws.String(c.AssumeRolePolicy) } + if len(c.AssumeRolePolicyARNs) > 0 { + var policyDescriptorTypes []*sts.PolicyDescriptorType + + for _, policyARN := range c.AssumeRolePolicyARNs { + policyDescriptorType := &sts.PolicyDescriptorType{ + Arn: aws.String(policyARN), + } + policyDescriptorTypes = append(policyDescriptorTypes, policyDescriptorType) + } + + assumeRoleProvider.PolicyArns = policyDescriptorTypes + } + + if c.AssumeRoleSessionName != "" { + assumeRoleProvider.RoleSessionName = c.AssumeRoleSessionName + } + + if len(c.AssumeRoleTags) > 0 { + var tags []*sts.Tag + + for k, v := range c.AssumeRoleTags { + tag := &sts.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + tags = append(tags, tag) + } + + assumeRoleProvider.Tags = tags + } + + if len(c.AssumeRoleTransitiveTagKeys) > 0 { + assumeRoleProvider.TransitiveTagKeys = aws.StringSlice(c.AssumeRoleTransitiveTagKeys) + } + providers = []awsCredentials.Provider{assumeRoleProvider} assumeRoleCreds := awsCredentials.NewChainCredentials(providers) _, err = assumeRoleCreds.Get() if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { - return nil, fmt.Errorf("The role %q cannot be assumed.\n\n"+ - " There are a number of possible causes of this - the most common are:\n"+ - " * The credentials used in order to assume the role are invalid\n"+ - " * The credentials do not have appropriate permission to assume the role\n"+ - " * The role ARN is not valid", - c.AssumeRoleARN) - } - - return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err) + return nil, c.NewCannotAssumeRoleError(err) } return assumeRoleCreds, nil diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/awserr.go b/vendor/github.com/hashicorp/aws-sdk-go-base/awserr.go index 28295483518..ce657679ae5 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/awserr.go +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/awserr.go @@ -1,6 +1,7 @@ package awsbase import ( + "errors" "strings" "github.com/aws/aws-sdk-go/aws/awserr" @@ -11,17 +12,13 @@ import ( // * Error.Code() matches code // * Error.Message() contains message func IsAWSErr(err error, code string, message string) bool { - awsErr, ok := err.(awserr.Error) + var awsErr awserr.Error - if !ok { - return false + if errors.As(err, &awsErr) { + return awsErr.Code() == code && strings.Contains(awsErr.Message(), message) } - if awsErr.Code() != code { - return false - } - - return strings.Contains(awsErr.Message(), message) + return false } // IsAWSErrExtended returns true if the error matches all these conditions: @@ -33,5 +30,15 @@ func IsAWSErrExtended(err error, code string, message string, origErrMessage str if !IsAWSErr(err, code, message) { return false } - return strings.Contains(err.(awserr.Error).OrigErr().Error(), origErrMessage) + + if origErrMessage == "" { + return true + } + + // Ensure OrigErr() is non-nil, to prevent panics + if origErr := err.(awserr.Error).OrigErr(); origErr != nil { + return strings.Contains(origErr.Error(), origErrMessage) + } + + return false } diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/config.go b/vendor/github.com/hashicorp/aws-sdk-go-base/config.go index fb57e44e41d..c5f9daafbdf 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/config.go +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/config.go @@ -1,25 +1,31 @@ package awsbase type Config struct { - AccessKey string - AssumeRoleARN string - AssumeRoleExternalID string - AssumeRolePolicy string - AssumeRoleSessionName string - CredsFilename string - DebugLogging bool - IamEndpoint string - Insecure bool - MaxRetries int - Profile string - Region string - SecretKey string - SkipCredsValidation bool - SkipMetadataApiCheck bool - SkipRequestingAccountId bool - StsEndpoint string - Token string - UserAgentProducts []*UserAgentProduct + AccessKey string + AssumeRoleARN string + AssumeRoleDurationSeconds int + AssumeRoleExternalID string + AssumeRolePolicy string + AssumeRolePolicyARNs []string + AssumeRoleSessionName string + AssumeRoleTags map[string]string + AssumeRoleTransitiveTagKeys []string + CallerDocumentationURL string + CallerName string + CredsFilename string + DebugLogging bool + IamEndpoint string + Insecure bool + MaxRetries int + Profile string + Region string + SecretKey string + SkipCredsValidation bool + SkipMetadataApiCheck bool + SkipRequestingAccountId bool + StsEndpoint string + Token string + UserAgentProducts []*UserAgentProduct } type UserAgentProduct struct { diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/endpoints.go b/vendor/github.com/hashicorp/aws-sdk-go-base/endpoints.go new file mode 100644 index 00000000000..c3f64d1cd37 --- /dev/null +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/endpoints.go @@ -0,0 +1,46 @@ +package awsbase + +import ( + "log" + "os" + + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/sts" +) + +func (c *Config) EndpointResolver() endpoints.Resolver { + resolver := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) { + // Ensure we pass all existing information (e.g. SigningRegion) and + // only override the URL, otherwise a MissingRegion error can occur + // when aws.Config.Region is not defined. + resolvedEndpoint, err := endpoints.DefaultResolver().EndpointFor(service, region, optFns...) + + if err != nil { + return resolvedEndpoint, err + } + + switch service { + case ec2metadata.ServiceName: + if endpoint := os.Getenv("AWS_METADATA_URL"); endpoint != "" { + log.Printf("[INFO] Setting custom EC2 metadata endpoint: %s", endpoint) + resolvedEndpoint.URL = endpoint + } + case iam.ServiceName: + if endpoint := c.IamEndpoint; endpoint != "" { + log.Printf("[INFO] Setting custom IAM endpoint: %s", endpoint) + resolvedEndpoint.URL = endpoint + } + case sts.ServiceName: + if endpoint := c.StsEndpoint; endpoint != "" { + log.Printf("[INFO] Setting custom STS endpoint: %s", endpoint) + resolvedEndpoint.URL = endpoint + } + } + + return resolvedEndpoint, nil + } + + return endpoints.ResolverFunc(resolver) +} diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/errors.go b/vendor/github.com/hashicorp/aws-sdk-go-base/errors.go new file mode 100644 index 00000000000..2d5f1677cc5 --- /dev/null +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/errors.go @@ -0,0 +1,76 @@ +package awsbase + +import ( + "errors" + "fmt" +) + +// CannotAssumeRoleError occurs when AssumeRole cannot complete. +type CannotAssumeRoleError struct { + Config *Config + Err error +} + +func (e CannotAssumeRoleError) Error() string { + if e.Config == nil { + return fmt.Sprintf("cannot assume role: %s", e.Err) + } + + return fmt.Sprintf(`IAM Role (%s) cannot be assumed. + +There are a number of possible causes of this - the most common are: + * The credentials used in order to assume the role are invalid + * The credentials do not have appropriate permission to assume the role + * The role ARN is not valid + +Error: %s +`, e.Config.AssumeRoleARN, e.Err) +} + +func (e CannotAssumeRoleError) Unwrap() error { + return e.Err +} + +// IsCannotAssumeRoleError returns true if the error contains the CannotAssumeRoleError type. +func IsCannotAssumeRoleError(err error) bool { + var e CannotAssumeRoleError + return errors.As(err, &e) +} + +func (c *Config) NewCannotAssumeRoleError(err error) CannotAssumeRoleError { + return CannotAssumeRoleError{Config: c, Err: err} +} + +// NoValidCredentialSourcesError occurs when all credential lookup methods have been exhausted without results. +type NoValidCredentialSourcesError struct { + Config *Config + Err error +} + +func (e NoValidCredentialSourcesError) Error() string { + if e.Config == nil { + return fmt.Sprintf("no valid credential sources found: %s", e.Err) + } + + return fmt.Sprintf(`no valid credential sources for %s found. + +Please see %s +for more information about providing credentials. + +Error: %s +`, e.Config.CallerName, e.Config.CallerDocumentationURL, e.Err) +} + +func (e NoValidCredentialSourcesError) Unwrap() error { + return e.Err +} + +// IsNoValidCredentialSourcesError returns true if the error contains the NoValidCredentialSourcesError type. +func IsNoValidCredentialSourcesError(err error) bool { + var e NoValidCredentialSourcesError + return errors.As(err, &e) +} + +func (c *Config) NewNoValidCredentialSourcesError(err error) NoValidCredentialSourcesError { + return NoValidCredentialSourcesError{Config: c, Err: err} +} diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/go.mod b/vendor/github.com/hashicorp/aws-sdk-go-base/go.mod index 1af445d2b17..d4cba362aec 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/go.mod +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/go.mod @@ -1,12 +1,10 @@ module github.com/hashicorp/aws-sdk-go-base require ( - github.com/aws/aws-sdk-go v1.25.3 + github.com/aws/aws-sdk-go v1.31.9 github.com/hashicorp/go-cleanhttp v0.5.0 github.com/hashicorp/go-multierror v1.0.0 - github.com/stretchr/testify v1.3.0 // indirect - golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect - golang.org/x/text v0.3.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 ) go 1.13 diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/go.sum b/vendor/github.com/hashicorp/aws-sdk-go-base/go.sum index f06c2e9105d..eabfab4b54f 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/go.sum +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/go.sum @@ -1,23 +1,31 @@ -github.com/aws/aws-sdk-go v1.16.36 h1:POeH34ZME++pr7GBGh+ZO6Y5kOwSMQpqp5BGUgooJ6k= -github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ= -github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.9 h1:n+b34ydVfgC30j0Qm69yaapmjejQPW2BoDBX7Uy/tLI= +github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/hashicorp/aws-sdk-go-base/mock.go b/vendor/github.com/hashicorp/aws-sdk-go-base/mock.go index e3c80266020..06ecd082bc4 100644 --- a/vendor/github.com/hashicorp/aws-sdk-go-base/mock.go +++ b/vendor/github.com/hashicorp/aws-sdk-go-base/mock.go @@ -2,6 +2,7 @@ package awsbase import ( "bytes" + "encoding/json" "fmt" "log" "net/http" @@ -19,7 +20,7 @@ func MockAwsApiServer(svcName string, endpoints []*MockEndpoint) *httptest.Serve ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { buf := new(bytes.Buffer) if _, err := buf.ReadFrom(r.Body); err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error reading from HTTP Request Body: %s", err) return } @@ -43,7 +44,7 @@ func MockAwsApiServer(svcName string, endpoints []*MockEndpoint) *httptest.Serve } } - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) })) return ts @@ -73,20 +74,43 @@ func awsMetadataApiMock(responses []*MetadataResponse) func() { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.Header().Add("Server", "MockEC2") - log.Printf("[DEBUG] Mocker server received request to %q", r.RequestURI) + log.Printf("[DEBUG] Mock EC2 metadata server received request: %s", r.RequestURI) for _, e := range responses { if r.RequestURI == e.Uri { fmt.Fprintln(w, e.Body) return } } - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) })) os.Setenv("AWS_METADATA_URL", ts.URL+"/latest") return ts.Close } +// ecsCredentialsApiMock establishes a httptest server to mock out the ECS credentials API. +func ecsCredentialsApiMock() func() { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Add("Server", "MockECS") + log.Printf("[DEBUG] Mock ECS credentials server received request: %s", r.RequestURI) + if r.RequestURI == "/creds" { + _ = json.NewEncoder(w).Encode(map[string]string{ + "AccessKeyId": "EcsCredentialsAccessKey", + "Expiration": time.Now().UTC().Format(time.RFC3339), + "RoleArn": "arn:aws:iam::000000000000:role/EcsCredentials", + "SecretAccessKey": "EcsCredentialsSecretKey", + "Token": "EcsCredentialsSessionToken", + }) + return + } + w.WriteHeader(http.StatusBadRequest) + })) + + os.Setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", ts.URL+"/creds") + return ts.Close +} + // MockEndpoint represents a basic request and response that can be used for creating simple httptest server routes. type MockEndpoint struct { Request *MockRequest @@ -119,13 +143,17 @@ var ec2metadata_instanceIdEndpoint = &MetadataResponse{ } var ec2metadata_securityCredentialsEndpoints = []*MetadataResponse{ + { + Uri: "/latest/api/token", + Body: "Ec2MetadataApiToken", + }, { Uri: "/latest/meta-data/iam/security-credentials/", Body: "test_role", }, { Uri: "/latest/meta-data/iam/security-credentials/test_role", - Body: "{\"Code\":\"Success\",\"LastUpdated\":\"2015-12-11T17:17:25Z\",\"Type\":\"AWS-HMAC\",\"AccessKeyId\":\"somekey\",\"SecretAccessKey\":\"somesecret\",\"Token\":\"sometoken\"}", + Body: "{\"Code\":\"Success\",\"LastUpdated\":\"2015-12-11T17:17:25Z\",\"Type\":\"AWS-HMAC\",\"AccessKeyId\":\"Ec2MetadataAccessKey\",\"SecretAccessKey\":\"Ec2MetadataSecretKey\",\"Token\":\"Ec2MetadataSessionToken\"}", }, } @@ -165,6 +193,54 @@ const iamResponse_GetUser_unauthorized = ` + + + arn:aws:sts::555555555555:assumed-role/role/AssumeRoleSessionName + ARO123EXAMPLE123:AssumeRoleSessionName + + + AssumeRoleAccessKey + AssumeRoleSecretKey + AssumeRoleSessionToken + %s + + + + 01234567-89ab-cdef-0123-456789abcdef + +`, time.Now().UTC().Format(time.RFC3339)) + +const stsResponse_AssumeRole_InvalidClientTokenId = ` + + Sender + InvalidClientTokenId + The security token included in the request is invalid. + +4d0cf5ec-892a-4d3f-84e4-30e9987d9bdd +` + +var stsResponse_AssumeRoleWithWebIdentity_valid = fmt.Sprintf(` + + amzn1.account.AF6RHO7KZU5XRVQJGXK6HB56KR2A + client.6666666666666666666.6666@apps.example.com + + arn:aws:sts::666666666666:assumed-role/FederatedWebIdentityRole/AssumeRoleWithWebIdentitySessionName + ARO123EXAMPLE123:AssumeRoleWithWebIdentitySessionName + + + AssumeRoleWithWebIdentitySessionToken + AssumeRoleWithWebIdentitySecretKey + %s + AssumeRoleWithWebIdentityAccessKey + + www.amazon.com + + + 01234567-89ab-cdef-0123-456789abcdef + +`, time.Now().UTC().Format(time.RFC3339)) + const stsResponse_GetCallerIdentity_valid = ` arn:aws:iam::222222222222:user/Alice @@ -228,3 +304,5 @@ const iamResponse_ListRoles_unauthorized = ` 0 { @@ -82,9 +99,7 @@ func GetSession(c *Config) (*session.Session, error) { // NOTE: This logic can be fooled by other request errors raising the retry count // before any networking error occurs sess.Handlers.Retry.PushBack(func(r *request.Request) { - // We currently depend on the DefaultRetryer exponential backoff here. - // ~10 retries gives a fair backoff of a few seconds. - if r.RetryCount < 9 { + if r.RetryCount < MaxNetworkRetryCount { return } // RequestError: send request failed @@ -102,9 +117,8 @@ func GetSession(c *Config) (*session.Session, error) { }) if !c.SkipCredsValidation { - stsClient := sts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.StsEndpoint)})) - if _, _, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsClient); err != nil { - return nil, fmt.Errorf("error using credentials to get account ID: %s", err) + if _, _, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(sts.New(sess)); err != nil { + return nil, fmt.Errorf("error validating provider credentials: %w", err) } } @@ -125,14 +139,14 @@ func GetSessionWithAccountIDAndPartition(c *Config) (*session.Session, string, s return sess, accountID, partition, nil } - iamClient := iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})) - stsClient := sts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.StsEndpoint)})) + iamClient := iam.New(sess) + stsClient := sts.New(sess) if !c.SkipCredsValidation { accountID, partition, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsClient) if err != nil { - return nil, "", "", fmt.Errorf("error validating provider credentials: %s", err) + return nil, "", "", fmt.Errorf("error validating provider credentials: %w", err) } return sess, accountID, partition, nil @@ -154,7 +168,7 @@ func GetSessionWithAccountIDAndPartition(c *Config) (*session.Session, string, s return nil, "", "", fmt.Errorf( "AWS account ID not previously found and failed retrieving via all available methods. "+ "See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for workaround and implications. "+ - "Errors: %s", err) + "Errors: %w", err) } var partition string diff --git a/vendor/modules.txt b/vendor/modules.txt index 9a43ead5f57..85d26fc57ce 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -482,7 +482,7 @@ github.com/googleapis/gax-go/v2 github.com/gookit/color # github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 github.com/gostaticanalysis/analysisutil -# github.com/hashicorp/aws-sdk-go-base v0.4.0 +# github.com/hashicorp/aws-sdk-go-base v0.5.0 github.com/hashicorp/aws-sdk-go-base # github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/errwrap diff --git a/website/docs/guides/version-3-upgrade.html.md b/website/docs/guides/version-3-upgrade.html.md index c0a4bf9b1c2..2858de3baaf 100644 --- a/website/docs/guides/version-3-upgrade.html.md +++ b/website/docs/guides/version-3-upgrade.html.md @@ -8,9 +8,7 @@ description: |- # Terraform AWS Provider Version 3 Upgrade Guide -~> **NOTE:** This upgrade guide is a work in progress and will not be completed until the release of version 3.0.0 of the provider in the coming months. Many of the topics discussed, except for the actual provider upgrade, can be performed using the most recent 2.X version of the provider. - -Version 3.0.0 of the AWS provider for Terraform is a major release and includes some changes that you will need to consider when upgrading. This guide is intended to help with that process and focuses only on changes from version 1.X to version 3.0.0. +Version 3.0.0 of the AWS provider for Terraform is a major release and includes some changes that you will need to consider when upgrading. This guide is intended to help with that process and focuses only on changes from version 2.X to version 3.0.0. See the [Version 2 Upgrade Guide](/docs/providers/aws/guides/version-2-upgrade.html) for information about upgrading from 1.X to version 2.0.0. Most of the changes outlined in this guide have been previously marked as deprecated in the Terraform plan/apply output throughout previous provider releases. These changes, such as deprecation notices, can always be found in the [Terraform AWS Provider CHANGELOG](https://github.com/terraform-providers/terraform-provider-aws/blob/master/CHANGELOG.md). @@ -19,16 +17,16 @@ Upgrade topics: - [Provider Version Configuration](#provider-version-configuration) +- [Provider Authentication Updates](#provider-authentication-updates) - [Data Source: aws_availability_zones](#data-source-aws_availability_zones) - [Data Source: aws_lambda_invocation](#data-source-aws_lambda_invocation) - [Resource: aws_emr_cluster](#resource-aws_emr_cluster) +- [Resource: aws_lb_listener_rule](#resource-aws_lb_listener_rule) ## Provider Version Configuration -!> **WARNING:** This topic is placeholder documentation until version 3.0.0 is released in the coming months. - -> Before upgrading to version 3.0.0, it is recommended to upgrade to the most recent 2.X version of the provider and ensure that your environment successfully runs [`terraform plan`](https://www.terraform.io/docs/commands/plan.html) without unexpected changes or deprecation notices. It is recommended to use [version constraints when configuring Terraform providers](https://www.terraform.io/docs/configuration/providers.html#provider-versions). If you are following that recommendation, update the version constraints in your Terraform configuration and run [`terraform init`](https://www.terraform.io/docs/commands/init.html) to download the new version. @@ -39,7 +37,7 @@ For example, given this previous configuration: provider "aws" { # ... other configuration ... - version = "~> 2.8" + version = "~> 2.70" } ``` @@ -53,6 +51,35 @@ provider "aws" { } ``` +## Provider Authentication Updates + +### Authentication Ordering + +Previously, the provider preferred credentials in the following order: + +- Static credentials (those defined in the Terraform configuration) +- Environment variables (e.g. `AWS_ACCESS_KEY_ID` or `AWS_PROFILE`) +- Shared credentials file (e.g. `~/.aws/credentials`) +- EC2 Instance Metadata Service +- Default AWS Go SDK handling (shared configuration, CodeBuild/ECS/EKS) + +The provider now prefers the following credential ordering: + +- Static credentials (those defined in the Terraform configuration) +- Environment variables (e.g. `AWS_ACCESS_KEY_ID` or `AWS_PROFILE`) +- Shared credentials and/or configuration file (e.g. `~/.aws/credentials` and `~/.aws/config`) +- Default AWS Go SDK handling (shared configuration, CodeBuild/ECS/EKS, EC2 Instance Metadata Service) + +This means workarounds of disabling the EC2 Instance Metadata Service handling to enable CodeBuild/ECS/EKS credentials or to enable other credential methods such as `credential_process` in the AWS shared configuration are no longer necessary. + +### Shared Configuration File Automatically Enabled + +The `AWS_SDK_LOAD_CONFIG` environment variable is no longer necessary for the provider to automatically load the AWS shared configuration file (e.g. `~/.aws/config`). + +### Removal of AWS_METADATA_TIMEOUT Environment Variable Usage + +The provider now relies on the default AWS Go SDK timeouts for interacting with the EC2 Instance Metadata Service. + ## Data Source: aws_availability_zones ### blacklisted_names Attribute Removal diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 39933c47d01..cd60ffc9402 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -18,7 +18,7 @@ Use the navigation to the left to read about the available resources. ```hcl # Configure the AWS Provider provider "aws" { - version = "~> 2.0" + version = "~> 3.0" region = "us-east-1" } @@ -36,10 +36,11 @@ explained below: - Static credentials - Environment variables -- Shared credentials file -- EC2 Role +- Shared credentials/configuration file +- CodeBuild, ECS, and EKS Roles +- EC2 Instance Metadata Service (IMDS and IMDSv2) -### Static credentials +### Static Credentials !> **Warning:** Hard-coding credentials into any Terraform configuration is not recommended, and risks secret leakage should this file ever be committed to a @@ -58,7 +59,7 @@ provider "aws" { } ``` -### Environment variables +### Environment Variables You can provide your credentials via the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, environment variables, representing your AWS @@ -81,17 +82,9 @@ $ export AWS_DEFAULT_REGION="us-west-2" $ terraform plan ``` -### Shared Credentials file +### Shared Credentials File -You can use an AWS credentials file to specify your credentials. The -default location is `$HOME/.aws/credentials` on Linux and OS X, or -`"%USERPROFILE%\.aws\credentials"` for Windows users. If we fail to -detect credentials inline, or in the environment, Terraform will check -this location. You can optionally specify a different location in the -configuration by providing the `shared_credentials_file` attribute, or -in the environment with the `AWS_SHARED_CREDENTIALS_FILE` variable. This -method also supports a `profile` configuration and matching -`AWS_PROFILE` environment variable: +You can use an [AWS credentials or configuration file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) to specify your credentials. The default location is `$HOME/.aws/credentials` on Linux and macOS, or `"%USERPROFILE%\.aws\credentials"` on Windows. You can optionally specify a different location in the Terraform configuration by providing the `shared_credentials_file` argument or using the `AWS_SHARED_CREDENTIALS_FILE` environment variable. This method also supports a `profile` configuration and matching `AWS_PROFILE` environment variable: Usage: @@ -103,17 +96,15 @@ provider "aws" { } ``` -If specifying the profile through the `AWS_PROFILE` environment variable, you -may also need to set `AWS_SDK_LOAD_CONFIG` to a truthy value (e.g. `AWS_SDK_LOAD_CONFIG=1`) for advanced AWS client configurations, such as profiles that use the `source_profile` or `role_arn` configurations. +Please note that the [AWS Go SDK](https://aws.amazon.com/sdk-for-go/), the underlying authentication handler used by the Terraform AWS Provider, does not support all AWS CLI features, such as Single Sign On (SSO) configuration or credentials. -### ECS and CodeBuild Task Roles +### CodeBuild, ECS, and EKS Roles -If you're running Terraform on ECS or CodeBuild and you have configured an [IAM Task Role](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html), -Terraform will use the container's Task Role. Terraform looks for the presence of the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` -environment variable that AWS injects when a Task Role is configured. If you have not defined a Task Role for your container -or CodeBuild job, Terraform will continue to use the [EC2 Role](#ec2-role). +If you're running Terraform on CodeBuild or ECS and have configured an [IAM Task Role](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html), Terraform will use the container's Task Role. This support is based on the underlying `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` and `AWS_CONTAINER_CREDENTIALS_FULL_URI` environment variables being automatically set by those services or manually for advanced usage. -### EC2 Role +If you're running Terraform on EKS and have configured [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html), Terraform will use the pod's role. This support is based on the underlying `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE` environment variables being automatically set by Kubernetes or manually for advanced usage. + +### EC2 Instance Metadata Service If you're running Terraform from an EC2 instance with IAM Instance Profile using IAM Role, Terraform will just ask @@ -127,15 +118,7 @@ which reduces the chance of leakage. You can provide the custom metadata API endpoint via the `AWS_METADATA_URL` variable which expects the endpoint URL, including the version, and defaults to `http://169.254.169.254:80/latest`. -The default deadline for the EC2 metadata API endpoint is 100 milliseconds, -which can be overidden by setting the `AWS_METADATA_TIMEOUT` environment -variable. The variable expects a positive golang Time.Duration string, which is -a sequence of decimal numbers and a unit suffix; valid suffixes are `ns` -(nanoseconds), `us` (microseconds), `ms` (milliseconds), `s` (seconds), `m` -(minutes), and `h` (hours). Examples of valid inputs: `100ms`, `250ms`, `1s`, -`2.5s`, `2.5m`, `1m30s`. - -### Assume role +### Assume Role If provided with a role ARN, Terraform will attempt to assume this role using the supplied credentials. @@ -343,20 +326,16 @@ for more information about connecting to alternate AWS endpoints or AWS compatib ### assume_role Configuration Block -The `assume_role` configuration block supports the following arguments: - -* `role_arn` - (Required) The ARN of the role to assume. - -* `session_name` - (Optional) The session name to use when making the - AssumeRole call. - -* `external_id` - (Optional) The external ID to use when making the - AssumeRole call. +The `assume_role` configuration block supports the following optional arguments: -* `policy` - (Optional) A more restrictive policy to apply to the temporary credentials. -This gives you a way to further restrict the permissions for the resulting temporary -security credentials. You cannot use the passed policy to grant permissions that are -in excess of those allowed by the access policy of the role that is being assumed. +* `duration_seconds` - (Optional) Number of seconds to restrict the assume role session duration. +* `external_id` - (Optional) External identifier to use when assuming the role. +* `policy` - (Optional) IAM Policy JSON describing further restricting permissions for the IAM Role being assumed. +* `policy_arns` - (Optional) Set of Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed. +* `role_arn` - (Optional) Amazon Resource Name (ARN) of the IAM Role to assume. +* `session_name` - (Optional) Session name to use when assuming the role. +* `tags` - (Optional) Map of assume role session tags. +* `transitive_tag_keys` - (Optional) Set of assume role session tag keys to pass to any subsequent sessions. ### ignore_tags Configuration Block