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