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