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