github.com/xsb/terraform@v0.6.13-0.20160314145438-fe415c2f09d7/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 14 "crypto/tls" 15 16 "github.com/aws/aws-sdk-go/aws" 17 "github.com/aws/aws-sdk-go/aws/awserr" 18 awsCredentials "github.com/aws/aws-sdk-go/aws/credentials" 19 "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" 20 "github.com/aws/aws-sdk-go/aws/ec2metadata" 21 "github.com/aws/aws-sdk-go/aws/session" 22 "github.com/aws/aws-sdk-go/service/apigateway" 23 "github.com/aws/aws-sdk-go/service/autoscaling" 24 "github.com/aws/aws-sdk-go/service/cloudformation" 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/elb" 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 ) 55 56 type Config struct { 57 AccessKey string 58 SecretKey string 59 CredsFilename string 60 Profile string 61 Token string 62 Region string 63 MaxRetries int 64 65 AllowedAccountIds []interface{} 66 ForbiddenAccountIds []interface{} 67 68 DynamoDBEndpoint string 69 KinesisEndpoint string 70 Ec2Endpoint string 71 IamEndpoint string 72 ElbEndpoint string 73 Insecure bool 74 } 75 76 type AWSClient struct { 77 cfconn *cloudformation.CloudFormation 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 := getCreds(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 _, 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 awsConfig := &aws.Config{ 146 Credentials: creds, 147 Region: aws.String(c.Region), 148 MaxRetries: aws.Int(c.MaxRetries), 149 HTTPClient: cleanhttp.DefaultClient(), 150 } 151 152 if c.Insecure { 153 transport := awsConfig.HTTPClient.Transport.(*http.Transport) 154 transport.TLSClientConfig = &tls.Config{ 155 InsecureSkipVerify: true, 156 } 157 } 158 159 log.Println("[INFO] Initializing IAM Connection") 160 sess := session.New(awsConfig) 161 162 awsIamConfig := *awsConfig 163 awsIamConfig.Endpoint = aws.String(c.IamEndpoint) 164 165 awsIamSess := session.New(&awsIamConfig) 166 client.iamconn = iam.New(awsIamSess) 167 168 err = c.ValidateCredentials(client.iamconn) 169 if err != nil { 170 errs = append(errs, err) 171 } 172 173 // Some services exist only in us-east-1, e.g. because they manage 174 // resources that can span across multiple regions, or because 175 // signature format v4 requires region to be us-east-1 for global 176 // endpoints: 177 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 178 usEast1AwsConfig := &aws.Config{ 179 Credentials: creds, 180 Region: aws.String("us-east-1"), 181 MaxRetries: aws.Int(c.MaxRetries), 182 HTTPClient: cleanhttp.DefaultClient(), 183 } 184 usEast1Sess := session.New(usEast1AwsConfig) 185 186 awsDynamoDBConfig := *awsConfig 187 awsDynamoDBConfig.Endpoint = aws.String(c.DynamoDBEndpoint) 188 189 log.Println("[INFO] Initializing DynamoDB connection") 190 dynamoSess := session.New(&awsDynamoDBConfig) 191 client.dynamodbconn = dynamodb.New(dynamoSess) 192 193 log.Println("[INFO] Initializing ELB connection") 194 awsElbConfig := *awsConfig 195 awsElbConfig.Endpoint = aws.String(c.ElbEndpoint) 196 197 awsElbSess := session.New(&awsElbConfig) 198 199 client.elbconn = elb.New(awsElbSess) 200 201 log.Println("[INFO] Initializing S3 connection") 202 client.s3conn = s3.New(sess) 203 204 log.Println("[INFO] Initializing SQS connection") 205 client.sqsconn = sqs.New(sess) 206 207 log.Println("[INFO] Initializing SNS connection") 208 client.snsconn = sns.New(sess) 209 210 log.Println("[INFO] Initializing RDS Connection") 211 client.rdsconn = rds.New(sess) 212 213 awsKinesisConfig := *awsConfig 214 awsKinesisConfig.Endpoint = aws.String(c.KinesisEndpoint) 215 216 log.Println("[INFO] Initializing Kinesis Connection") 217 kinesisSess := session.New(&awsKinesisConfig) 218 client.kinesisconn = kinesis.New(kinesisSess) 219 220 log.Println("[INFO] Initializing Elastic Beanstalk Connection") 221 client.elasticbeanstalkconn = elasticbeanstalk.New(sess) 222 223 authErr := c.ValidateAccountId(client.iamconn) 224 if authErr != nil { 225 errs = append(errs, authErr) 226 } 227 228 log.Println("[INFO] Initializing Kinesis Firehose Connection") 229 client.firehoseconn = firehose.New(sess) 230 231 log.Println("[INFO] Initializing AutoScaling connection") 232 client.autoscalingconn = autoscaling.New(sess) 233 234 log.Println("[INFO] Initializing EC2 Connection") 235 236 awsEc2Config := *awsConfig 237 awsEc2Config.Endpoint = aws.String(c.Ec2Endpoint) 238 239 awsEc2Sess := session.New(&awsEc2Config) 240 client.ec2conn = ec2.New(awsEc2Sess) 241 242 log.Println("[INFO] Initializing ECR Connection") 243 client.ecrconn = ecr.New(sess) 244 245 log.Println("[INFO] Initializing API Gateway") 246 client.apigateway = apigateway.New(sess) 247 248 log.Println("[INFO] Initializing ECS Connection") 249 client.ecsconn = ecs.New(sess) 250 251 log.Println("[INFO] Initializing EFS Connection") 252 client.efsconn = efs.New(sess) 253 254 log.Println("[INFO] Initializing ElasticSearch Connection") 255 client.esconn = elasticsearch.New(sess) 256 257 log.Println("[INFO] Initializing Route 53 connection") 258 client.r53conn = route53.New(usEast1Sess) 259 260 log.Println("[INFO] Initializing Elasticache Connection") 261 client.elasticacheconn = elasticache.New(sess) 262 263 log.Println("[INFO] Initializing Lambda Connection") 264 client.lambdaconn = lambda.New(sess) 265 266 log.Println("[INFO] Initializing Cloudformation Connection") 267 client.cfconn = cloudformation.New(sess) 268 269 log.Println("[INFO] Initializing CloudWatch SDK connection") 270 client.cloudwatchconn = cloudwatch.New(sess) 271 272 log.Println("[INFO] Initializing CloudWatch Events connection") 273 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 274 275 log.Println("[INFO] Initializing CloudTrail connection") 276 client.cloudtrailconn = cloudtrail.New(sess) 277 278 log.Println("[INFO] Initializing CloudWatch Logs connection") 279 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 280 281 log.Println("[INFO] Initializing OpsWorks Connection") 282 client.opsworksconn = opsworks.New(usEast1Sess) 283 284 log.Println("[INFO] Initializing Directory Service connection") 285 client.dsconn = directoryservice.New(sess) 286 287 log.Println("[INFO] Initializing Glacier connection") 288 client.glacierconn = glacier.New(sess) 289 290 log.Println("[INFO] Initializing CodeDeploy Connection") 291 client.codedeployconn = codedeploy.New(sess) 292 293 log.Println("[INFO] Initializing CodeCommit SDK connection") 294 client.codecommitconn = codecommit.New(usEast1Sess) 295 296 log.Println("[INFO] Initializing Redshift SDK connection") 297 client.redshiftconn = redshift.New(sess) 298 299 log.Println("[INFO] Initializing KMS connection") 300 client.kmsconn = kms.New(sess) 301 } 302 303 if len(errs) > 0 { 304 return nil, &multierror.Error{Errors: errs} 305 } 306 307 return &client, nil 308 } 309 310 // ValidateRegion returns an error if the configured region is not a 311 // valid aws region and nil otherwise. 312 func (c *Config) ValidateRegion() error { 313 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 314 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 315 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 316 317 for _, valid := range regions { 318 if c.Region == valid { 319 return nil 320 } 321 } 322 return fmt.Errorf("Not a valid region: %s", c.Region) 323 } 324 325 // Validate credentials early and fail before we do any graph walking. 326 // In the case of an IAM role/profile with insuffecient privileges, fail 327 // silently 328 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 329 _, err := iamconn.GetUser(nil) 330 331 if awsErr, ok := err.(awserr.Error); ok { 332 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 333 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile") 334 // User may be an IAM instance profile, or otherwise IAM role without the 335 // GetUser permissions, so fail silently 336 return nil 337 } 338 339 if awsErr.Code() == "SignatureDoesNotMatch" { 340 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 341 } 342 } 343 344 return err 345 } 346 347 // ValidateAccountId returns a context-specific error if the configured account 348 // id is explicitly forbidden or not authorised; and nil if it is authorised. 349 func (c *Config) ValidateAccountId(iamconn *iam.IAM) error { 350 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 351 return nil 352 } 353 354 log.Printf("[INFO] Validating account ID") 355 356 out, err := iamconn.GetUser(nil) 357 358 if err != nil { 359 awsErr, _ := err.(awserr.Error) 360 if awsErr.Code() == "ValidationError" { 361 log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile") 362 // User may be an IAM instance profile, so fail silently. 363 // If it is an IAM instance profile 364 // validating account might be superfluous 365 return nil 366 } else { 367 return fmt.Errorf("Failed getting account ID from IAM: %s", err) 368 // return error if the account id is explicitly not authorised 369 } 370 } 371 372 account_id := strings.Split(*out.User.Arn, ":")[4] 373 374 if c.ForbiddenAccountIds != nil { 375 for _, id := range c.ForbiddenAccountIds { 376 if id == account_id { 377 return fmt.Errorf("Forbidden account ID (%s)", id) 378 } 379 } 380 } 381 382 if c.AllowedAccountIds != nil { 383 for _, id := range c.AllowedAccountIds { 384 if id == account_id { 385 return nil 386 } 387 } 388 return fmt.Errorf("Account ID not allowed (%s)", account_id) 389 } 390 391 return nil 392 } 393 394 // This function is responsible for reading credentials from the 395 // environment in the case that they're not explicitly specified 396 // in the Terraform configuration. 397 func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials { 398 // build a chain provider, lazy-evaulated by aws-sdk 399 providers := []awsCredentials.Provider{ 400 &awsCredentials.StaticProvider{Value: awsCredentials.Value{ 401 AccessKeyID: key, 402 SecretAccessKey: secret, 403 SessionToken: token, 404 }}, 405 &awsCredentials.EnvProvider{}, 406 &awsCredentials.SharedCredentialsProvider{ 407 Filename: credsfile, 408 Profile: profile, 409 }, 410 } 411 412 // We only look in the EC2 metadata API if we can connect 413 // to the metadata service within a reasonable amount of time 414 metadataURL := os.Getenv("AWS_METADATA_URL") 415 if metadataURL == "" { 416 metadataURL = "http://169.254.169.254:80/latest" 417 } 418 c := http.Client{ 419 Timeout: 100 * time.Millisecond, 420 } 421 422 r, err := c.Get(metadataURL) 423 // Flag to determine if we should add the EC2Meta data provider. Default false 424 var useIAM bool 425 if err == nil { 426 // AWS will add a "Server: EC2ws" header value for the metadata request. We 427 // check the headers for this value to ensure something else didn't just 428 // happent to be listening on that IP:Port 429 if r.Header["Server"] != nil && strings.Contains(r.Header["Server"][0], "EC2") { 430 useIAM = true 431 } 432 } 433 434 if useIAM { 435 log.Printf("[DEBUG] EC2 Metadata service found, adding EC2 Role Credential Provider") 436 providers = append(providers, &ec2rolecreds.EC2RoleProvider{ 437 Client: ec2metadata.New(session.New(&aws.Config{ 438 Endpoint: aws.String(metadataURL), 439 })), 440 }) 441 } else { 442 log.Printf("[DEBUG] EC2 Metadata service not found, not adding EC2 Role Credential Provider") 443 } 444 return awsCredentials.NewChainCredentials(providers) 445 }