github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/aws/config.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "os" 8 "strings" 9 "time" 10 11 "github.com/hashicorp/go-cleanhttp" 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/terraform/helper/logging" 14 "github.com/hashicorp/terraform/terraform" 15 16 "crypto/tls" 17 18 "github.com/aws/aws-sdk-go/aws" 19 "github.com/aws/aws-sdk-go/aws/awserr" 20 awsCredentials "github.com/aws/aws-sdk-go/aws/credentials" 21 "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" 22 "github.com/aws/aws-sdk-go/aws/ec2metadata" 23 "github.com/aws/aws-sdk-go/aws/request" 24 "github.com/aws/aws-sdk-go/aws/session" 25 "github.com/aws/aws-sdk-go/service/apigateway" 26 "github.com/aws/aws-sdk-go/service/autoscaling" 27 "github.com/aws/aws-sdk-go/service/cloudformation" 28 "github.com/aws/aws-sdk-go/service/cloudtrail" 29 "github.com/aws/aws-sdk-go/service/cloudwatch" 30 "github.com/aws/aws-sdk-go/service/cloudwatchevents" 31 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 32 "github.com/aws/aws-sdk-go/service/codecommit" 33 "github.com/aws/aws-sdk-go/service/codedeploy" 34 "github.com/aws/aws-sdk-go/service/directoryservice" 35 "github.com/aws/aws-sdk-go/service/dynamodb" 36 "github.com/aws/aws-sdk-go/service/ec2" 37 "github.com/aws/aws-sdk-go/service/ecr" 38 "github.com/aws/aws-sdk-go/service/ecs" 39 "github.com/aws/aws-sdk-go/service/efs" 40 "github.com/aws/aws-sdk-go/service/elasticache" 41 "github.com/aws/aws-sdk-go/service/elasticbeanstalk" 42 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 43 "github.com/aws/aws-sdk-go/service/elb" 44 "github.com/aws/aws-sdk-go/service/firehose" 45 "github.com/aws/aws-sdk-go/service/glacier" 46 "github.com/aws/aws-sdk-go/service/iam" 47 "github.com/aws/aws-sdk-go/service/kinesis" 48 "github.com/aws/aws-sdk-go/service/kms" 49 "github.com/aws/aws-sdk-go/service/lambda" 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/sns" 56 "github.com/aws/aws-sdk-go/service/sqs" 57 ) 58 59 type Config struct { 60 AccessKey string 61 SecretKey string 62 CredsFilename string 63 Profile string 64 Token string 65 Region string 66 MaxRetries int 67 68 AllowedAccountIds []interface{} 69 ForbiddenAccountIds []interface{} 70 71 DynamoDBEndpoint string 72 KinesisEndpoint string 73 Ec2Endpoint string 74 IamEndpoint string 75 ElbEndpoint string 76 Insecure bool 77 } 78 79 type AWSClient struct { 80 cfconn *cloudformation.CloudFormation 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 esconn *elasticsearch.ElasticsearchService 93 apigateway *apigateway.APIGateway 94 autoscalingconn *autoscaling.AutoScaling 95 s3conn *s3.S3 96 sqsconn *sqs.SQS 97 snsconn *sns.SNS 98 redshiftconn *redshift.Redshift 99 r53conn *route53.Route53 100 region string 101 rdsconn *rds.RDS 102 iamconn *iam.IAM 103 kinesisconn *kinesis.Kinesis 104 kmsconn *kms.KMS 105 firehoseconn *firehose.Firehose 106 elasticacheconn *elasticache.ElastiCache 107 elasticbeanstalkconn *elasticbeanstalk.ElasticBeanstalk 108 lambdaconn *lambda.Lambda 109 opsworksconn *opsworks.OpsWorks 110 glacierconn *glacier.Glacier 111 codedeployconn *codedeploy.CodeDeploy 112 codecommitconn *codecommit.CodeCommit 113 } 114 115 // Client configures and returns a fully initialized AWSClient 116 func (c *Config) Client() (interface{}, error) { 117 // Get the auth and region. This can fail if keys/regions were not 118 // specified and we're attempting to use the environment. 119 var errs []error 120 121 log.Println("[INFO] Building AWS region structure") 122 err := c.ValidateRegion() 123 if err != nil { 124 errs = append(errs, err) 125 } 126 127 var client AWSClient 128 if len(errs) == 0 { 129 // store AWS region in client struct, for region specific operations such as 130 // bucket storage in S3 131 client.region = c.Region 132 133 log.Println("[INFO] Building AWS auth structure") 134 creds := getCreds(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename) 135 // Call Get to check for credential provider. If nothing found, we'll get an 136 // error, and we can present it nicely to the user 137 _, err = creds.Get() 138 if err != nil { 139 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 140 errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider. 141 Please see https://terraform.io/docs/providers/aws/index.html for more information on 142 providing credentials for the AWS Provider`)) 143 } else { 144 errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)) 145 } 146 return nil, &multierror.Error{Errors: errs} 147 } 148 awsConfig := &aws.Config{ 149 Credentials: creds, 150 Region: aws.String(c.Region), 151 MaxRetries: aws.Int(c.MaxRetries), 152 HTTPClient: cleanhttp.DefaultClient(), 153 } 154 155 if logging.IsDebugOrHigher() { 156 awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody) 157 awsConfig.Logger = awsLogger{} 158 } 159 160 if c.Insecure { 161 transport := awsConfig.HTTPClient.Transport.(*http.Transport) 162 transport.TLSClientConfig = &tls.Config{ 163 InsecureSkipVerify: true, 164 } 165 } 166 167 // Set up base session 168 sess := session.New(awsConfig) 169 sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent) 170 171 log.Println("[INFO] Initializing IAM Connection") 172 awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) 173 client.iamconn = iam.New(awsIamSess) 174 175 err = c.ValidateCredentials(client.iamconn) 176 if err != nil { 177 errs = append(errs, err) 178 } 179 180 // Some services exist only in us-east-1, e.g. because they manage 181 // resources that can span across multiple regions, or because 182 // signature format v4 requires region to be us-east-1 for global 183 // endpoints: 184 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 185 usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")}) 186 187 log.Println("[INFO] Initializing DynamoDB connection") 188 dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) 189 client.dynamodbconn = dynamodb.New(dynamoSess) 190 191 log.Println("[INFO] Initializing ELB connection") 192 awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) 193 client.elbconn = elb.New(awsElbSess) 194 195 log.Println("[INFO] Initializing S3 connection") 196 client.s3conn = s3.New(sess) 197 198 log.Println("[INFO] Initializing SQS connection") 199 client.sqsconn = sqs.New(sess) 200 201 log.Println("[INFO] Initializing SNS connection") 202 client.snsconn = sns.New(sess) 203 204 log.Println("[INFO] Initializing RDS Connection") 205 client.rdsconn = rds.New(sess) 206 207 log.Println("[INFO] Initializing Kinesis Connection") 208 kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) 209 client.kinesisconn = kinesis.New(kinesisSess) 210 211 log.Println("[INFO] Initializing Elastic Beanstalk Connection") 212 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 213 214 authErr := c.ValidateAccountId(client.iamconn) 215 if authErr != nil { 216 errs = append(errs, authErr) 217 } 218 219 log.Println("[INFO] Initializing Kinesis Firehose Connection") 220 client.firehoseconn = firehose.New(sess) 221 222 log.Println("[INFO] Initializing AutoScaling connection") 223 client.autoscalingconn = autoscaling.New(sess) 224 225 log.Println("[INFO] Initializing EC2 Connection") 226 227 awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) 228 client.ec2conn = ec2.New(awsEc2Sess) 229 230 log.Println("[INFO] Initializing ECR Connection") 231 client.ecrconn = ecr.New(sess) 232 233 log.Println("[INFO] Initializing API Gateway") 234 client.apigateway = apigateway.New(sess) 235 236 log.Println("[INFO] Initializing ECS Connection") 237 client.ecsconn = ecs.New(sess) 238 239 log.Println("[INFO] Initializing EFS Connection") 240 client.efsconn = efs.New(sess) 241 242 log.Println("[INFO] Initializing ElasticSearch Connection") 243 client.esconn = elasticsearch.New(sess) 244 245 log.Println("[INFO] Initializing Route 53 connection") 246 client.r53conn = route53.New(usEast1Sess) 247 248 log.Println("[INFO] Initializing Elasticache Connection") 249 client.elasticacheconn = elasticache.New(sess) 250 251 log.Println("[INFO] Initializing Lambda Connection") 252 client.lambdaconn = lambda.New(sess) 253 254 log.Println("[INFO] Initializing Cloudformation Connection") 255 client.cfconn = cloudformation.New(sess) 256 257 log.Println("[INFO] Initializing CloudWatch SDK connection") 258 client.cloudwatchconn = cloudwatch.New(sess) 259 260 log.Println("[INFO] Initializing CloudWatch Events connection") 261 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 262 263 log.Println("[INFO] Initializing CloudTrail connection") 264 client.cloudtrailconn = cloudtrail.New(sess) 265 266 log.Println("[INFO] Initializing CloudWatch Logs connection") 267 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 268 269 log.Println("[INFO] Initializing OpsWorks Connection") 270 client.opsworksconn = opsworks.New(usEast1Sess) 271 272 log.Println("[INFO] Initializing Directory Service connection") 273 client.dsconn = directoryservice.New(sess) 274 275 log.Println("[INFO] Initializing Glacier connection") 276 client.glacierconn = glacier.New(sess) 277 278 log.Println("[INFO] Initializing CodeDeploy Connection") 279 client.codedeployconn = codedeploy.New(sess) 280 281 log.Println("[INFO] Initializing CodeCommit SDK connection") 282 client.codecommitconn = codecommit.New(usEast1Sess) 283 284 log.Println("[INFO] Initializing Redshift SDK connection") 285 client.redshiftconn = redshift.New(sess) 286 287 log.Println("[INFO] Initializing KMS connection") 288 client.kmsconn = kms.New(sess) 289 } 290 291 if len(errs) > 0 { 292 return nil, &multierror.Error{Errors: errs} 293 } 294 295 return &client, nil 296 } 297 298 // ValidateRegion returns an error if the configured region is not a 299 // valid aws region and nil otherwise. 300 func (c *Config) ValidateRegion() error { 301 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 302 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 303 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 304 305 for _, valid := range regions { 306 if c.Region == valid { 307 return nil 308 } 309 } 310 return fmt.Errorf("Not a valid region: %s", c.Region) 311 } 312 313 // Validate credentials early and fail before we do any graph walking. 314 // In the case of an IAM role/profile with insuffecient privileges, fail 315 // silently 316 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 317 _, err := iamconn.GetUser(nil) 318 319 if awsErr, ok := err.(awserr.Error); ok { 320 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 321 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile") 322 // User may be an IAM instance profile, or otherwise IAM role without the 323 // GetUser permissions, so fail silently 324 return nil 325 } 326 327 if awsErr.Code() == "SignatureDoesNotMatch" { 328 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 329 } 330 } 331 332 return err 333 } 334 335 // ValidateAccountId returns a context-specific error if the configured account 336 // id is explicitly forbidden or not authorised; and nil if it is authorised. 337 func (c *Config) ValidateAccountId(iamconn *iam.IAM) error { 338 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 339 return nil 340 } 341 342 log.Printf("[INFO] Validating account ID") 343 344 out, err := iamconn.GetUser(nil) 345 346 if err != nil { 347 awsErr, _ := err.(awserr.Error) 348 if awsErr.Code() == "ValidationError" { 349 log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile") 350 // User may be an IAM instance profile, so fail silently. 351 // If it is an IAM instance profile 352 // validating account might be superfluous 353 return nil 354 } else { 355 return fmt.Errorf("Failed getting account ID from IAM: %s", err) 356 // return error if the account id is explicitly not authorised 357 } 358 } 359 360 account_id := strings.Split(*out.User.Arn, ":")[4] 361 362 if c.ForbiddenAccountIds != nil { 363 for _, id := range c.ForbiddenAccountIds { 364 if id == account_id { 365 return fmt.Errorf("Forbidden account ID (%s)", id) 366 } 367 } 368 } 369 370 if c.AllowedAccountIds != nil { 371 for _, id := range c.AllowedAccountIds { 372 if id == account_id { 373 return nil 374 } 375 } 376 return fmt.Errorf("Account ID not allowed (%s)", account_id) 377 } 378 379 return nil 380 } 381 382 // This function is responsible for reading credentials from the 383 // environment in the case that they're not explicitly specified 384 // in the Terraform configuration. 385 func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials { 386 // build a chain provider, lazy-evaulated by aws-sdk 387 providers := []awsCredentials.Provider{ 388 &awsCredentials.StaticProvider{Value: awsCredentials.Value{ 389 AccessKeyID: key, 390 SecretAccessKey: secret, 391 SessionToken: token, 392 }}, 393 &awsCredentials.EnvProvider{}, 394 &awsCredentials.SharedCredentialsProvider{ 395 Filename: credsfile, 396 Profile: profile, 397 }, 398 } 399 400 // We only look in the EC2 metadata API if we can connect 401 // to the metadata service within a reasonable amount of time 402 metadataURL := os.Getenv("AWS_METADATA_URL") 403 if metadataURL == "" { 404 metadataURL = "http://169.254.169.254:80/latest" 405 } 406 c := http.Client{ 407 Timeout: 100 * time.Millisecond, 408 } 409 410 r, err := c.Get(metadataURL) 411 // Flag to determine if we should add the EC2Meta data provider. Default false 412 var useIAM bool 413 if err == nil { 414 // AWS will add a "Server: EC2ws" header value for the metadata request. We 415 // check the headers for this value to ensure something else didn't just 416 // happent to be listening on that IP:Port 417 if r.Header["Server"] != nil && strings.Contains(r.Header["Server"][0], "EC2") { 418 useIAM = true 419 } 420 } 421 422 if useIAM { 423 log.Printf("[DEBUG] EC2 Metadata service found, adding EC2 Role Credential Provider") 424 providers = append(providers, &ec2rolecreds.EC2RoleProvider{ 425 Client: ec2metadata.New(session.New(&aws.Config{ 426 Endpoint: aws.String(metadataURL), 427 })), 428 }) 429 } else { 430 log.Printf("[DEBUG] EC2 Metadata service not found, not adding EC2 Role Credential Provider") 431 } 432 return awsCredentials.NewChainCredentials(providers) 433 } 434 435 // addTerraformVersionToUserAgent is a named handler that will add Terraform's 436 // version information to requests made by the AWS SDK. 437 var addTerraformVersionToUserAgent = request.NamedHandler{ 438 Name: "terraform.TerraformVersionUserAgentHandler", 439 Fn: request.MakeAddToUserAgentHandler( 440 "terraform", terraform.Version, terraform.VersionPrerelease), 441 } 442 443 type awsLogger struct{} 444 445 func (l awsLogger) Log(args ...interface{}) { 446 tokens := make([]string, 0, len(args)) 447 for _, arg := range args { 448 if token, ok := arg.(string); ok { 449 tokens = append(tokens, token) 450 } 451 } 452 log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) 453 }