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