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