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:
parent
d444d122bf
commit
e3ccb51168
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue