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