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