github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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 // Removes the SDK Version handler, so we only have the provider User-Agent 229 // Ex: "User-Agent: APN/1.0 HashiCorp/1.0 Terraform/0.7.9-dev" 230 sess.Handlers.Build.Remove(request.NamedHandler{Name: "core.SDKVersionUserAgentHandler"}) 231 sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent) 232 233 if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" { 234 sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure) 235 } 236 237 // Some services exist only in us-east-1, e.g. because they manage 238 // resources that can span across multiple regions, or because 239 // signature format v4 requires region to be us-east-1 for global 240 // endpoints: 241 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 242 usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")}) 243 244 // Some services have user-configurable endpoints 245 awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) 246 awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) 247 awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) 248 awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)}) 249 dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) 250 kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) 251 252 // These two services need to be set up early so we can check on AccountID 253 client.iamconn = iam.New(awsIamSess) 254 client.stsconn = sts.New(sess) 255 256 if !c.SkipCredsValidation { 257 err = c.ValidateCredentials(client.stsconn) 258 if err != nil { 259 return nil, err 260 } 261 } 262 263 if !c.SkipRequestingAccountId { 264 partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName) 265 if err == nil { 266 client.partition = partition 267 client.accountid = accountId 268 } 269 } 270 271 authErr := c.ValidateAccountId(client.accountid) 272 if authErr != nil { 273 return nil, authErr 274 } 275 276 client.ec2conn = ec2.New(awsEc2Sess) 277 278 supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn) 279 if err != nil { 280 // We intentionally fail *silently* because there's a chance 281 // user just doesn't have ec2:DescribeAccountAttributes permissions 282 log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err) 283 } else { 284 client.supportedplatforms = supportedPlatforms 285 } 286 287 client.acmconn = acm.New(sess) 288 client.apigateway = apigateway.New(sess) 289 client.appautoscalingconn = applicationautoscaling.New(sess) 290 client.autoscalingconn = autoscaling.New(sess) 291 client.cfconn = cloudformation.New(sess) 292 client.cloudfrontconn = cloudfront.New(sess) 293 client.cloudtrailconn = cloudtrail.New(sess) 294 client.cloudwatchconn = cloudwatch.New(sess) 295 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 296 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 297 client.codecommitconn = codecommit.New(sess) 298 client.codebuildconn = codebuild.New(sess) 299 client.codedeployconn = codedeploy.New(sess) 300 client.configconn = configservice.New(sess) 301 client.dmsconn = databasemigrationservice.New(sess) 302 client.codepipelineconn = codepipeline.New(sess) 303 client.dsconn = directoryservice.New(sess) 304 client.dynamodbconn = dynamodb.New(dynamoSess) 305 client.ecrconn = ecr.New(sess) 306 client.ecsconn = ecs.New(sess) 307 client.efsconn = efs.New(sess) 308 client.elasticacheconn = elasticache.New(sess) 309 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 310 client.elastictranscoderconn = elastictranscoder.New(sess) 311 client.elbconn = elb.New(awsElbSess) 312 client.elbv2conn = elbv2.New(awsElbSess) 313 client.emrconn = emr.New(sess) 314 client.esconn = elasticsearch.New(sess) 315 client.firehoseconn = firehose.New(sess) 316 client.inspectorconn = inspector.New(sess) 317 client.glacierconn = glacier.New(sess) 318 client.kinesisconn = kinesis.New(kinesisSess) 319 client.kmsconn = kms.New(sess) 320 client.lambdaconn = lambda.New(sess) 321 client.lightsailconn = lightsail.New(usEast1Sess) 322 client.opsworksconn = opsworks.New(sess) 323 client.r53conn = route53.New(usEast1Sess) 324 client.rdsconn = rds.New(sess) 325 client.redshiftconn = redshift.New(sess) 326 client.simpledbconn = simpledb.New(sess) 327 client.s3conn = s3.New(awsS3Sess) 328 client.sesConn = ses.New(sess) 329 client.sfnconn = sfn.New(sess) 330 client.snsconn = sns.New(sess) 331 client.sqsconn = sqs.New(sess) 332 client.ssmconn = ssm.New(sess) 333 client.wafconn = waf.New(sess) 334 335 return &client, nil 336 } 337 338 // ValidateRegion returns an error if the configured region is not a 339 // valid aws region and nil otherwise. 340 func (c *Config) ValidateRegion() error { 341 var regions = []string{ 342 "ap-northeast-1", 343 "ap-northeast-2", 344 "ap-south-1", 345 "ap-southeast-1", 346 "ap-southeast-2", 347 "ca-central-1", 348 "cn-north-1", 349 "eu-central-1", 350 "eu-west-1", 351 "eu-west-2", 352 "sa-east-1", 353 "us-east-1", 354 "us-east-2", 355 "us-gov-west-1", 356 "us-west-1", 357 "us-west-2", 358 } 359 360 for _, valid := range regions { 361 if c.Region == valid { 362 return nil 363 } 364 } 365 return fmt.Errorf("Not a valid region: %s", c.Region) 366 } 367 368 // Validate credentials early and fail before we do any graph walking. 369 func (c *Config) ValidateCredentials(stsconn *sts.STS) error { 370 _, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 371 return err 372 } 373 374 // ValidateAccountId returns a context-specific error if the configured account 375 // id is explicitly forbidden or not authorised; and nil if it is authorised. 376 func (c *Config) ValidateAccountId(accountId string) error { 377 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 378 return nil 379 } 380 381 log.Println("[INFO] Validating account ID") 382 383 if c.ForbiddenAccountIds != nil { 384 for _, id := range c.ForbiddenAccountIds { 385 if id == accountId { 386 return fmt.Errorf("Forbidden account ID (%s)", id) 387 } 388 } 389 } 390 391 if c.AllowedAccountIds != nil { 392 for _, id := range c.AllowedAccountIds { 393 if id == accountId { 394 return nil 395 } 396 } 397 return fmt.Errorf("Account ID not allowed (%s)", accountId) 398 } 399 400 return nil 401 } 402 403 func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) { 404 attrName := "supported-platforms" 405 406 input := ec2.DescribeAccountAttributesInput{ 407 AttributeNames: []*string{aws.String(attrName)}, 408 } 409 attributes, err := conn.DescribeAccountAttributes(&input) 410 if err != nil { 411 return nil, err 412 } 413 414 var platforms []string 415 for _, attr := range attributes.AccountAttributes { 416 if *attr.AttributeName == attrName { 417 for _, v := range attr.AttributeValues { 418 platforms = append(platforms, *v.AttributeValue) 419 } 420 break 421 } 422 } 423 424 if len(platforms) == 0 { 425 return nil, fmt.Errorf("No EC2 platforms detected") 426 } 427 428 return platforms, nil 429 } 430 431 // addTerraformVersionToUserAgent is a named handler that will add Terraform's 432 // version information to requests made by the AWS SDK. 433 var addTerraformVersionToUserAgent = request.NamedHandler{ 434 Name: "terraform.TerraformVersionUserAgentHandler", 435 Fn: request.MakeAddToUserAgentHandler( 436 "APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()), 437 } 438 439 var debugAuthFailure = request.NamedHandler{ 440 Name: "terraform.AuthFailureAdditionalDebugHandler", 441 Fn: func(req *request.Request) { 442 if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") { 443 log.Printf("[INFO] Additional AuthFailure Debugging Context") 444 log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC()) 445 log.Printf("[INFO] Request object: %s", spew.Sdump(req)) 446 } 447 }, 448 } 449 450 type awsLogger struct{} 451 452 func (l awsLogger) Log(args ...interface{}) { 453 tokens := make([]string, 0, len(args)) 454 for _, arg := range args { 455 if token, ok := arg.(string); ok { 456 tokens = append(tokens, token) 457 } 458 } 459 log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) 460 }