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)
This commit is contained in:
Renier Morales 2016-08-10 10:10:34 -04:00 committed by Radek Simko
parent 6b477e888a
commit c2bcb5fbe5
5 changed files with 107 additions and 56 deletions

View File

@ -86,18 +86,18 @@ func parseAccountIdFromArn(arn string) (string, error) {
// This function is responsible for reading credentials from the // This function is responsible for reading credentials from the
// environment in the case that they're not explicitly specified // environment in the case that they're not explicitly specified
// in the Terraform configuration. // 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 // build a chain provider, lazy-evaulated by aws-sdk
providers := []awsCredentials.Provider{ providers := []awsCredentials.Provider{
&awsCredentials.StaticProvider{Value: awsCredentials.Value{ &awsCredentials.StaticProvider{Value: awsCredentials.Value{
AccessKeyID: key, AccessKeyID: c.AccessKey,
SecretAccessKey: secret, SecretAccessKey: c.SecretKey,
SessionToken: token, SessionToken: c.Token,
}}, }},
&awsCredentials.EnvProvider{}, &awsCredentials.EnvProvider{},
&awsCredentials.SharedCredentialsProvider{ &awsCredentials.SharedCredentialsProvider{
Filename: credsfile, Filename: c.CredsFilename,
Profile: profile, 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. // Real AWS should reply to a simple metadata request.
// We check it actually does to ensure something else didn't just // We check it actually does to ensure something else didn't just
// happen to be listening on the same IP:Port // happen to be listening on the same IP:Port
metadataClient := ec2metadata.New(session.New(cfg)) if c.SkipMetadataApiCheck == false {
if metadataClient.Available() { metadataClient := ec2metadata.New(session.New(cfg))
providers = append(providers, &ec2rolecreds.EC2RoleProvider{ if metadataClient.Available() {
Client: metadataClient, 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") log.Printf("[INFO] AWS EC2 instance detected via default metadata" +
} else { " API endpoint, EC2RoleProvider added to the auth chain")
if usedEndpoint == "" { } else {
usedEndpoint = "default location" 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) return awsCredentials.NewChainCredentials(providers)

View File

@ -218,7 +218,7 @@ func TestAWSGetCredentials_shouldError(t *testing.T) {
defer resetEnv() defer resetEnv()
cfg := Config{} cfg := Config{}
c := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) c := GetCredentials(&cfg)
_, err := c.Get() _, err := c.Get()
if awsErr, ok := err.(awserr.Error); ok { if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" { if awsErr.Code() != "NoCredentialProviders" {
@ -251,7 +251,7 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) {
Token: c.Token, Token: c.Token,
} }
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) creds := GetCredentials(&cfg)
if creds == nil { if creds == nil {
t.Fatalf("Expected a static creds provider to be returned") 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 // An empty config, no key supplied
cfg := Config{} cfg := Config{}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) creds := GetCredentials(&cfg)
if creds == nil { if creds == nil {
t.Fatalf("Expected a static creds provider to be returned") t.Fatalf("Expected a static creds provider to be returned")
} }
@ -335,7 +335,7 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) {
Token: c.Token, Token: c.Token,
} }
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) creds := GetCredentials(&cfg)
if creds == nil { if creds == nil {
t.Fatalf("Expected a static creds provider to be returned") t.Fatalf("Expected a static creds provider to be returned")
} }
@ -362,7 +362,7 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) {
ts := invalidAwsEnv(t) ts := invalidAwsEnv(t)
defer ts() defer ts()
creds := GetCredentials("", "", "", "", "") creds := GetCredentials(&Config{})
v, err := creds.Get() v, err := creds.Get()
if err == nil { if err == nil {
t.Fatal("Expected error returned when getting creds w/ invalid EC2 endpoint") 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) ts := invalidAwsEnv(t)
defer ts() defer ts()
creds := GetCredentials("accessKey", "secretKey", "", "", "") creds := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
v, err := creds.Get() v, err := creds.Get()
if err != nil { if err != nil {
t.Fatalf("Getting static credentials w/ invalid EC2 endpoint failed: %s", err) 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) ts := awsEnv(t)
defer ts() defer ts()
creds := GetCredentials("", "", "", "", "") creds := GetCredentials(&Config{})
if creds == nil { if creds == nil {
t.Fatalf("Expected an EC2Role creds provider to be returned") 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) 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 { if creds == nil {
t.Fatalf("Expected a provider chain to be returned") t.Fatalf("Expected a provider chain to be returned")
} }
@ -479,7 +479,7 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
defer resetEnv() defer resetEnv()
cfg := Config{} cfg := Config{}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename) creds := GetCredentials(&cfg)
if creds == nil { if creds == nil {
t.Fatalf("Expected a static creds provider to be returned") t.Fatalf("Expected a static creds provider to be returned")
} }

View File

@ -70,12 +70,16 @@ type Config struct {
AllowedAccountIds []interface{} AllowedAccountIds []interface{}
ForbiddenAccountIds []interface{} ForbiddenAccountIds []interface{}
DynamoDBEndpoint string DynamoDBEndpoint string
KinesisEndpoint string KinesisEndpoint string
Ec2Endpoint string Ec2Endpoint string
IamEndpoint string IamEndpoint string
ElbEndpoint string ElbEndpoint string
Insecure bool S3Endpoint string
Insecure bool
SkipIamCredsValidation bool
SkipIamAccountId bool
SkipMetadataApiCheck bool
} }
type AWSClient struct { type AWSClient struct {
@ -141,7 +145,7 @@ func (c *Config) Client() (interface{}, error) {
client.region = c.Region client.region = c.Region
log.Println("[INFO] Building AWS auth structure") 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 // Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user // error, and we can present it nicely to the user
cp, err := creds.Get() cp, err := creds.Get()
@ -199,19 +203,24 @@ func (c *Config) Client() (interface{}, error) {
client.iamconn = iam.New(awsIamSess) client.iamconn = iam.New(awsIamSess)
client.stsconn = sts.New(sess) client.stsconn = sts.New(sess)
err = c.ValidateCredentials(client.stsconn) if c.SkipIamCredsValidation == false {
if err != nil { err = c.ValidateCredentials(client.stsconn)
errs = append(errs, err) if err != nil {
return nil, &multierror.Error{Errors: errs} 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
} }
authErr := c.ValidateAccountId(client.accountid) if c.SkipIamAccountId == false {
if authErr != nil { accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName)
errs = append(errs, authErr) if err == nil {
client.accountid = accountId
}
authErr := c.ValidateAccountId(client.accountid)
if authErr != nil {
errs = append(errs, authErr)
}
} }
client.apigateway = apigateway.New(sess) client.apigateway = apigateway.New(sess)

View File

@ -100,6 +100,7 @@ func Provider() terraform.ResourceProvider {
Default: "", Default: "",
Description: descriptions["kinesis_endpoint"], Description: descriptions["kinesis_endpoint"],
}, },
"endpoints": endpointsSchema(), "endpoints": endpointsSchema(),
"insecure": &schema.Schema{ "insecure": &schema.Schema{
@ -108,6 +109,27 @@ func Provider() terraform.ResourceProvider {
Default: false, Default: false,
Description: descriptions["insecure"], 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{ DataSourcesMap: map[string]*schema.Resource{
@ -332,21 +354,33 @@ func init() {
"insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted," + "insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted," +
"default value is `false`", "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) { func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{ config := Config{
AccessKey: d.Get("access_key").(string), AccessKey: d.Get("access_key").(string),
SecretKey: d.Get("secret_key").(string), SecretKey: d.Get("secret_key").(string),
Profile: d.Get("profile").(string), Profile: d.Get("profile").(string),
CredsFilename: d.Get("shared_credentials_file").(string), CredsFilename: d.Get("shared_credentials_file").(string),
Token: d.Get("token").(string), Token: d.Get("token").(string),
Region: d.Get("region").(string), Region: d.Get("region").(string),
MaxRetries: d.Get("max_retries").(int), MaxRetries: d.Get("max_retries").(int),
DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string), DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string),
KinesisEndpoint: d.Get("kinesis_endpoint").(string), KinesisEndpoint: d.Get("kinesis_endpoint").(string),
Insecure: d.Get("insecure").(bool), 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) endpointsSet := d.Get("endpoints").(*schema.Set)

View File

@ -60,7 +60,13 @@ func s3Factory(conf map[string]string) (Client, error) {
kmsKeyID := conf["kms_key_id"] kmsKeyID := conf["kms_key_id"]
var errs []error 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 // Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user // error, and we can present it nicely to the user
_, err := creds.Get() _, err := creds.Get()