Skip to content

Commit

Permalink
provider: Authentication updates for Terraform AWS Provider v3.0.0
Browse files Browse the repository at this point in the history
Reference: #5018
Reference: #6913
Reference: #7333
Reference: #9236
Reference: #9869
Reference: #9898
Reference: #9962
Reference: #9986
Reference: #10507
Reference: #11429
Reference: #12236
Reference: #12727
Reference: #12815
Reference: #13057

Changes:

```
NOTES

* provider: 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)
* provider: 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

ENHANCEMENTS

* provider: Always enable shared configuration file support (no longer require `AWS_SDK_LOAD_CONFIG` environment variable)
* provider: Add `assume_role` configuration block `duration_seconds`, `policy_arns`, `tags`, and `transitive_tag_keys` arguments

BUG FIXES

* provider: Ensure configured STS endpoint is used during `AssumeRole` API calls
* provider: Prefer AWS shared configuration over EC2 metadata credentials by default
* provider: Prefer CodeBuild, ECS, EKS credentials over EC2 metadata credentials by default
```

Output from acceptance testing:

```
--- PASS: TestAccAWSProvider_Region_AwsCommercial (3.89s)
--- PASS: TestAccAWSProvider_Region_AwsGovCloudUs (3.90s)
--- PASS: TestAccAWSProvider_Region_AwsChina (3.99s)
--- PASS: TestAccAWSProvider_IgnoreTags_Keys_None (4.22s)
--- PASS: TestAccAWSProvider_IgnoreTags_KeyPrefixes_None (4.29s)
--- PASS: TestAccAWSProvider_IgnoreTags_KeyPrefixes_One (4.37s)
--- PASS: TestAccAWSProvider_IgnoreTags_KeyPrefixes_Multiple (4.38s)
--- PASS: TestAccAWSProvider_IgnoreTags_Keys_One (4.39s)
--- PASS: TestAccAWSProvider_IgnoreTags_EmptyConfigurationBlock (4.40s)
--- PASS: TestAccAWSProvider_IgnoreTags_Keys_Multiple (4.40s)
--- PASS: TestAccAWSProvider_Endpoints_Deprecated (4.42s)
--- PASS: TestAccAWSProvider_Endpoints (4.53s)
--- PASS: TestAccAWSProvider_AssumeRole_Empty (8.32s)
```
  • Loading branch information
bflad committed Jul 7, 2020
1 parent 104630b commit 717f065
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 116 deletions.
57 changes: 33 additions & 24 deletions aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"},
Expand All @@ -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 == "" {
Expand Down
138 changes: 91 additions & 47 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -1017,18 +1016,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{
Expand Down Expand Up @@ -1183,6 +1170,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{})),
Expand All @@ -1196,32 +1184,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)
Expand Down Expand Up @@ -1258,28 +1282,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},
},
},
},
Expand Down
69 changes: 24 additions & 45 deletions website/docs/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:

Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 717f065

Please sign in to comment.