provider/aws: Add assume_role block to provider

This replaces the previous `role_arn` with a block which looks like
this:

```
provider "aws" {
        // secret key, access key etc

	assume_role {
	        role_arn = "<Role ARN>"
		session_name = "<Session Name>"
		external_id = "<External ID>"
	}
}
```

We also modify the configuration structure and read the values from the
block if present into those values and adjust the call to AssumeRole to
include the SessionName and ExternalID based on the values set in the
configuration block.

Finally we clean up the tests and add in missing error checks, and clean
up the error handling logic in the Auth helper functions.
This commit is contained in:
James Nugent 2016-09-02 10:36:01 -07:00
parent d444d122bf
commit e3ccb51168
5 changed files with 312 additions and 210 deletions

View File

@ -1,6 +1,7 @@
package aws
import (
"errors"
"fmt"
"log"
"os"
@ -18,7 +19,6 @@ import (
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-multierror"
)
func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (string, error) {
@ -77,7 +77,7 @@ func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (
}
if len(outRoles.Roles) < 1 {
return "", fmt.Errorf("Failed getting account ID via 'iam:ListRoles': No roles available")
return "", errors.New("Failed getting account ID via 'iam:ListRoles': No roles available")
}
return parseAccountIdFromArn(*outRoles.Roles[0].Arn)
@ -95,8 +95,6 @@ func parseAccountIdFromArn(arn string) (string, error) {
// environment in the case that they're not explicitly specified
// in the Terraform configuration.
func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
var errs []error
// build a chain provider, lazy-evaulated by aws-sdk
providers := []awsCredentials.Provider{
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
@ -130,7 +128,7 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
providers = append(providers, &ec2rolecreds.EC2RoleProvider{
Client: metadataClient,
})
log.Printf("[INFO] AWS EC2 instance detected via default metadata" +
log.Print("[INFO] AWS EC2 instance detected via default metadata" +
" API endpoint, EC2RoleProvider added to the auth chain")
} else {
if usedEndpoint == "" {
@ -141,40 +139,68 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
}
}
if c.RoleArn != "" {
log.Printf("[INFO] attempting to assume role %s", c.RoleArn)
creds := awsCredentials.NewChainCredentials(providers)
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
errs = append(errs, fmt.Errorf(`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`))
} else {
errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
}
return nil, &multierror.Error{Errors: errs}
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}
stsclient := sts.New(session.New(awsConfig))
providers = []awsCredentials.Provider{&stscreds.AssumeRoleProvider{
Client: stsclient,
RoleARN: c.RoleArn,
}}
// This is the "normal" flow (i.e. not assuming a role)
if c.AssumeRoleARN == "" {
return awsCredentials.NewChainCredentials(providers), nil
}
return awsCredentials.NewChainCredentials(providers), nil
// 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)
creds := awsCredentials.NewChainCredentials(providers)
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, errors.New(`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`)
}
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}
stsclient := sts.New(session.New(awsConfig))
assumeRoleProvider := &stscreds.AssumeRoleProvider{
Client: stsclient,
RoleARN: c.AssumeRoleARN,
}
if c.AssumeRoleSessionName != "" {
assumeRoleProvider.RoleSessionName = c.AssumeRoleSessionName
}
if c.AssumeRoleExternalID != "" {
assumeRoleProvider.ExternalID = aws.String(c.AssumeRoleExternalID)
}
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 assumeRoleCreds, nil
}
func setOptionalEndpoint(cfg *aws.Config) string {

View File

@ -51,7 +51,7 @@ func TestAWSGetAccountId_shouldBeValid_EC2RoleHasPriority(t *testing.T) {
defer awsTs()
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_GetUser_valid, "text/xml"},
},
@ -72,7 +72,7 @@ func TestAWSGetAccountId_shouldBeValid_EC2RoleHasPriority(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromIamUser(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_GetUser_valid, "text/xml"},
},
@ -94,11 +94,11 @@ func TestAWSGetAccountId_shouldBeValid_fromIamUser(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromGetCallerIdentity(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetCallerIdentity&Version=2011-06-15"},
Response: &iamResponse{200, stsResponse_GetCallerIdentity_valid, "text/xml"},
},
@ -119,15 +119,15 @@ func TestAWSGetAccountId_shouldBeValid_fromGetCallerIdentity(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromIamListRoles(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetCallerIdentity&Version=2011-06-15"},
Response: &iamResponse{403, stsResponse_GetCallerIdentity_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_ListRoles_valid, "text/xml"},
},
@ -148,11 +148,11 @@ func TestAWSGetAccountId_shouldBeValid_fromIamListRoles(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_federatedRole(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{400, iamResponse_GetUser_federatedFailure, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_ListRoles_valid, "text/xml"},
},
@ -173,11 +173,11 @@ func TestAWSGetAccountId_shouldBeValid_federatedRole(t *testing.T) {
func TestAWSGetAccountId_shouldError_unauthorizedFromIam(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_ListRoles_unauthorized, "text/xml"},
},
@ -221,17 +221,17 @@ func TestAWSGetCredentials_shouldError(t *testing.T) {
c, err := GetCredentials(&cfg)
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatalf("Expected NoCredentialProviders error")
t.Fatal("Expected NoCredentialProviders error")
}
}
_, err = c.Get()
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatalf("Expected NoCredentialProviders error")
t.Fatal("Expected NoCredentialProviders error")
}
}
if err == nil {
t.Fatalf("Expected an error with empty env, keys, and IAM in AWS Config")
t.Fatal("Expected an error with empty env, keys, and IAM in AWS Config")
}
}
@ -257,16 +257,18 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) {
}
creds, err := GetCredentials(&cfg)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if v.AccessKeyID != c.Key {
t.Fatalf("AccessKeyID mismatch, expected: (%s), got (%s)", c.Key, v.AccessKeyID)
}
@ -295,12 +297,13 @@ func TestAWSGetCredentials_shouldIAM(t *testing.T) {
cfg := Config{}
creds, err := GetCredentials(&cfg)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -346,12 +349,13 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) {
}
creds, err := GetCredentials(&cfg)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -379,6 +383,10 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) {
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err == nil {
t.Fatal("Expected error returned when getting creds w/ invalid EC2 endpoint")
@ -404,6 +412,9 @@ func TestAWSGetCredentials_shouldIgnoreInvalidEndpoint(t *testing.T) {
if err != nil {
t.Fatalf("Getting static credentials w/ invalid EC2 endpoint failed: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
if v.ProviderName != "StaticProvider" {
t.Fatalf("Expected provider name to be %q, %q given", "StaticProvider", v.ProviderName)
@ -426,12 +437,13 @@ func TestAWSGetCredentials_shouldCatchEC2RoleProvider(t *testing.T) {
defer ts()
creds, err := GetCredentials(&Config{})
if creds == nil {
t.Fatalf("Expected an EC2Role creds provider to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected an EC2Role creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Expected no error when getting creds: %s", err)
@ -475,12 +487,13 @@ func TestAWSGetCredentials_shouldBeShared(t *testing.T) {
}
creds, err := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
if creds == nil {
t.Fatalf("Expected a provider chain to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a provider chain to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -505,12 +518,13 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
cfg := Config{}
creds, err := GetCredentials(&cfg)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)

View File

@ -2,6 +2,7 @@ package aws
import (
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
@ -54,7 +55,6 @@ import (
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/terraform"
)
@ -66,9 +66,12 @@ type Config struct {
Profile string
Token string
Region string
RoleArn string
MaxRetries int
AssumeRoleARN string
AssumeRoleExternalID string
AssumeRoleSessionName string
AllowedAccountIds []interface{}
ForbiddenAccountIds []interface{}
@ -136,152 +139,142 @@ type AWSClient struct {
func (c *Config) Client() (interface{}, error) {
// Get the auth and region. This can fail if keys/regions were not
// specified and we're attempting to use the environment.
var errs []error
log.Println("[INFO] Building AWS region structure")
err := c.ValidateRegion()
if err != nil {
errs = append(errs, err)
return nil, err
}
var client AWSClient
if len(errs) == 0 {
// store AWS region in client struct, for region specific operations such as
// bucket storage in S3
client.region = c.Region
// store AWS region in client struct, for region specific operations such as
// bucket storage in S3
client.region = c.Region
log.Println("[INFO] Building AWS auth structure")
creds, err := GetCredentials(c)
if err != nil {
return nil, &multierror.Error{Errors: errs}
}
// Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider.
log.Println("[INFO] Building AWS auth structure")
creds, err := GetCredentials(c)
if err != nil {
return nil, err
}
// Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, errors.New(`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`))
} else {
errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
}
return nil, &multierror.Error{Errors: errs}
providing credentials for the AWS Provider`)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}
if logging.IsDebugOrHigher() {
awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
awsConfig.Logger = awsLogger{}
}
if c.Insecure {
transport := awsConfig.HTTPClient.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
if logging.IsDebugOrHigher() {
awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
awsConfig.Logger = awsLogger{}
}
// Set up base session
sess, err := session.NewSession(awsConfig)
if err != nil {
return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
}
sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
if c.Insecure {
transport := awsConfig.HTTPClient.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
// Some services exist only in us-east-1, e.g. because they manage
// resources that can span across multiple regions, or because
// signature format v4 requires region to be us-east-1 for global
// endpoints:
// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
// Set up base session
sess, err := session.NewSession(awsConfig)
// Some services have user-configurable endpoints
awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
// These two services need to be set up early so we can check on AccountID
client.iamconn = iam.New(awsIamSess)
client.stsconn = sts.New(sess)
if !c.SkipCredsValidation {
err = c.ValidateCredentials(client.stsconn)
if err != nil {
return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
return nil, err
}
sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
// Some services exist only in us-east-1, e.g. because they manage
// resources that can span across multiple regions, or because
// signature format v4 requires region to be us-east-1 for global
// endpoints:
// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
// Some services have user-configurable endpoints
awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
// These two services need to be set up early so we can check on AccountID
client.iamconn = iam.New(awsIamSess)
client.stsconn = sts.New(sess)
if !c.SkipCredsValidation {
err = c.ValidateCredentials(client.stsconn)
if err != nil {
errs = append(errs, err)
return nil, &multierror.Error{Errors: errs}
}
}
if !c.SkipRequestingAccountId {
accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName)
if err == nil {
client.accountid = accountId
}
}
authErr := c.ValidateAccountId(client.accountid)
if authErr != nil {
errs = append(errs, authErr)
}
client.apigateway = apigateway.New(sess)
client.appautoscalingconn = applicationautoscaling.New(sess)
client.autoscalingconn = autoscaling.New(sess)
client.cfconn = cloudformation.New(sess)
client.cloudfrontconn = cloudfront.New(sess)
client.cloudtrailconn = cloudtrail.New(sess)
client.cloudwatchconn = cloudwatch.New(sess)
client.cloudwatcheventsconn = cloudwatchevents.New(sess)
client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
client.codecommitconn = codecommit.New(usEast1Sess)
client.codedeployconn = codedeploy.New(sess)
client.dsconn = directoryservice.New(sess)
client.dynamodbconn = dynamodb.New(dynamoSess)
client.ec2conn = ec2.New(awsEc2Sess)
client.ecrconn = ecr.New(sess)
client.ecsconn = ecs.New(sess)
client.efsconn = efs.New(sess)
client.elasticacheconn = elasticache.New(sess)
client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
client.elastictranscoderconn = elastictranscoder.New(sess)
client.elbconn = elb.New(awsElbSess)
client.elbv2conn = elbv2.New(awsElbSess)
client.emrconn = emr.New(sess)
client.esconn = elasticsearch.New(sess)
client.firehoseconn = firehose.New(sess)
client.glacierconn = glacier.New(sess)
client.kinesisconn = kinesis.New(kinesisSess)
client.kmsconn = kms.New(sess)
client.lambdaconn = lambda.New(sess)
client.opsworksconn = opsworks.New(usEast1Sess)
client.r53conn = route53.New(usEast1Sess)
client.rdsconn = rds.New(sess)
client.redshiftconn = redshift.New(sess)
client.simpledbconn = simpledb.New(sess)
client.s3conn = s3.New(awsS3Sess)
client.sesConn = ses.New(sess)
client.snsconn = sns.New(sess)
client.sqsconn = sqs.New(sess)
client.ssmconn = ssm.New(sess)
}
if len(errs) > 0 {
return nil, &multierror.Error{Errors: errs}
if !c.SkipRequestingAccountId {
accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName)
if err == nil {
client.accountid = accountId
}
}
authErr := c.ValidateAccountId(client.accountid)
if authErr != nil {
return nil, err
}
client.apigateway = apigateway.New(sess)
client.appautoscalingconn = applicationautoscaling.New(sess)
client.autoscalingconn = autoscaling.New(sess)
client.cfconn = cloudformation.New(sess)
client.cloudfrontconn = cloudfront.New(sess)
client.cloudtrailconn = cloudtrail.New(sess)
client.cloudwatchconn = cloudwatch.New(sess)
client.cloudwatcheventsconn = cloudwatchevents.New(sess)
client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
client.codecommitconn = codecommit.New(usEast1Sess)
client.codedeployconn = codedeploy.New(sess)
client.dsconn = directoryservice.New(sess)
client.dynamodbconn = dynamodb.New(dynamoSess)
client.ec2conn = ec2.New(awsEc2Sess)
client.ecrconn = ecr.New(sess)
client.ecsconn = ecs.New(sess)
client.efsconn = efs.New(sess)
client.elasticacheconn = elasticache.New(sess)
client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
client.elastictranscoderconn = elastictranscoder.New(sess)
client.elbconn = elb.New(awsElbSess)
client.elbv2conn = elbv2.New(awsElbSess)
client.emrconn = emr.New(sess)
client.esconn = elasticsearch.New(sess)
client.firehoseconn = firehose.New(sess)
client.glacierconn = glacier.New(sess)
client.kinesisconn = kinesis.New(kinesisSess)
client.kmsconn = kms.New(sess)
client.lambdaconn = lambda.New(sess)
client.opsworksconn = opsworks.New(usEast1Sess)
client.r53conn = route53.New(usEast1Sess)
client.rdsconn = rds.New(sess)
client.redshiftconn = redshift.New(sess)
client.simpledbconn = simpledb.New(sess)
client.s3conn = s3.New(awsS3Sess)
client.sesConn = ses.New(sess)
client.snsconn = sns.New(sess)
client.sqsconn = sqs.New(sess)
client.ssmconn = ssm.New(sess)
return &client, nil
}
@ -325,7 +318,7 @@ func (c *Config) ValidateAccountId(accountId string) error {
return nil
}
log.Printf("[INFO] Validating account ID")
log.Println("[INFO] Validating account ID")
if c.ForbiddenAccountIds != nil {
for _, id := range c.ForbiddenAccountIds {

View File

@ -3,6 +3,7 @@ package aws
import (
"bytes"
"fmt"
"log"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/mutexkv"
@ -39,6 +40,8 @@ func Provider() terraform.ResourceProvider {
Description: descriptions["profile"],
},
"assume_role": assumeRoleSchema(),
"shared_credentials_file": {
Type: schema.TypeString,
Optional: true,
@ -64,13 +67,6 @@ func Provider() terraform.ResourceProvider {
InputDefault: "us-east-1",
},
"role_arn": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["role_arn"],
},
"max_retries": {
Type: schema.TypeInt,
Optional: true,
@ -360,8 +356,6 @@ func init() {
"profile": "The profile for API operations. If not set, the default profile\n" +
"created with `aws configure` will be used.",
"role_arn": "The role to be assumed using the supplied access_key and secret_key",
"shared_credentials_file": "The path to the shared credentials file. If not set\n" +
"this defaults to ~/.aws/credentials.",
@ -402,6 +396,14 @@ 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 ommitted," +
" no session name is passed to the AssumeRole call.",
"assume_role_external_id": "The external ID to use when assuming the role. If ommitted," +
" no external ID is passed to the AssumeRole call.",
}
}
@ -413,7 +415,6 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
CredsFilename: d.Get("shared_credentials_file").(string),
Token: d.Get("token").(string),
Region: d.Get("region").(string),
RoleArn: d.Get("role_arn").(string),
MaxRetries: d.Get("max_retries").(int),
DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string),
KinesisEndpoint: d.Get("kinesis_endpoint").(string),
@ -424,6 +425,18 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
S3ForcePathStyle: d.Get("s3_force_path_style").(bool),
}
assumeRoleList := d.Get("assume_role").(*schema.Set).List()
if len(assumeRoleList) == 1 {
assumeRole := assumeRoleList[0].(map[string]interface{})
config.AssumeRoleARN = assumeRole["role_arn"].(string)
config.AssumeRoleSessionName = assumeRole["session_name"].(string)
config.AssumeRoleExternalID = assumeRole["external_id"].(string)
log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)",
config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID)
} else {
log.Printf("[INFO] No assume_role block read from configuration")
}
endpointsSet := d.Get("endpoints").(*schema.Set)
for _, endpointsSetI := range endpointsSet.List() {
@ -448,6 +461,45 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
// This is a global MutexKV for use within this plugin.
var awsMutexKV = mutexkv.NewMutexKV()
func assumeRoleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"role_arn": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_role_arn"],
},
"session_name": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_session_name"],
},
"external_id": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_external_id"],
},
},
},
Set: assumeRoleToHash,
}
}
func assumeRoleToHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
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)))
return hashcode.String(buf.String())
}
func endpointsSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,

View File

@ -113,14 +113,18 @@ and defaults to `http://169.254.169.254:80/latest`.
###Assume role
If provided with a role arn, terraform will attempt to assume this role
If provided with a role ARN, Terraform will attempt to assume this role
using the supplied credentials.
Usage:
```
provider "aws" {
role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
assume_role {
role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
session_name = "SESSION_NAME"
external_id = "EXTERNAL_ID"
}
}
```
@ -143,6 +147,9 @@ The following arguments are supported in the `provider` block:
* `profile` - (Optional) This is the AWS profile name as set in the shared credentials
file.
* `assume_role` - (Optional) An `assume_role` block (documented below).`Only one
`assume_role` block may be in the configuration.
* `shared_credentials_file` = (Optional) This is the path to the shared credentials file.
If this is not set and a profile is specified, ~/.aws/credentials will be used.
@ -200,7 +207,17 @@ The following arguments are supported in the `provider` block:
S3 client will use virtual hosted bucket addressing when possible
(http://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.
Nested `endpoints` block supports the followings:
The nested `assume_role` block supports the following:
* `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.
Nested `endpoints` block supports the following:
* `iam` - (Optional) Use this to override the default endpoint
URL constructed from the `region`. It's typically used to connect to