From c2bcb5fbe526069e68e4ffb570c70430599ec84d Mon Sep 17 00:00:00 2001 From: Renier Morales Date: Wed, 10 Aug 2016 10:10:34 -0400 Subject: [PATCH] Skip IAM/STS validation and metadata check (#7874) * Skip IAM/STS validation and metadata check * Skip IAM/STS identity validation - For environments or other api implementations where there are no IAM/STS endpoints available, this option lets you opt out from that provider initialization step. * Skip metdata api check - For environments in which you know ahead of time there isn't going to be a metadta api endpoint, this option lets you opt out from that check to save time. * Allow iam/sts initialization even if skipping account/cred validation (#7874) * Split out skip of IAM validation into credentials and account id (#7874) --- builtin/providers/aws/auth_helpers.go | 38 +++++++-------- builtin/providers/aws/auth_helpers_test.go | 18 ++++---- builtin/providers/aws/config.go | 45 ++++++++++-------- builtin/providers/aws/provider.go | 54 ++++++++++++++++++---- state/remote/s3.go | 8 +++- 5 files changed, 107 insertions(+), 56 deletions(-) diff --git a/builtin/providers/aws/auth_helpers.go b/builtin/providers/aws/auth_helpers.go index 552a4234f..91671a801 100644 --- a/builtin/providers/aws/auth_helpers.go +++ b/builtin/providers/aws/auth_helpers.go @@ -86,18 +86,18 @@ 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(key, secret, token, profile, credsfile string) *awsCredentials.Credentials { +func GetCredentials(c *Config) *awsCredentials.Credentials { // build a chain provider, lazy-evaulated by aws-sdk providers := []awsCredentials.Provider{ &awsCredentials.StaticProvider{Value: awsCredentials.Value{ - AccessKeyID: key, - SecretAccessKey: secret, - SessionToken: token, + AccessKeyID: c.AccessKey, + SecretAccessKey: c.SecretKey, + SessionToken: c.Token, }}, &awsCredentials.EnvProvider{}, &awsCredentials.SharedCredentialsProvider{ - Filename: credsfile, - Profile: profile, + Filename: c.CredsFilename, + Profile: c.Profile, }, } @@ -114,19 +114,21 @@ func GetCredentials(key, secret, token, profile, credsfile string) *awsCredentia // Real AWS should reply to a simple metadata request. // We check it actually does to ensure something else didn't just // happen to be listening on the same IP:Port - metadataClient := ec2metadata.New(session.New(cfg)) - if metadataClient.Available() { - providers = append(providers, &ec2rolecreds.EC2RoleProvider{ - Client: metadataClient, - }) - log.Printf("[INFO] AWS EC2 instance detected via default metadata" + - " API endpoint, EC2RoleProvider added to the auth chain") - } else { - if usedEndpoint == "" { - usedEndpoint = "default location" + if c.SkipMetadataApiCheck == false { + metadataClient := ec2metadata.New(session.New(cfg)) + if metadataClient.Available() { + providers = append(providers, &ec2rolecreds.EC2RoleProvider{ + Client: metadataClient, + }) + log.Printf("[INFO] AWS EC2 instance detected via default metadata" + + " API endpoint, EC2RoleProvider added to the auth chain") + } else { + if usedEndpoint == "" { + usedEndpoint = "default location" + } + log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+ + "as it doesn't return any instance-id", usedEndpoint) } - log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+ - "as it doesn't return any instance-id", usedEndpoint) } return awsCredentials.NewChainCredentials(providers) diff --git a/builtin/providers/aws/auth_helpers_test.go b/builtin/providers/aws/auth_helpers_test.go index a9de0fcc6..b5e1699a0 100644 --- a/builtin/providers/aws/auth_helpers_test.go +++ b/builtin/providers/aws/auth_helpers_test.go @@ -218,7 +218,7 @@ func TestAWSGetCredentials_shouldError(t *testing.T) { defer resetEnv() cfg := Config{} - c := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) + c := GetCredentials(&cfg) _, err := c.Get() if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() != "NoCredentialProviders" { @@ -251,7 +251,7 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) { Token: c.Token, } - creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) + creds := GetCredentials(&cfg) if creds == nil { t.Fatalf("Expected a static creds provider to be returned") } @@ -286,7 +286,7 @@ func TestAWSGetCredentials_shouldIAM(t *testing.T) { // An empty config, no key supplied cfg := Config{} - creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) + creds := GetCredentials(&cfg) if creds == nil { t.Fatalf("Expected a static creds provider to be returned") } @@ -335,7 +335,7 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) { Token: c.Token, } - creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) + creds := GetCredentials(&cfg) if creds == nil { t.Fatalf("Expected a static creds provider to be returned") } @@ -362,7 +362,7 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) { ts := invalidAwsEnv(t) defer ts() - creds := GetCredentials("", "", "", "", "") + creds := GetCredentials(&Config{}) v, err := creds.Get() if err == nil { t.Fatal("Expected error returned when getting creds w/ invalid EC2 endpoint") @@ -380,7 +380,7 @@ func TestAWSGetCredentials_shouldIgnoreInvalidEndpoint(t *testing.T) { ts := invalidAwsEnv(t) defer ts() - creds := GetCredentials("accessKey", "secretKey", "", "", "") + creds := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"}) v, err := creds.Get() if err != nil { t.Fatalf("Getting static credentials w/ invalid EC2 endpoint failed: %s", err) @@ -406,7 +406,7 @@ func TestAWSGetCredentials_shouldCatchEC2RoleProvider(t *testing.T) { ts := awsEnv(t) defer ts() - creds := GetCredentials("", "", "", "", "") + creds := GetCredentials(&Config{}) if creds == nil { t.Fatalf("Expected an EC2Role creds provider to be returned") } @@ -452,7 +452,7 @@ func TestAWSGetCredentials_shouldBeShared(t *testing.T) { t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err) } - creds := GetCredentials("", "", "", "myprofile", file.Name()) + creds := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()}) if creds == nil { t.Fatalf("Expected a provider chain to be returned") } @@ -479,7 +479,7 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) { defer resetEnv() cfg := Config{} - creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) + creds := GetCredentials(&cfg) if creds == nil { t.Fatalf("Expected a static creds provider to be returned") } diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 434bdffdd..84a7b5d26 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -70,12 +70,16 @@ type Config struct { AllowedAccountIds []interface{} ForbiddenAccountIds []interface{} - DynamoDBEndpoint string - KinesisEndpoint string - Ec2Endpoint string - IamEndpoint string - ElbEndpoint string - Insecure bool + DynamoDBEndpoint string + KinesisEndpoint string + Ec2Endpoint string + IamEndpoint string + ElbEndpoint string + S3Endpoint string + Insecure bool + SkipIamCredsValidation bool + SkipIamAccountId bool + SkipMetadataApiCheck bool } type AWSClient struct { @@ -141,7 +145,7 @@ func (c *Config) Client() (interface{}, error) { client.region = c.Region log.Println("[INFO] Building AWS auth structure") - creds := GetCredentials(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename) + 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() @@ -199,19 +203,24 @@ func (c *Config) Client() (interface{}, error) { client.iamconn = iam.New(awsIamSess) client.stsconn = sts.New(sess) - err = c.ValidateCredentials(client.stsconn) - if err != nil { - errs = append(errs, err) - return nil, &multierror.Error{Errors: errs} - } - accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName) - if err == nil { - client.accountid = accountId + if c.SkipIamCredsValidation == false { + err = c.ValidateCredentials(client.stsconn) + if err != nil { + errs = append(errs, err) + return nil, &multierror.Error{Errors: errs} + } } - authErr := c.ValidateAccountId(client.accountid) - if authErr != nil { - errs = append(errs, authErr) + if c.SkipIamAccountId == false { + 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) diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index dab42ba87..af041e44e 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -100,6 +100,7 @@ func Provider() terraform.ResourceProvider { Default: "", Description: descriptions["kinesis_endpoint"], }, + "endpoints": endpointsSchema(), "insecure": &schema.Schema{ @@ -108,6 +109,27 @@ func Provider() terraform.ResourceProvider { Default: false, Description: descriptions["insecure"], }, + + "skip_iam_creds_validation": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: descriptions["skip_iam_creds_validation"], + }, + + "skip_iam_account_id": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: descriptions["skip_iam_account_id"], + }, + + "skip_metadata_api_check": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: descriptions["skip_metadata_api_check"], + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -332,21 +354,33 @@ func init() { "insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted," + "default value is `false`", + + "skip_iam_creds_validation": "Skip the IAM/STS credentials validation. " + + "Used for AWS API implementations that do not use IAM.", + + "skip_iam_account_id": "Skip the request of account id to IAM/STS. " + + "Used for AWS API implementations that do not use IAM.", + + "skip_medatadata_api_check": "Skip the AWS Metadata API check. " + + "Used for AWS API implementations that do not have a metadata api endpoint.", } } func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ - AccessKey: d.Get("access_key").(string), - SecretKey: d.Get("secret_key").(string), - Profile: d.Get("profile").(string), - CredsFilename: d.Get("shared_credentials_file").(string), - Token: d.Get("token").(string), - Region: d.Get("region").(string), - MaxRetries: d.Get("max_retries").(int), - DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string), - KinesisEndpoint: d.Get("kinesis_endpoint").(string), - Insecure: d.Get("insecure").(bool), + AccessKey: d.Get("access_key").(string), + SecretKey: d.Get("secret_key").(string), + Profile: d.Get("profile").(string), + CredsFilename: d.Get("shared_credentials_file").(string), + Token: d.Get("token").(string), + Region: d.Get("region").(string), + MaxRetries: d.Get("max_retries").(int), + DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string), + KinesisEndpoint: d.Get("kinesis_endpoint").(string), + Insecure: d.Get("insecure").(bool), + SkipIamCredsValidation: d.Get("skip_iam_creds_validation").(bool), + SkipIamAccountId: d.Get("skip_iam_account_id").(bool), + SkipMetadataApiCheck: d.Get("skip_metadata_api_check").(bool), } endpointsSet := d.Get("endpoints").(*schema.Set) diff --git a/state/remote/s3.go b/state/remote/s3.go index 230df5fce..026e50a11 100644 --- a/state/remote/s3.go +++ b/state/remote/s3.go @@ -60,7 +60,13 @@ func s3Factory(conf map[string]string) (Client, error) { kmsKeyID := conf["kms_key_id"] var errs []error - creds := terraformAws.GetCredentials(conf["access_key"], conf["secret_key"], conf["token"], conf["profile"], conf["shared_credentials_file"]) + creds := terraformAws.GetCredentials(&terraformAws.Config{ + AccessKey: conf["access_key"], + SecretKey: conf["secret_key"], + Token: conf["token"], + Profile: conf["profile"], + CredsFilename: conf["shared_credentials_file"], + }) // 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()