github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/config.go (about) 1 package aws 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "log" 8 "net/http" 9 "os" 10 "strings" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/aws/request" 16 "github.com/aws/aws-sdk-go/aws/session" 17 "github.com/aws/aws-sdk-go/service/acm" 18 "github.com/aws/aws-sdk-go/service/apigateway" 19 "github.com/aws/aws-sdk-go/service/applicationautoscaling" 20 "github.com/aws/aws-sdk-go/service/autoscaling" 21 "github.com/aws/aws-sdk-go/service/cloudformation" 22 "github.com/aws/aws-sdk-go/service/cloudfront" 23 "github.com/aws/aws-sdk-go/service/cloudtrail" 24 "github.com/aws/aws-sdk-go/service/cloudwatch" 25 "github.com/aws/aws-sdk-go/service/cloudwatchevents" 26 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 27 "github.com/aws/aws-sdk-go/service/codebuild" 28 "github.com/aws/aws-sdk-go/service/codecommit" 29 "github.com/aws/aws-sdk-go/service/codedeploy" 30 "github.com/aws/aws-sdk-go/service/codepipeline" 31 "github.com/aws/aws-sdk-go/service/cognitoidentity" 32 "github.com/aws/aws-sdk-go/service/configservice" 33 "github.com/aws/aws-sdk-go/service/databasemigrationservice" 34 "github.com/aws/aws-sdk-go/service/directoryservice" 35 "github.com/aws/aws-sdk-go/service/dynamodb" 36 "github.com/aws/aws-sdk-go/service/ec2" 37 "github.com/aws/aws-sdk-go/service/ecr" 38 "github.com/aws/aws-sdk-go/service/ecs" 39 "github.com/aws/aws-sdk-go/service/efs" 40 "github.com/aws/aws-sdk-go/service/elasticache" 41 "github.com/aws/aws-sdk-go/service/elasticbeanstalk" 42 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 43 "github.com/aws/aws-sdk-go/service/elastictranscoder" 44 "github.com/aws/aws-sdk-go/service/elb" 45 "github.com/aws/aws-sdk-go/service/elbv2" 46 "github.com/aws/aws-sdk-go/service/emr" 47 "github.com/aws/aws-sdk-go/service/firehose" 48 "github.com/aws/aws-sdk-go/service/glacier" 49 "github.com/aws/aws-sdk-go/service/iam" 50 "github.com/aws/aws-sdk-go/service/inspector" 51 "github.com/aws/aws-sdk-go/service/kinesis" 52 "github.com/aws/aws-sdk-go/service/kms" 53 "github.com/aws/aws-sdk-go/service/lambda" 54 "github.com/aws/aws-sdk-go/service/lightsail" 55 "github.com/aws/aws-sdk-go/service/opsworks" 56 "github.com/aws/aws-sdk-go/service/rds" 57 "github.com/aws/aws-sdk-go/service/redshift" 58 "github.com/aws/aws-sdk-go/service/route53" 59 "github.com/aws/aws-sdk-go/service/s3" 60 "github.com/aws/aws-sdk-go/service/ses" 61 "github.com/aws/aws-sdk-go/service/sfn" 62 "github.com/aws/aws-sdk-go/service/simpledb" 63 "github.com/aws/aws-sdk-go/service/sns" 64 "github.com/aws/aws-sdk-go/service/sqs" 65 "github.com/aws/aws-sdk-go/service/ssm" 66 "github.com/aws/aws-sdk-go/service/sts" 67 "github.com/aws/aws-sdk-go/service/waf" 68 "github.com/davecgh/go-spew/spew" 69 "github.com/hashicorp/errwrap" 70 "github.com/hashicorp/go-cleanhttp" 71 "github.com/hashicorp/terraform/helper/logging" 72 "github.com/hashicorp/terraform/terraform" 73 ) 74 75 type Config struct { 76 AccessKey string 77 SecretKey string 78 CredsFilename string 79 Profile string 80 Token string 81 Region string 82 MaxRetries int 83 84 AssumeRoleARN string 85 AssumeRoleExternalID string 86 AssumeRoleSessionName string 87 AssumeRolePolicy string 88 89 AllowedAccountIds []interface{} 90 ForbiddenAccountIds []interface{} 91 92 DynamoDBEndpoint string 93 KinesisEndpoint string 94 Ec2Endpoint string 95 IamEndpoint string 96 ElbEndpoint string 97 S3Endpoint string 98 Insecure bool 99 100 SkipCredsValidation bool 101 SkipGetEC2Platforms bool 102 SkipRegionValidation bool 103 SkipRequestingAccountId bool 104 SkipMetadataApiCheck bool 105 S3ForcePathStyle bool 106 } 107 108 type AWSClient struct { 109 cfconn *cloudformation.CloudFormation 110 cloudfrontconn *cloudfront.CloudFront 111 cloudtrailconn *cloudtrail.CloudTrail 112 cloudwatchconn *cloudwatch.CloudWatch 113 cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs 114 cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents 115 cognitoconn *cognitoidentity.CognitoIdentity 116 configconn *configservice.ConfigService 117 dmsconn *databasemigrationservice.DatabaseMigrationService 118 dsconn *directoryservice.DirectoryService 119 dynamodbconn *dynamodb.DynamoDB 120 ec2conn *ec2.EC2 121 ecrconn *ecr.ECR 122 ecsconn *ecs.ECS 123 efsconn *efs.EFS 124 elbconn *elb.ELB 125 elbv2conn *elbv2.ELBV2 126 emrconn *emr.EMR 127 esconn *elasticsearch.ElasticsearchService 128 acmconn *acm.ACM 129 apigateway *apigateway.APIGateway 130 appautoscalingconn *applicationautoscaling.ApplicationAutoScaling 131 autoscalingconn *autoscaling.AutoScaling 132 s3conn *s3.S3 133 sesConn *ses.SES 134 simpledbconn *simpledb.SimpleDB 135 sqsconn *sqs.SQS 136 snsconn *sns.SNS 137 stsconn *sts.STS 138 redshiftconn *redshift.Redshift 139 r53conn *route53.Route53 140 partition string 141 accountid string 142 supportedplatforms []string 143 region string 144 rdsconn *rds.RDS 145 iamconn *iam.IAM 146 kinesisconn *kinesis.Kinesis 147 kmsconn *kms.KMS 148 firehoseconn *firehose.Firehose 149 inspectorconn *inspector.Inspector 150 elasticacheconn *elasticache.ElastiCache 151 elasticbeanstalkconn *elasticbeanstalk.ElasticBeanstalk 152 elastictranscoderconn *elastictranscoder.ElasticTranscoder 153 lambdaconn *lambda.Lambda 154 lightsailconn *lightsail.Lightsail 155 opsworksconn *opsworks.OpsWorks 156 glacierconn *glacier.Glacier 157 codebuildconn *codebuild.CodeBuild 158 codedeployconn *codedeploy.CodeDeploy 159 codecommitconn *codecommit.CodeCommit 160 codepipelineconn *codepipeline.CodePipeline 161 sfnconn *sfn.SFN 162 ssmconn *ssm.SSM 163 wafconn *waf.WAF 164 } 165 166 func (c *AWSClient) S3() *s3.S3 { 167 return c.s3conn 168 } 169 170 func (c *AWSClient) DynamoDB() *dynamodb.DynamoDB { 171 return c.dynamodbconn 172 } 173 174 // Client configures and returns a fully initialized AWSClient 175 func (c *Config) Client() (interface{}, error) { 176 // Get the auth and region. This can fail if keys/regions were not 177 // specified and we're attempting to use the environment. 178 if c.SkipRegionValidation { 179 log.Println("[INFO] Skipping region validation") 180 } else { 181 log.Println("[INFO] Building AWS region structure") 182 err := c.ValidateRegion() 183 if err != nil { 184 return nil, err 185 } 186 } 187 188 var client AWSClient 189 // store AWS region in client struct, for region specific operations such as 190 // bucket storage in S3 191 client.region = c.Region 192 193 log.Println("[INFO] Building AWS auth structure") 194 creds, err := GetCredentials(c) 195 if err != nil { 196 return nil, err 197 } 198 // Call Get to check for credential provider. If nothing found, we'll get an 199 // error, and we can present it nicely to the user 200 cp, err := creds.Get() 201 if err != nil { 202 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 203 return nil, errors.New(`No valid credential sources found for AWS Provider. 204 Please see https://terraform.io/docs/providers/aws/index.html for more information on 205 providing credentials for the AWS Provider`) 206 } 207 208 return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err) 209 } 210 211 log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName) 212 213 awsConfig := &aws.Config{ 214 Credentials: creds, 215 Region: aws.String(c.Region), 216 MaxRetries: aws.Int(c.MaxRetries), 217 HTTPClient: cleanhttp.DefaultClient(), 218 S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle), 219 } 220 221 if logging.IsDebugOrHigher() { 222 awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody) 223 awsConfig.Logger = awsLogger{} 224 } 225 226 if c.Insecure { 227 transport := awsConfig.HTTPClient.Transport.(*http.Transport) 228 transport.TLSClientConfig = &tls.Config{ 229 InsecureSkipVerify: true, 230 } 231 } 232 233 // Set up base session 234 sess, err := session.NewSession(awsConfig) 235 if err != nil { 236 return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err) 237 } 238 239 sess.Handlers.Build.PushBackNamed(addTerraformVersionToUserAgent) 240 241 if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" { 242 sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure) 243 } 244 245 // Some services exist only in us-east-1, e.g. because they manage 246 // resources that can span across multiple regions, or because 247 // signature format v4 requires region to be us-east-1 for global 248 // endpoints: 249 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 250 usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")}) 251 252 // Some services have user-configurable endpoints 253 awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) 254 awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) 255 awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) 256 awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)}) 257 dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) 258 kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) 259 260 // These two services need to be set up early so we can check on AccountID 261 client.iamconn = iam.New(awsIamSess) 262 client.stsconn = sts.New(sess) 263 264 if !c.SkipCredsValidation { 265 err = c.ValidateCredentials(client.stsconn) 266 if err != nil { 267 return nil, err 268 } 269 } 270 271 if !c.SkipRequestingAccountId { 272 partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName) 273 if err == nil { 274 client.partition = partition 275 client.accountid = accountId 276 } 277 } 278 279 authErr := c.ValidateAccountId(client.accountid) 280 if authErr != nil { 281 return nil, authErr 282 } 283 284 client.ec2conn = ec2.New(awsEc2Sess) 285 286 if !c.SkipGetEC2Platforms { 287 supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn) 288 if err != nil { 289 // We intentionally fail *silently* because there's a chance 290 // user just doesn't have ec2:DescribeAccountAttributes permissions 291 log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err) 292 } else { 293 client.supportedplatforms = supportedPlatforms 294 } 295 } 296 297 client.acmconn = acm.New(sess) 298 client.apigateway = apigateway.New(sess) 299 client.appautoscalingconn = applicationautoscaling.New(sess) 300 client.autoscalingconn = autoscaling.New(sess) 301 client.cfconn = cloudformation.New(sess) 302 client.cloudfrontconn = cloudfront.New(sess) 303 client.cloudtrailconn = cloudtrail.New(sess) 304 client.cloudwatchconn = cloudwatch.New(sess) 305 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 306 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 307 client.codecommitconn = codecommit.New(sess) 308 client.codebuildconn = codebuild.New(sess) 309 client.codedeployconn = codedeploy.New(sess) 310 client.configconn = configservice.New(sess) 311 client.cognitoconn = cognitoidentity.New(sess) 312 client.dmsconn = databasemigrationservice.New(sess) 313 client.codepipelineconn = codepipeline.New(sess) 314 client.dsconn = directoryservice.New(sess) 315 client.dynamodbconn = dynamodb.New(dynamoSess) 316 client.ecrconn = ecr.New(sess) 317 client.ecsconn = ecs.New(sess) 318 client.efsconn = efs.New(sess) 319 client.elasticacheconn = elasticache.New(sess) 320 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 321 client.elastictranscoderconn = elastictranscoder.New(sess) 322 client.elbconn = elb.New(awsElbSess) 323 client.elbv2conn = elbv2.New(awsElbSess) 324 client.emrconn = emr.New(sess) 325 client.esconn = elasticsearch.New(sess) 326 client.firehoseconn = firehose.New(sess) 327 client.inspectorconn = inspector.New(sess) 328 client.glacierconn = glacier.New(sess) 329 client.kinesisconn = kinesis.New(kinesisSess) 330 client.kmsconn = kms.New(sess) 331 client.lambdaconn = lambda.New(sess) 332 client.lightsailconn = lightsail.New(usEast1Sess) 333 client.opsworksconn = opsworks.New(sess) 334 client.r53conn = route53.New(usEast1Sess) 335 client.rdsconn = rds.New(sess) 336 client.redshiftconn = redshift.New(sess) 337 client.simpledbconn = simpledb.New(sess) 338 client.s3conn = s3.New(awsS3Sess) 339 client.sesConn = ses.New(sess) 340 client.sfnconn = sfn.New(sess) 341 client.snsconn = sns.New(sess) 342 client.sqsconn = sqs.New(sess) 343 client.ssmconn = ssm.New(sess) 344 client.wafconn = waf.New(sess) 345 346 return &client, nil 347 } 348 349 // ValidateRegion returns an error if the configured region is not a 350 // valid aws region and nil otherwise. 351 func (c *Config) ValidateRegion() error { 352 var regions = []string{ 353 "ap-northeast-1", 354 "ap-northeast-2", 355 "ap-south-1", 356 "ap-southeast-1", 357 "ap-southeast-2", 358 "ca-central-1", 359 "cn-north-1", 360 "eu-central-1", 361 "eu-west-1", 362 "eu-west-2", 363 "sa-east-1", 364 "us-east-1", 365 "us-east-2", 366 "us-gov-west-1", 367 "us-west-1", 368 "us-west-2", 369 } 370 371 for _, valid := range regions { 372 if c.Region == valid { 373 return nil 374 } 375 } 376 return fmt.Errorf("Not a valid region: %s", c.Region) 377 } 378 379 // Validate credentials early and fail before we do any graph walking. 380 func (c *Config) ValidateCredentials(stsconn *sts.STS) error { 381 _, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 382 return err 383 } 384 385 // ValidateAccountId returns a context-specific error if the configured account 386 // id is explicitly forbidden or not authorised; and nil if it is authorised. 387 func (c *Config) ValidateAccountId(accountId string) error { 388 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 389 return nil 390 } 391 392 log.Println("[INFO] Validating account ID") 393 394 if c.ForbiddenAccountIds != nil { 395 for _, id := range c.ForbiddenAccountIds { 396 if id == accountId { 397 return fmt.Errorf("Forbidden account ID (%s)", id) 398 } 399 } 400 } 401 402 if c.AllowedAccountIds != nil { 403 for _, id := range c.AllowedAccountIds { 404 if id == accountId { 405 return nil 406 } 407 } 408 return fmt.Errorf("Account ID not allowed (%s)", accountId) 409 } 410 411 return nil 412 } 413 414 func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) { 415 attrName := "supported-platforms" 416 417 input := ec2.DescribeAccountAttributesInput{ 418 AttributeNames: []*string{aws.String(attrName)}, 419 } 420 attributes, err := conn.DescribeAccountAttributes(&input) 421 if err != nil { 422 return nil, err 423 } 424 425 var platforms []string 426 for _, attr := range attributes.AccountAttributes { 427 if *attr.AttributeName == attrName { 428 for _, v := range attr.AttributeValues { 429 platforms = append(platforms, *v.AttributeValue) 430 } 431 break 432 } 433 } 434 435 if len(platforms) == 0 { 436 return nil, fmt.Errorf("No EC2 platforms detected") 437 } 438 439 return platforms, nil 440 } 441 442 // addTerraformVersionToUserAgent is a named handler that will add Terraform's 443 // version information to requests made by the AWS SDK. 444 var addTerraformVersionToUserAgent = request.NamedHandler{ 445 Name: "terraform.TerraformVersionUserAgentHandler", 446 Fn: request.MakeAddToUserAgentHandler( 447 "APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()), 448 } 449 450 var debugAuthFailure = request.NamedHandler{ 451 Name: "terraform.AuthFailureAdditionalDebugHandler", 452 Fn: func(req *request.Request) { 453 if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") { 454 log.Printf("[INFO] Additional AuthFailure Debugging Context") 455 log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC()) 456 log.Printf("[INFO] Request object: %s", spew.Sdump(req)) 457 } 458 }, 459 } 460 461 type awsLogger struct{} 462 463 func (l awsLogger) Log(args ...interface{}) { 464 tokens := make([]string, 0, len(args)) 465 for _, arg := range args { 466 if token, ok := arg.(string); ok { 467 tokens = append(tokens, token) 468 } 469 } 470 log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) 471 }