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