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