From 59e8dca6ae341de8f7bec88affcb1f7ba0758817 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Thu, 13 Oct 2016 09:33:33 +0200 Subject: [PATCH] AWS Support Assume Role with MFA This adds initial support for using MFA when assuming a role by adding `mfa_serial` and `token_code` to the `assumeRole` block in an AWS provider. --- builtin/providers/aws/auth_helpers.go | 9 +++++++-- builtin/providers/aws/config.go | 2 ++ builtin/providers/aws/provider.go | 18 ++++++++++++++++++ .../docs/providers/aws/index.html.markdown | 10 +++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/auth_helpers.go b/builtin/providers/aws/auth_helpers.go index fab4928b6dbf..cb2dec0df6a8 100644 --- a/builtin/providers/aws/auth_helpers.go +++ b/builtin/providers/aws/auth_helpers.go @@ -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() @@ -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"+ diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 101a450a85a2..f823a2014f51 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -75,6 +75,8 @@ type Config struct { AssumeRoleARN string AssumeRoleExternalID string AssumeRoleSessionName string + AssumeRoleMFASerial string + AssumeRoleTokenCode string AllowedAccountIds []interface{} ForbiddenAccountIds []interface{} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index a9ecf320fde9..2247bead387c 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -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.", } } @@ -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 { @@ -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, @@ -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("%d-", m["token_code"].(string))) return hashcode.String(buf.String()) } diff --git a/website/source/docs/providers/aws/index.html.markdown b/website/source/docs/providers/aws/index.html.markdown index 5b1754f83067..c64ca13473f4 100644 --- a/website/source/docs/providers/aws/index.html.markdown +++ b/website/source/docs/providers/aws/index.html.markdown @@ -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}" } } ``` @@ -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: