Merge pull request #8638 from hashicorp/f-aws-assume-role
provider/aws: Add support for AssumeRole prior to operations
This commit is contained in:
commit
94ca84e772
|
@ -1,6 +1,7 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
|
@ -75,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)
|
||||
|
@ -92,7 +94,7 @@ func parseAccountIdFromArn(arn string) (string, error) {
|
|||
// This function is responsible for reading credentials from the
|
||||
// environment in the case that they're not explicitly specified
|
||||
// in the Terraform configuration.
|
||||
func GetCredentials(c *Config) *awsCredentials.Credentials {
|
||||
func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
|
||||
// build a chain provider, lazy-evaulated by aws-sdk
|
||||
providers := []awsCredentials.Provider{
|
||||
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
|
||||
|
@ -126,7 +128,7 @@ func GetCredentials(c *Config) *awsCredentials.Credentials {
|
|||
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 == "" {
|
||||
|
@ -137,7 +139,68 @@ func GetCredentials(c *Config) *awsCredentials.Credentials {
|
|||
}
|
||||
}
|
||||
|
||||
return awsCredentials.NewChainCredentials(providers)
|
||||
// This is the "normal" flow (i.e. not assuming a role)
|
||||
if c.AssumeRoleARN == "" {
|
||||
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"},
|
||||
},
|
||||
|
@ -218,15 +218,20 @@ func TestAWSGetCredentials_shouldError(t *testing.T) {
|
|||
defer resetEnv()
|
||||
cfg := Config{}
|
||||
|
||||
c := GetCredentials(&cfg)
|
||||
_, err := c.Get()
|
||||
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.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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,14 +256,19 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) {
|
|||
Token: c.Token,
|
||||
}
|
||||
|
||||
creds := GetCredentials(&cfg)
|
||||
if creds == nil {
|
||||
t.Fatalf("Expected a static creds provider to be returned")
|
||||
creds, err := GetCredentials(&cfg)
|
||||
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)
|
||||
}
|
||||
|
@ -286,9 +296,12 @@ func TestAWSGetCredentials_shouldIAM(t *testing.T) {
|
|||
// An empty config, no key supplied
|
||||
cfg := Config{}
|
||||
|
||||
creds := GetCredentials(&cfg)
|
||||
creds, err := GetCredentials(&cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Error gettings creds: %s", err)
|
||||
}
|
||||
if creds == nil {
|
||||
t.Fatalf("Expected a static creds provider to be returned")
|
||||
t.Fatal("Expected a static creds provider to be returned")
|
||||
}
|
||||
|
||||
v, err := creds.Get()
|
||||
|
@ -335,10 +348,14 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) {
|
|||
Token: c.Token,
|
||||
}
|
||||
|
||||
creds := GetCredentials(&cfg)
|
||||
if creds == nil {
|
||||
t.Fatalf("Expected a static creds provider to be returned")
|
||||
creds, err := GetCredentials(&cfg)
|
||||
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)
|
||||
|
@ -362,7 +379,14 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) {
|
|||
ts := invalidAwsEnv(t)
|
||||
defer ts()
|
||||
|
||||
creds := GetCredentials(&Config{})
|
||||
creds, err := GetCredentials(&Config{})
|
||||
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")
|
||||
|
@ -380,11 +404,17 @@ func TestAWSGetCredentials_shouldIgnoreInvalidEndpoint(t *testing.T) {
|
|||
ts := invalidAwsEnv(t)
|
||||
defer ts()
|
||||
|
||||
creds := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
|
||||
creds, err := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error gettings creds: %s", err)
|
||||
}
|
||||
v, err := creds.Get()
|
||||
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)
|
||||
|
@ -406,10 +436,14 @@ func TestAWSGetCredentials_shouldCatchEC2RoleProvider(t *testing.T) {
|
|||
ts := awsEnv(t)
|
||||
defer ts()
|
||||
|
||||
creds := GetCredentials(&Config{})
|
||||
if creds == nil {
|
||||
t.Fatalf("Expected an EC2Role creds provider to be returned")
|
||||
creds, err := GetCredentials(&Config{})
|
||||
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)
|
||||
|
@ -452,10 +486,14 @@ func TestAWSGetCredentials_shouldBeShared(t *testing.T) {
|
|||
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
|
||||
}
|
||||
|
||||
creds := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
|
||||
if creds == nil {
|
||||
t.Fatalf("Expected a provider chain to be returned")
|
||||
creds, err := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
|
||||
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)
|
||||
|
@ -479,10 +517,14 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
|
|||
defer resetEnv()
|
||||
|
||||
cfg := Config{}
|
||||
creds := GetCredentials(&cfg)
|
||||
creds, err := GetCredentials(&cfg)
|
||||
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"
|
||||
)
|
||||
|
@ -68,6 +68,10 @@ type Config struct {
|
|||
Region string
|
||||
MaxRetries int
|
||||
|
||||
AssumeRoleARN string
|
||||
AssumeRoleExternalID string
|
||||
AssumeRoleSessionName string
|
||||
|
||||
AllowedAccountIds []interface{}
|
||||
ForbiddenAccountIds []interface{}
|
||||
|
||||
|
@ -135,149 +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 := GetCredentials(c)
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -321,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"
|
||||
|
@ -18,42 +19,44 @@ func Provider() terraform.ResourceProvider {
|
|||
// The actual provider
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"access_key": &schema.Schema{
|
||||
"access_key": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["access_key"],
|
||||
},
|
||||
|
||||
"secret_key": &schema.Schema{
|
||||
"secret_key": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["secret_key"],
|
||||
},
|
||||
|
||||
"profile": &schema.Schema{
|
||||
"profile": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["profile"],
|
||||
},
|
||||
|
||||
"shared_credentials_file": &schema.Schema{
|
||||
"assume_role": assumeRoleSchema(),
|
||||
|
||||
"shared_credentials_file": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["shared_credentials_file"],
|
||||
},
|
||||
|
||||
"token": &schema.Schema{
|
||||
"token": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["token"],
|
||||
},
|
||||
|
||||
"region": &schema.Schema{
|
||||
"region": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
|
||||
|
@ -64,14 +67,14 @@ func Provider() terraform.ResourceProvider {
|
|||
InputDefault: "us-east-1",
|
||||
},
|
||||
|
||||
"max_retries": &schema.Schema{
|
||||
"max_retries": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 11,
|
||||
Description: descriptions["max_retries"],
|
||||
},
|
||||
|
||||
"allowed_account_ids": &schema.Schema{
|
||||
"allowed_account_ids": {
|
||||
Type: schema.TypeSet,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Optional: true,
|
||||
|
@ -79,7 +82,7 @@ func Provider() terraform.ResourceProvider {
|
|||
Set: schema.HashString,
|
||||
},
|
||||
|
||||
"forbidden_account_ids": &schema.Schema{
|
||||
"forbidden_account_ids": {
|
||||
Type: schema.TypeSet,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Optional: true,
|
||||
|
@ -87,14 +90,14 @@ func Provider() terraform.ResourceProvider {
|
|||
Set: schema.HashString,
|
||||
},
|
||||
|
||||
"dynamodb_endpoint": &schema.Schema{
|
||||
"dynamodb_endpoint": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["dynamodb_endpoint"],
|
||||
},
|
||||
|
||||
"kinesis_endpoint": &schema.Schema{
|
||||
"kinesis_endpoint": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
|
@ -103,35 +106,35 @@ func Provider() terraform.ResourceProvider {
|
|||
|
||||
"endpoints": endpointsSchema(),
|
||||
|
||||
"insecure": &schema.Schema{
|
||||
"insecure": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: descriptions["insecure"],
|
||||
},
|
||||
|
||||
"skip_credentials_validation": &schema.Schema{
|
||||
"skip_credentials_validation": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: descriptions["skip_credentials_validation"],
|
||||
},
|
||||
|
||||
"skip_requesting_account_id": &schema.Schema{
|
||||
"skip_requesting_account_id": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: descriptions["skip_requesting_account_id"],
|
||||
},
|
||||
|
||||
"skip_metadata_api_check": &schema.Schema{
|
||||
"skip_metadata_api_check": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: descriptions["skip_metadata_api_check"],
|
||||
},
|
||||
|
||||
"s3_force_path_style": &schema.Schema{
|
||||
"s3_force_path_style": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
|
@ -393,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.",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,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() {
|
||||
|
@ -438,33 +461,72 @@ 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,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"iam": &schema.Schema{
|
||||
"iam": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["iam_endpoint"],
|
||||
},
|
||||
|
||||
"ec2": &schema.Schema{
|
||||
"ec2": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["ec2_endpoint"],
|
||||
},
|
||||
|
||||
"elb": &schema.Schema{
|
||||
"elb": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: descriptions["elb_endpoint"],
|
||||
},
|
||||
"s3": &schema.Schema{
|
||||
"s3": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
|
|
|
@ -60,7 +60,7 @@ func s3Factory(conf map[string]string) (Client, error) {
|
|||
kmsKeyID := conf["kms_key_id"]
|
||||
|
||||
var errs []error
|
||||
creds := terraformAws.GetCredentials(&terraformAws.Config{
|
||||
creds, err := terraformAws.GetCredentials(&terraformAws.Config{
|
||||
AccessKey: conf["access_key"],
|
||||
SecretKey: conf["secret_key"],
|
||||
Token: conf["token"],
|
||||
|
@ -69,7 +69,7 @@ func s3Factory(conf map[string]string) (Client, error) {
|
|||
})
|
||||
// Call Get to check for credential provider. If nothing found, we'll get an
|
||||
// error, and we can present it nicely to the user
|
||||
_, err := creds.Get()
|
||||
_, 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 S3 remote.
|
||||
|
|
|
@ -111,6 +111,23 @@ You can provide custom metadata API endpoint via `AWS_METADATA_ENDPOINT` variabl
|
|||
which expects the endpoint URL including the version
|
||||
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
|
||||
using the supplied credentials.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
provider "aws" {
|
||||
assume_role {
|
||||
role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
|
||||
session_name = "SESSION_NAME"
|
||||
external_id = "EXTERNAL_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported in the `provider` block:
|
||||
|
@ -130,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.
|
||||
|
||||
|
@ -187,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