github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/builtin/providers/aws/config.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "strings" 8 9 "github.com/hashicorp/go-cleanhttp" 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/helper/logging" 12 "github.com/hashicorp/terraform/terraform" 13 14 "crypto/tls" 15 16 "github.com/aws/aws-sdk-go/aws" 17 "github.com/aws/aws-sdk-go/aws/awserr" 18 "github.com/aws/aws-sdk-go/aws/request" 19 "github.com/aws/aws-sdk-go/aws/session" 20 "github.com/aws/aws-sdk-go/service/apigateway" 21 "github.com/aws/aws-sdk-go/service/autoscaling" 22 "github.com/aws/aws-sdk-go/service/cloudformation" 23 "github.com/aws/aws-sdk-go/service/cloudfront" 24 "github.com/aws/aws-sdk-go/service/cloudtrail" 25 "github.com/aws/aws-sdk-go/service/cloudwatch" 26 "github.com/aws/aws-sdk-go/service/cloudwatchevents" 27 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 28 "github.com/aws/aws-sdk-go/service/codecommit" 29 "github.com/aws/aws-sdk-go/service/codedeploy" 30 "github.com/aws/aws-sdk-go/service/directoryservice" 31 "github.com/aws/aws-sdk-go/service/dynamodb" 32 "github.com/aws/aws-sdk-go/service/ec2" 33 "github.com/aws/aws-sdk-go/service/ecr" 34 "github.com/aws/aws-sdk-go/service/ecs" 35 "github.com/aws/aws-sdk-go/service/efs" 36 "github.com/aws/aws-sdk-go/service/elasticache" 37 "github.com/aws/aws-sdk-go/service/elasticbeanstalk" 38 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 39 "github.com/aws/aws-sdk-go/service/elastictranscoder" 40 "github.com/aws/aws-sdk-go/service/elb" 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/sns" 54 "github.com/aws/aws-sdk-go/service/sqs" 55 "github.com/aws/aws-sdk-go/service/sts" 56 ) 57 58 type Config struct { 59 AccessKey string 60 SecretKey string 61 CredsFilename string 62 Profile string 63 Token string 64 Region string 65 MaxRetries int 66 67 AllowedAccountIds []interface{} 68 ForbiddenAccountIds []interface{} 69 70 DynamoDBEndpoint string 71 KinesisEndpoint string 72 Ec2Endpoint string 73 IamEndpoint string 74 ElbEndpoint string 75 Insecure bool 76 } 77 78 type AWSClient struct { 79 cfconn *cloudformation.CloudFormation 80 cloudfrontconn *cloudfront.CloudFront 81 cloudtrailconn *cloudtrail.CloudTrail 82 cloudwatchconn *cloudwatch.CloudWatch 83 cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs 84 cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents 85 dsconn *directoryservice.DirectoryService 86 dynamodbconn *dynamodb.DynamoDB 87 ec2conn *ec2.EC2 88 ecrconn *ecr.ECR 89 ecsconn *ecs.ECS 90 efsconn *efs.EFS 91 elbconn *elb.ELB 92 emrconn *emr.EMR 93 esconn *elasticsearch.ElasticsearchService 94 apigateway *apigateway.APIGateway 95 autoscalingconn *autoscaling.AutoScaling 96 s3conn *s3.S3 97 sqsconn *sqs.SQS 98 snsconn *sns.SNS 99 stsconn *sts.STS 100 redshiftconn *redshift.Redshift 101 r53conn *route53.Route53 102 accountid string 103 region string 104 rdsconn *rds.RDS 105 iamconn *iam.IAM 106 kinesisconn *kinesis.Kinesis 107 kmsconn *kms.KMS 108 firehoseconn *firehose.Firehose 109 elasticacheconn *elasticache.ElastiCache 110 elasticbeanstalkconn *elasticbeanstalk.ElasticBeanstalk 111 elastictranscoderconn *elastictranscoder.ElasticTranscoder 112 lambdaconn *lambda.Lambda 113 opsworksconn *opsworks.OpsWorks 114 glacierconn *glacier.Glacier 115 codedeployconn *codedeploy.CodeDeploy 116 codecommitconn *codecommit.CodeCommit 117 } 118 119 // Client configures and returns a fully initialized AWSClient 120 func (c *Config) Client() (interface{}, error) { 121 // Get the auth and region. This can fail if keys/regions were not 122 // specified and we're attempting to use the environment. 123 var errs []error 124 125 log.Println("[INFO] Building AWS region structure") 126 err := c.ValidateRegion() 127 if err != nil { 128 errs = append(errs, err) 129 } 130 131 var client AWSClient 132 if len(errs) == 0 { 133 // store AWS region in client struct, for region specific operations such as 134 // bucket storage in S3 135 client.region = c.Region 136 137 log.Println("[INFO] Building AWS auth structure") 138 creds := GetCredentials(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename) 139 // Call Get to check for credential provider. If nothing found, we'll get an 140 // error, and we can present it nicely to the user 141 cp, err := creds.Get() 142 if err != nil { 143 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 144 errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider. 145 Please see https://terraform.io/docs/providers/aws/index.html for more information on 146 providing credentials for the AWS Provider`)) 147 } else { 148 errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)) 149 } 150 return nil, &multierror.Error{Errors: errs} 151 } 152 153 log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName) 154 155 awsConfig := &aws.Config{ 156 Credentials: creds, 157 Region: aws.String(c.Region), 158 MaxRetries: aws.Int(c.MaxRetries), 159 HTTPClient: cleanhttp.DefaultClient(), 160 } 161 162 if logging.IsDebugOrHigher() { 163 awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody) 164 awsConfig.Logger = awsLogger{} 165 } 166 167 if c.Insecure { 168 transport := awsConfig.HTTPClient.Transport.(*http.Transport) 169 transport.TLSClientConfig = &tls.Config{ 170 InsecureSkipVerify: true, 171 } 172 } 173 174 // Set up base session 175 sess := session.New(awsConfig) 176 sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent) 177 178 log.Println("[INFO] Initializing IAM Connection") 179 awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) 180 client.iamconn = iam.New(awsIamSess) 181 182 log.Println("[INFO] Initializing STS connection") 183 client.stsconn = sts.New(sess) 184 185 err = c.ValidateCredentials(client.iamconn) 186 if err != nil { 187 errs = append(errs, err) 188 return nil, &multierror.Error{Errors: errs} 189 } 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 accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName) 199 if err == nil { 200 client.accountid = accountId 201 } 202 203 log.Println("[INFO] Initializing DynamoDB connection") 204 dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) 205 client.dynamodbconn = dynamodb.New(dynamoSess) 206 207 log.Println("[INFO] Initializing Cloudfront connection") 208 client.cloudfrontconn = cloudfront.New(sess) 209 210 log.Println("[INFO] Initializing ELB connection") 211 awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) 212 client.elbconn = elb.New(awsElbSess) 213 214 log.Println("[INFO] Initializing S3 connection") 215 client.s3conn = s3.New(sess) 216 217 log.Println("[INFO] Initializing SQS connection") 218 client.sqsconn = sqs.New(sess) 219 220 log.Println("[INFO] Initializing SNS connection") 221 client.snsconn = sns.New(sess) 222 223 log.Println("[INFO] Initializing RDS Connection") 224 client.rdsconn = rds.New(sess) 225 226 log.Println("[INFO] Initializing Kinesis Connection") 227 kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) 228 client.kinesisconn = kinesis.New(kinesisSess) 229 230 log.Println("[INFO] Initializing Elastic Beanstalk Connection") 231 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 232 233 log.Println("[INFO] Initializing Elastic Transcoder Connection") 234 client.elastictranscoderconn = elastictranscoder.New(sess) 235 236 authErr := c.ValidateAccountId(client.accountid) 237 if authErr != nil { 238 errs = append(errs, authErr) 239 } 240 241 log.Println("[INFO] Initializing Kinesis Firehose Connection") 242 client.firehoseconn = firehose.New(sess) 243 244 log.Println("[INFO] Initializing AutoScaling connection") 245 client.autoscalingconn = autoscaling.New(sess) 246 247 log.Println("[INFO] Initializing EC2 Connection") 248 249 awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) 250 client.ec2conn = ec2.New(awsEc2Sess) 251 252 log.Println("[INFO] Initializing ECR Connection") 253 client.ecrconn = ecr.New(sess) 254 255 log.Println("[INFO] Initializing API Gateway") 256 client.apigateway = apigateway.New(sess) 257 258 log.Println("[INFO] Initializing ECS Connection") 259 client.ecsconn = ecs.New(sess) 260 261 log.Println("[INFO] Initializing EFS Connection") 262 client.efsconn = efs.New(sess) 263 264 log.Println("[INFO] Initializing ElasticSearch Connection") 265 client.esconn = elasticsearch.New(sess) 266 267 log.Println("[INFO] Initializing EMR Connection") 268 client.emrconn = emr.New(sess) 269 270 log.Println("[INFO] Initializing Route 53 connection") 271 client.r53conn = route53.New(usEast1Sess) 272 273 log.Println("[INFO] Initializing Elasticache Connection") 274 client.elasticacheconn = elasticache.New(sess) 275 276 log.Println("[INFO] Initializing Lambda Connection") 277 client.lambdaconn = lambda.New(sess) 278 279 log.Println("[INFO] Initializing Cloudformation Connection") 280 client.cfconn = cloudformation.New(sess) 281 282 log.Println("[INFO] Initializing CloudWatch SDK connection") 283 client.cloudwatchconn = cloudwatch.New(sess) 284 285 log.Println("[INFO] Initializing CloudWatch Events connection") 286 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 287 288 log.Println("[INFO] Initializing CloudTrail connection") 289 client.cloudtrailconn = cloudtrail.New(sess) 290 291 log.Println("[INFO] Initializing CloudWatch Logs connection") 292 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 293 294 log.Println("[INFO] Initializing OpsWorks Connection") 295 client.opsworksconn = opsworks.New(usEast1Sess) 296 297 log.Println("[INFO] Initializing Directory Service connection") 298 client.dsconn = directoryservice.New(sess) 299 300 log.Println("[INFO] Initializing Glacier connection") 301 client.glacierconn = glacier.New(sess) 302 303 log.Println("[INFO] Initializing CodeDeploy Connection") 304 client.codedeployconn = codedeploy.New(sess) 305 306 log.Println("[INFO] Initializing CodeCommit SDK connection") 307 client.codecommitconn = codecommit.New(usEast1Sess) 308 309 log.Println("[INFO] Initializing Redshift SDK connection") 310 client.redshiftconn = redshift.New(sess) 311 312 log.Println("[INFO] Initializing KMS connection") 313 client.kmsconn = kms.New(sess) 314 } 315 316 if len(errs) > 0 { 317 return nil, &multierror.Error{Errors: errs} 318 } 319 320 return &client, nil 321 } 322 323 // ValidateRegion returns an error if the configured region is not a 324 // valid aws region and nil otherwise. 325 func (c *Config) ValidateRegion() error { 326 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 327 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 328 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 329 330 for _, valid := range regions { 331 if c.Region == valid { 332 return nil 333 } 334 } 335 return fmt.Errorf("Not a valid region: %s", c.Region) 336 } 337 338 // Validate credentials early and fail before we do any graph walking. 339 // In the case of an IAM role/profile with insuffecient privileges, fail 340 // silently 341 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 342 _, err := iamconn.GetUser(nil) 343 344 if awsErr, ok := err.(awserr.Error); ok { 345 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 346 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM role") 347 // User may be an IAM instance profile, or otherwise IAM role without the 348 // GetUser permissions, so fail silently 349 return nil 350 } 351 352 if awsErr.Code() == "SignatureDoesNotMatch" { 353 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 354 } 355 } 356 357 return err 358 } 359 360 // ValidateAccountId returns a context-specific error if the configured account 361 // id is explicitly forbidden or not authorised; and nil if it is authorised. 362 func (c *Config) ValidateAccountId(accountId string) error { 363 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 364 return nil 365 } 366 367 log.Printf("[INFO] Validating account ID") 368 369 if c.ForbiddenAccountIds != nil { 370 for _, id := range c.ForbiddenAccountIds { 371 if id == accountId { 372 return fmt.Errorf("Forbidden account ID (%s)", id) 373 } 374 } 375 } 376 377 if c.AllowedAccountIds != nil { 378 for _, id := range c.AllowedAccountIds { 379 if id == accountId { 380 return nil 381 } 382 } 383 return fmt.Errorf("Account ID not allowed (%s)", accountId) 384 } 385 386 return nil 387 } 388 389 // addTerraformVersionToUserAgent is a named handler that will add Terraform's 390 // version information to requests made by the AWS SDK. 391 var addTerraformVersionToUserAgent = request.NamedHandler{ 392 Name: "terraform.TerraformVersionUserAgentHandler", 393 Fn: request.MakeAddToUserAgentHandler( 394 "terraform", terraform.Version, terraform.VersionPrerelease), 395 } 396 397 type awsLogger struct{} 398 399 func (l awsLogger) Log(args ...interface{}) { 400 tokens := make([]string, 0, len(args)) 401 for _, arg := range args { 402 if token, ok := arg.(string); ok { 403 tokens = append(tokens, token) 404 } 405 } 406 log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) 407 }