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