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