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