github.com/IBM-Cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/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 // Some services exist only in us-east-1, e.g. because they manage 273 // resources that can span across multiple regions, or because 274 // signature format v4 requires region to be us-east-1 for global 275 // endpoints: 276 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 277 usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")}) 278 279 // Some services have user-configurable endpoints 280 awsCfSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudFormationEndpoint)}) 281 awsCwSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEndpoint)}) 282 awsCweSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEventsEndpoint)}) 283 awsCwlSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchLogsEndpoint)}) 284 awsDynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) 285 awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) 286 awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) 287 awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) 288 awsKinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) 289 awsKmsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KmsEndpoint)}) 290 awsRdsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.RdsEndpoint)}) 291 awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)}) 292 awsSnsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SnsEndpoint)}) 293 awsSqsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SqsEndpoint)}) 294 awsDeviceFarmSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DeviceFarmEndpoint)}) 295 296 log.Println("[INFO] Initializing DeviceFarm SDK connection") 297 client.devicefarmconn = devicefarm.New(awsDeviceFarmSess) 298 299 // These two services need to be set up early so we can check on AccountID 300 client.iamconn = iam.New(awsIamSess) 301 client.stsconn = sts.New(sess) 302 303 if !c.SkipCredsValidation { 304 err = c.ValidateCredentials(client.stsconn) 305 if err != nil { 306 return nil, err 307 } 308 } 309 310 if !c.SkipRequestingAccountId { 311 partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName) 312 if err == nil { 313 client.partition = partition 314 client.accountid = accountId 315 } 316 } 317 318 authErr := c.ValidateAccountId(client.accountid) 319 if authErr != nil { 320 return nil, authErr 321 } 322 323 client.ec2conn = ec2.New(awsEc2Sess) 324 325 if !c.SkipGetEC2Platforms { 326 supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn) 327 if err != nil { 328 // We intentionally fail *silently* because there's a chance 329 // user just doesn't have ec2:DescribeAccountAttributes permissions 330 log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err) 331 } else { 332 client.supportedplatforms = supportedPlatforms 333 } 334 } 335 336 client.acmconn = acm.New(sess) 337 client.apigateway = apigateway.New(sess) 338 client.appautoscalingconn = applicationautoscaling.New(sess) 339 client.autoscalingconn = autoscaling.New(sess) 340 client.cfconn = cloudformation.New(awsCfSess) 341 client.cloudfrontconn = cloudfront.New(sess) 342 client.cloudtrailconn = cloudtrail.New(sess) 343 client.cloudwatchconn = cloudwatch.New(awsCwSess) 344 client.cloudwatcheventsconn = cloudwatchevents.New(awsCweSess) 345 client.cloudwatchlogsconn = cloudwatchlogs.New(awsCwlSess) 346 client.codecommitconn = codecommit.New(sess) 347 client.codebuildconn = codebuild.New(sess) 348 client.codedeployconn = codedeploy.New(sess) 349 client.configconn = configservice.New(sess) 350 client.cognitoconn = cognitoidentity.New(sess) 351 client.dmsconn = databasemigrationservice.New(sess) 352 client.codepipelineconn = codepipeline.New(sess) 353 client.dsconn = directoryservice.New(sess) 354 client.dynamodbconn = dynamodb.New(awsDynamoSess) 355 client.ecrconn = ecr.New(sess) 356 client.ecsconn = ecs.New(sess) 357 client.efsconn = efs.New(sess) 358 client.elasticacheconn = elasticache.New(sess) 359 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 360 client.elastictranscoderconn = elastictranscoder.New(sess) 361 client.elbconn = elb.New(awsElbSess) 362 client.elbv2conn = elbv2.New(awsElbSess) 363 client.emrconn = emr.New(sess) 364 client.esconn = elasticsearch.New(sess) 365 client.firehoseconn = firehose.New(sess) 366 client.inspectorconn = inspector.New(sess) 367 client.glacierconn = glacier.New(sess) 368 client.kinesisconn = kinesis.New(awsKinesisSess) 369 client.kmsconn = kms.New(awsKmsSess) 370 client.lambdaconn = lambda.New(sess) 371 client.lightsailconn = lightsail.New(usEast1Sess) 372 client.opsworksconn = opsworks.New(sess) 373 client.r53conn = route53.New(usEast1Sess) 374 client.rdsconn = rds.New(awsRdsSess) 375 client.redshiftconn = redshift.New(sess) 376 client.simpledbconn = simpledb.New(sess) 377 client.s3conn = s3.New(awsS3Sess) 378 client.sesConn = ses.New(sess) 379 client.sfnconn = sfn.New(sess) 380 client.snsconn = sns.New(awsSnsSess) 381 client.sqsconn = sqs.New(awsSqsSess) 382 client.ssmconn = ssm.New(sess) 383 client.wafconn = waf.New(sess) 384 client.wafregionalconn = wafregional.New(sess) 385 386 return &client, nil 387 } 388 389 // ValidateRegion returns an error if the configured region is not a 390 // valid aws region and nil otherwise. 391 func (c *Config) ValidateRegion() error { 392 var regions = []string{ 393 "ap-northeast-1", 394 "ap-northeast-2", 395 "ap-south-1", 396 "ap-southeast-1", 397 "ap-southeast-2", 398 "ca-central-1", 399 "cn-north-1", 400 "eu-central-1", 401 "eu-west-1", 402 "eu-west-2", 403 "sa-east-1", 404 "us-east-1", 405 "us-east-2", 406 "us-gov-west-1", 407 "us-west-1", 408 "us-west-2", 409 } 410 411 for _, valid := range regions { 412 if c.Region == valid { 413 return nil 414 } 415 } 416 return fmt.Errorf("Not a valid region: %s", c.Region) 417 } 418 419 // Validate credentials early and fail before we do any graph walking. 420 func (c *Config) ValidateCredentials(stsconn *sts.STS) error { 421 _, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 422 return err 423 } 424 425 // ValidateAccountId returns a context-specific error if the configured account 426 // id is explicitly forbidden or not authorised; and nil if it is authorised. 427 func (c *Config) ValidateAccountId(accountId string) error { 428 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 429 return nil 430 } 431 432 log.Println("[INFO] Validating account ID") 433 434 if c.ForbiddenAccountIds != nil { 435 for _, id := range c.ForbiddenAccountIds { 436 if id == accountId { 437 return fmt.Errorf("Forbidden account ID (%s)", id) 438 } 439 } 440 } 441 442 if c.AllowedAccountIds != nil { 443 for _, id := range c.AllowedAccountIds { 444 if id == accountId { 445 return nil 446 } 447 } 448 return fmt.Errorf("Account ID not allowed (%s)", accountId) 449 } 450 451 return nil 452 } 453 454 func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) { 455 attrName := "supported-platforms" 456 457 input := ec2.DescribeAccountAttributesInput{ 458 AttributeNames: []*string{aws.String(attrName)}, 459 } 460 attributes, err := conn.DescribeAccountAttributes(&input) 461 if err != nil { 462 return nil, err 463 } 464 465 var platforms []string 466 for _, attr := range attributes.AccountAttributes { 467 if *attr.AttributeName == attrName { 468 for _, v := range attr.AttributeValues { 469 platforms = append(platforms, *v.AttributeValue) 470 } 471 break 472 } 473 } 474 475 if len(platforms) == 0 { 476 return nil, fmt.Errorf("No EC2 platforms detected") 477 } 478 479 return platforms, nil 480 } 481 482 // addTerraformVersionToUserAgent is a named handler that will add Terraform's 483 // version information to requests made by the AWS SDK. 484 var addTerraformVersionToUserAgent = request.NamedHandler{ 485 Name: "terraform.TerraformVersionUserAgentHandler", 486 Fn: request.MakeAddToUserAgentHandler( 487 "APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()), 488 } 489 490 var debugAuthFailure = request.NamedHandler{ 491 Name: "terraform.AuthFailureAdditionalDebugHandler", 492 Fn: func(req *request.Request) { 493 if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") { 494 log.Printf("[INFO] Additional AuthFailure Debugging Context") 495 log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC()) 496 log.Printf("[INFO] Request object: %s", spew.Sdump(req)) 497 } 498 }, 499 } 500 501 type awsLogger struct{} 502 503 func (l awsLogger) Log(args ...interface{}) { 504 tokens := make([]string, 0, len(args)) 505 for _, arg := range args { 506 if token, ok := arg.(string); ok { 507 tokens = append(tokens, token) 508 } 509 } 510 log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) 511 }