github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/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/autoscaling" 23 "github.com/aws/aws-sdk-go/service/cloudformation" 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 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 38 "github.com/aws/aws-sdk-go/service/elb" 39 "github.com/aws/aws-sdk-go/service/firehose" 40 "github.com/aws/aws-sdk-go/service/glacier" 41 "github.com/aws/aws-sdk-go/service/iam" 42 "github.com/aws/aws-sdk-go/service/kinesis" 43 "github.com/aws/aws-sdk-go/service/lambda" 44 "github.com/aws/aws-sdk-go/service/opsworks" 45 "github.com/aws/aws-sdk-go/service/rds" 46 "github.com/aws/aws-sdk-go/service/redshift" 47 "github.com/aws/aws-sdk-go/service/route53" 48 "github.com/aws/aws-sdk-go/service/s3" 49 "github.com/aws/aws-sdk-go/service/sns" 50 "github.com/aws/aws-sdk-go/service/sqs" 51 ) 52 53 type Config struct { 54 AccessKey string 55 SecretKey string 56 CredsFilename string 57 Profile string 58 Token string 59 Region string 60 MaxRetries int 61 62 AllowedAccountIds []interface{} 63 ForbiddenAccountIds []interface{} 64 65 DynamoDBEndpoint string 66 KinesisEndpoint string 67 Ec2Endpoint string 68 IamEndpoint string 69 ElbEndpoint string 70 Insecure bool 71 } 72 73 type AWSClient struct { 74 cfconn *cloudformation.CloudFormation 75 cloudtrailconn *cloudtrail.CloudTrail 76 cloudwatchconn *cloudwatch.CloudWatch 77 cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs 78 cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents 79 dsconn *directoryservice.DirectoryService 80 dynamodbconn *dynamodb.DynamoDB 81 ec2conn *ec2.EC2 82 ecrconn *ecr.ECR 83 ecsconn *ecs.ECS 84 efsconn *efs.EFS 85 elbconn *elb.ELB 86 esconn *elasticsearch.ElasticsearchService 87 autoscalingconn *autoscaling.AutoScaling 88 s3conn *s3.S3 89 sqsconn *sqs.SQS 90 snsconn *sns.SNS 91 redshiftconn *redshift.Redshift 92 r53conn *route53.Route53 93 region string 94 rdsconn *rds.RDS 95 iamconn *iam.IAM 96 kinesisconn *kinesis.Kinesis 97 firehoseconn *firehose.Firehose 98 elasticacheconn *elasticache.ElastiCache 99 lambdaconn *lambda.Lambda 100 opsworksconn *opsworks.OpsWorks 101 glacierconn *glacier.Glacier 102 codedeployconn *codedeploy.CodeDeploy 103 codecommitconn *codecommit.CodeCommit 104 } 105 106 // Client configures and returns a fully initialized AWSClient 107 func (c *Config) Client() (interface{}, error) { 108 var client AWSClient 109 110 // Get the auth and region. This can fail if keys/regions were not 111 // specified and we're attempting to use the environment. 112 var errs []error 113 114 log.Println("[INFO] Building AWS region structure") 115 err := c.ValidateRegion() 116 if err != nil { 117 errs = append(errs, err) 118 } 119 120 if len(errs) == 0 { 121 // store AWS region in client struct, for region specific operations such as 122 // bucket storage in S3 123 client.region = c.Region 124 125 log.Println("[INFO] Building AWS auth structure") 126 creds := getCreds(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename) 127 // Call Get to check for credential provider. If nothing found, we'll get an 128 // error, and we can present it nicely to the user 129 _, err = creds.Get() 130 if err != nil { 131 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 132 errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider. 133 Please see https://terraform.io/docs/providers/aws/index.html for more information on 134 providing credentials for the AWS Provider`)) 135 } else { 136 errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)) 137 } 138 return nil, &multierror.Error{Errors: errs} 139 } 140 awsConfig := &aws.Config{ 141 Credentials: creds, 142 Region: aws.String(c.Region), 143 MaxRetries: aws.Int(c.MaxRetries), 144 HTTPClient: cleanhttp.DefaultClient(), 145 } 146 147 if c.Insecure { 148 transport := awsConfig.HTTPClient.Transport.(*http.Transport) 149 transport.TLSClientConfig = &tls.Config{ 150 InsecureSkipVerify: true, 151 } 152 } 153 154 log.Println("[INFO] Initializing IAM Connection") 155 sess := session.New(awsConfig) 156 157 awsIamConfig := *awsConfig 158 awsIamConfig.Endpoint = aws.String(c.IamEndpoint) 159 160 awsIamSess := session.New(&awsIamConfig) 161 client.iamconn = iam.New(awsIamSess) 162 163 err = c.ValidateCredentials(client.iamconn) 164 if err != nil { 165 errs = append(errs, err) 166 } 167 168 // Some services exist only in us-east-1, e.g. because they manage 169 // resources that can span across multiple regions, or because 170 // signature format v4 requires region to be us-east-1 for global 171 // endpoints: 172 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 173 usEast1AwsConfig := &aws.Config{ 174 Credentials: creds, 175 Region: aws.String("us-east-1"), 176 MaxRetries: aws.Int(c.MaxRetries), 177 HTTPClient: cleanhttp.DefaultClient(), 178 } 179 usEast1Sess := session.New(usEast1AwsConfig) 180 181 awsDynamoDBConfig := *awsConfig 182 awsDynamoDBConfig.Endpoint = aws.String(c.DynamoDBEndpoint) 183 184 log.Println("[INFO] Initializing DynamoDB connection") 185 dynamoSess := session.New(&awsDynamoDBConfig) 186 client.dynamodbconn = dynamodb.New(dynamoSess) 187 188 log.Println("[INFO] Initializing ELB connection") 189 awsElbConfig := *awsConfig 190 awsElbConfig.Endpoint = aws.String(c.ElbEndpoint) 191 192 awsElbSess := session.New(&awsElbConfig) 193 194 client.elbconn = elb.New(awsElbSess) 195 196 log.Println("[INFO] Initializing S3 connection") 197 client.s3conn = s3.New(sess) 198 199 log.Println("[INFO] Initializing SQS connection") 200 client.sqsconn = sqs.New(sess) 201 202 log.Println("[INFO] Initializing SNS connection") 203 client.snsconn = sns.New(sess) 204 205 log.Println("[INFO] Initializing RDS Connection") 206 client.rdsconn = rds.New(sess) 207 208 awsKinesisConfig := *awsConfig 209 awsKinesisConfig.Endpoint = aws.String(c.KinesisEndpoint) 210 211 log.Println("[INFO] Initializing Kinesis Connection") 212 kinesisSess := session.New(&awsKinesisConfig) 213 client.kinesisconn = kinesis.New(kinesisSess) 214 215 authErr := c.ValidateAccountId(client.iamconn) 216 if authErr != nil { 217 errs = append(errs, authErr) 218 } 219 220 log.Println("[INFO] Initializing Kinesis Firehose Connection") 221 client.firehoseconn = firehose.New(sess) 222 223 log.Println("[INFO] Initializing AutoScaling connection") 224 client.autoscalingconn = autoscaling.New(sess) 225 226 log.Println("[INFO] Initializing EC2 Connection") 227 228 awsEc2Config := *awsConfig 229 awsEc2Config.Endpoint = aws.String(c.Ec2Endpoint) 230 231 awsEc2Sess := session.New(&awsEc2Config) 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 ECS Connection") 238 client.ecsconn = ecs.New(sess) 239 240 log.Println("[INFO] Initializing EFS Connection") 241 client.efsconn = efs.New(sess) 242 243 log.Println("[INFO] Initializing ElasticSearch Connection") 244 client.esconn = elasticsearch.New(sess) 245 246 log.Println("[INFO] Initializing Route 53 connection") 247 client.r53conn = route53.New(usEast1Sess) 248 249 log.Println("[INFO] Initializing Elasticache Connection") 250 client.elasticacheconn = elasticache.New(sess) 251 252 log.Println("[INFO] Initializing Lambda Connection") 253 client.lambdaconn = lambda.New(sess) 254 255 log.Println("[INFO] Initializing Cloudformation Connection") 256 client.cfconn = cloudformation.New(sess) 257 258 log.Println("[INFO] Initializing CloudWatch SDK connection") 259 client.cloudwatchconn = cloudwatch.New(sess) 260 261 log.Println("[INFO] Initializing CloudWatch Events connection") 262 client.cloudwatcheventsconn = cloudwatchevents.New(sess) 263 264 log.Println("[INFO] Initializing CloudTrail connection") 265 client.cloudtrailconn = cloudtrail.New(sess) 266 267 log.Println("[INFO] Initializing CloudWatch Logs connection") 268 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 269 270 log.Println("[INFO] Initializing OpsWorks Connection") 271 client.opsworksconn = opsworks.New(usEast1Sess) 272 273 log.Println("[INFO] Initializing Directory Service connection") 274 client.dsconn = directoryservice.New(sess) 275 276 log.Println("[INFO] Initializing Glacier connection") 277 client.glacierconn = glacier.New(sess) 278 279 log.Println("[INFO] Initializing CodeDeploy Connection") 280 client.codedeployconn = codedeploy.New(sess) 281 282 log.Println("[INFO] Initializing CodeCommit SDK connection") 283 client.codecommitconn = codecommit.New(usEast1Sess) 284 285 log.Println("[INFO] Initializing Redshift SDK connection") 286 client.redshiftconn = redshift.New(sess) 287 288 } 289 290 if len(errs) > 0 { 291 return nil, &multierror.Error{Errors: errs} 292 } 293 294 return &client, nil 295 } 296 297 // ValidateRegion returns an error if the configured region is not a 298 // valid aws region and nil otherwise. 299 func (c *Config) ValidateRegion() error { 300 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 301 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 302 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 303 304 for _, valid := range regions { 305 if c.Region == valid { 306 return nil 307 } 308 } 309 return fmt.Errorf("Not a valid region: %s", c.Region) 310 } 311 312 // Validate credentials early and fail before we do any graph walking. 313 // In the case of an IAM role/profile with insuffecient privileges, fail 314 // silently 315 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 316 _, err := iamconn.GetUser(nil) 317 318 if awsErr, ok := err.(awserr.Error); ok { 319 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 }