Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: AWS MFA Support when Using Assume Role #9349

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions builtin/providers/aws/auth_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {

// Otherwise we need to construct and 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)",
c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID)
log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q, MFA Serial: %q)",
c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID, c.AssumeRoleMFASerial)

creds := awsCredentials.NewChainCredentials(providers)
cp, err := creds.Get()
Expand Down Expand Up @@ -182,11 +182,16 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
if c.AssumeRoleExternalID != "" {
assumeRoleProvider.ExternalID = aws.String(c.AssumeRoleExternalID)
}
if c.AssumeRoleMFASerial != "" {
assumeRoleProvider.SerialNumber = aws.String(c.AssumeRoleMFASerial)
assumeRoleProvider.TokenCode = aws.String(c.AssumeRoleTokenCode)
}

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"+
Expand Down
2 changes: 2 additions & 0 deletions builtin/providers/aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type Config struct {
AssumeRoleARN string
AssumeRoleExternalID string
AssumeRoleSessionName string
AssumeRoleMFASerial string
AssumeRoleTokenCode string

AllowedAccountIds []interface{}
ForbiddenAccountIds []interface{}
Expand Down
18 changes: 18 additions & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ func init() {

"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_mfa_serial": "The serial of a MFA device.",
"assume_role_token_code": "The MFA OTP code.",
}
}

Expand Down Expand Up @@ -442,6 +445,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config.AssumeRoleARN = assumeRole["role_arn"].(string)
config.AssumeRoleSessionName = assumeRole["session_name"].(string)
config.AssumeRoleExternalID = assumeRole["external_id"].(string)
config.AssumeRoleMFASerial = assumeRole["mfa_serial"].(string)
config.AssumeRoleTokenCode = assumeRole["token_code"].(string)
log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)",
config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID)
} else {
Expand Down Expand Up @@ -496,6 +501,17 @@ func assumeRoleSchema() *schema.Schema {
Optional: true,
Description: descriptions["assume_role_external_id"],
},

"mfa_serial": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_mfa_serial"],
},
"token_code": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_token_code"],
},
},
},
Set: assumeRoleToHash,
Expand All @@ -508,6 +524,8 @@ func assumeRoleToHash(v interface{}) int {
buf.WriteString(fmt.Sprintf("%s-", m["role_arn"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["session_name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["external_id"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["mfa_serial"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["token_code"].(string)))
return hashcode.String(buf.String())
}

Expand Down
22 changes: 15 additions & 7 deletions website/source/docs/providers/aws/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ resource "aws_instance" "web" {
}
```

## Authentication
## Authentication

The AWS provider offers flexible means of providing credentials for
authentication. The following methods are supported, in this order, and
Expand All @@ -46,7 +46,7 @@ explained below:
Static credentials can be provided by adding an `access_key` and `secret_key` in-line in the
aws provider block:

Usage:
Usage:

```
provider "aws" {
Expand All @@ -58,7 +58,7 @@ provider "aws" {

###Environment variables

You can provide your credentials via `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`,
You can provide your credentials via `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`,
environment variables, representing your AWS Access Key and AWS Secret Key, respectively.
`AWS_DEFAULT_REGION` and `AWS_SESSION_TOKEN` are also used, if applicable:

Expand All @@ -69,7 +69,7 @@ provider "aws" {}
Usage:

```
$ export AWS_ACCESS_KEY_ID="anaccesskey"
$ export AWS_ACCESS_KEY_ID="anaccesskey"
$ export AWS_SECRET_ACCESS_KEY="asecretkey"
$ export AWS_DEFAULT_REGION="us-west-2"
$ terraform plan
Expand All @@ -78,15 +78,15 @@ $ terraform plan
###Shared Credentials file

You can use an AWS credentials file to specify your credentials. The default
location is `$HOME/.aws/credentials` on Linux and OSX, or `"%USERPROFILE%\.aws\credentials"`
location is `$HOME/.aws/credentials` on Linux and OSX, 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 `shared_credentials_file`,
or in the environment with the `AWS_SHARED_CREDENTIALS_FILE` variable. This
method also supports a `profile` configuration and matching `AWS_PROFILE`
environment variable:

Usage:
Usage:

```
provider "aws" {
Expand Down Expand Up @@ -124,6 +124,8 @@ provider "aws" {
role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
session_name = "SESSION_NAME"
external_id = "EXTERNAL_ID"
mfa_serial = "arn:aws:iam::ACCOUNT_ID:mfa/USERNAME"
token_code = "${var.token_code}"
}
}
```
Expand Down Expand Up @@ -215,7 +217,13 @@ The nested `assume_role` block supports the following:
AssumeRole call.

* `external_id` - (Optional) The external ID to use when making the
AssumeRole call.
AssumeRole call.

* `mfa_serial` - (Optional) The MFA serial to use when making the
AssumeRole call.

* `token_code` - (Optional) The MFA OTP code to use when making the
AssumeRole call. Required if `mfa_serial` is given.

Nested `endpoints` block supports the following:

Expand Down