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