github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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 "github.com/aws/aws-sdk-go/aws" 15 "github.com/aws/aws-sdk-go/aws/awserr" 16 awsCredentials "github.com/aws/aws-sdk-go/aws/credentials" 17 "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" 18 "github.com/aws/aws-sdk-go/aws/ec2metadata" 19 "github.com/aws/aws-sdk-go/aws/session" 20 "github.com/aws/aws-sdk-go/service/autoscaling" 21 "github.com/aws/aws-sdk-go/service/cloudformation" 22 "github.com/aws/aws-sdk-go/service/cloudtrail" 23 "github.com/aws/aws-sdk-go/service/cloudwatch" 24 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 25 "github.com/aws/aws-sdk-go/service/codecommit" 26 "github.com/aws/aws-sdk-go/service/codedeploy" 27 "github.com/aws/aws-sdk-go/service/directoryservice" 28 "github.com/aws/aws-sdk-go/service/dynamodb" 29 "github.com/aws/aws-sdk-go/service/ec2" 30 "github.com/aws/aws-sdk-go/service/ecr" 31 "github.com/aws/aws-sdk-go/service/ecs" 32 "github.com/aws/aws-sdk-go/service/efs" 33 "github.com/aws/aws-sdk-go/service/elasticache" 34 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 35 "github.com/aws/aws-sdk-go/service/elb" 36 "github.com/aws/aws-sdk-go/service/firehose" 37 "github.com/aws/aws-sdk-go/service/glacier" 38 "github.com/aws/aws-sdk-go/service/iam" 39 "github.com/aws/aws-sdk-go/service/kinesis" 40 "github.com/aws/aws-sdk-go/service/lambda" 41 "github.com/aws/aws-sdk-go/service/opsworks" 42 "github.com/aws/aws-sdk-go/service/rds" 43 "github.com/aws/aws-sdk-go/service/redshift" 44 "github.com/aws/aws-sdk-go/service/route53" 45 "github.com/aws/aws-sdk-go/service/s3" 46 "github.com/aws/aws-sdk-go/service/sns" 47 "github.com/aws/aws-sdk-go/service/sqs" 48 ) 49 50 type Config struct { 51 AccessKey string 52 SecretKey string 53 CredsFilename string 54 Profile string 55 Token string 56 Region string 57 MaxRetries int 58 59 AllowedAccountIds []interface{} 60 ForbiddenAccountIds []interface{} 61 62 DynamoDBEndpoint string 63 KinesisEndpoint string 64 } 65 66 type AWSClient struct { 67 cfconn *cloudformation.CloudFormation 68 cloudtrailconn *cloudtrail.CloudTrail 69 cloudwatchconn *cloudwatch.CloudWatch 70 cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs 71 dsconn *directoryservice.DirectoryService 72 dynamodbconn *dynamodb.DynamoDB 73 ec2conn *ec2.EC2 74 ecrconn *ecr.ECR 75 ecsconn *ecs.ECS 76 efsconn *efs.EFS 77 elbconn *elb.ELB 78 esconn *elasticsearch.ElasticsearchService 79 autoscalingconn *autoscaling.AutoScaling 80 s3conn *s3.S3 81 sqsconn *sqs.SQS 82 snsconn *sns.SNS 83 redshiftconn *redshift.Redshift 84 r53conn *route53.Route53 85 region string 86 rdsconn *rds.RDS 87 iamconn *iam.IAM 88 kinesisconn *kinesis.Kinesis 89 firehoseconn *firehose.Firehose 90 elasticacheconn *elasticache.ElastiCache 91 lambdaconn *lambda.Lambda 92 opsworksconn *opsworks.OpsWorks 93 glacierconn *glacier.Glacier 94 codedeployconn *codedeploy.CodeDeploy 95 codecommitconn *codecommit.CodeCommit 96 } 97 98 // Client configures and returns a fully initialized AWSClient 99 func (c *Config) Client() (interface{}, error) { 100 var client AWSClient 101 102 // Get the auth and region. This can fail if keys/regions were not 103 // specified and we're attempting to use the environment. 104 var errs []error 105 106 log.Println("[INFO] Building AWS region structure") 107 err := c.ValidateRegion() 108 if err != nil { 109 errs = append(errs, err) 110 } 111 112 if len(errs) == 0 { 113 // store AWS region in client struct, for region specific operations such as 114 // bucket storage in S3 115 client.region = c.Region 116 117 log.Println("[INFO] Building AWS auth structure") 118 creds := getCreds(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename) 119 // Call Get to check for credential provider. If nothing found, we'll get an 120 // error, and we can present it nicely to the user 121 _, err = creds.Get() 122 if err != nil { 123 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 124 errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider. 125 Please see https://terraform.io/docs/providers/aws/index.html for more information on 126 providing credentials for the AWS Provider`)) 127 } else { 128 errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)) 129 } 130 return nil, &multierror.Error{Errors: errs} 131 } 132 awsConfig := &aws.Config{ 133 Credentials: creds, 134 Region: aws.String(c.Region), 135 MaxRetries: aws.Int(c.MaxRetries), 136 HTTPClient: cleanhttp.DefaultClient(), 137 } 138 139 log.Println("[INFO] Initializing IAM Connection") 140 sess := session.New(awsConfig) 141 client.iamconn = iam.New(sess) 142 143 err = c.ValidateCredentials(client.iamconn) 144 if err != nil { 145 errs = append(errs, err) 146 } 147 148 // Some services exist only in us-east-1, e.g. because they manage 149 // resources that can span across multiple regions, or because 150 // signature format v4 requires region to be us-east-1 for global 151 // endpoints: 152 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 153 usEast1AwsConfig := &aws.Config{ 154 Credentials: creds, 155 Region: aws.String("us-east-1"), 156 MaxRetries: aws.Int(c.MaxRetries), 157 HTTPClient: cleanhttp.DefaultClient(), 158 } 159 usEast1Sess := session.New(usEast1AwsConfig) 160 161 awsDynamoDBConfig := *awsConfig 162 awsDynamoDBConfig.Endpoint = aws.String(c.DynamoDBEndpoint) 163 164 log.Println("[INFO] Initializing DynamoDB connection") 165 dynamoSess := session.New(&awsDynamoDBConfig) 166 client.dynamodbconn = dynamodb.New(dynamoSess) 167 168 log.Println("[INFO] Initializing ELB connection") 169 client.elbconn = elb.New(sess) 170 171 log.Println("[INFO] Initializing S3 connection") 172 client.s3conn = s3.New(sess) 173 174 log.Println("[INFO] Initializing SQS connection") 175 client.sqsconn = sqs.New(sess) 176 177 log.Println("[INFO] Initializing SNS connection") 178 client.snsconn = sns.New(sess) 179 180 log.Println("[INFO] Initializing RDS Connection") 181 client.rdsconn = rds.New(sess) 182 183 awsKinesisConfig := *awsConfig 184 awsKinesisConfig.Endpoint = aws.String(c.KinesisEndpoint) 185 186 log.Println("[INFO] Initializing Kinesis Connection") 187 kinesisSess := session.New(&awsKinesisConfig) 188 client.kinesisconn = kinesis.New(kinesisSess) 189 190 authErr := c.ValidateAccountId(client.iamconn) 191 if authErr != nil { 192 errs = append(errs, authErr) 193 } 194 195 log.Println("[INFO] Initializing Kinesis Firehose Connection") 196 client.firehoseconn = firehose.New(sess) 197 198 log.Println("[INFO] Initializing AutoScaling connection") 199 client.autoscalingconn = autoscaling.New(sess) 200 201 log.Println("[INFO] Initializing EC2 Connection") 202 client.ec2conn = ec2.New(sess) 203 204 log.Println("[INFO] Initializing ECR Connection") 205 client.ecrconn = ecr.New(sess) 206 207 log.Println("[INFO] Initializing ECS Connection") 208 client.ecsconn = ecs.New(sess) 209 210 log.Println("[INFO] Initializing EFS Connection") 211 client.efsconn = efs.New(sess) 212 213 log.Println("[INFO] Initializing ElasticSearch Connection") 214 client.esconn = elasticsearch.New(sess) 215 216 log.Println("[INFO] Initializing Route 53 connection") 217 client.r53conn = route53.New(usEast1Sess) 218 219 log.Println("[INFO] Initializing Elasticache Connection") 220 client.elasticacheconn = elasticache.New(sess) 221 222 log.Println("[INFO] Initializing Lambda Connection") 223 client.lambdaconn = lambda.New(sess) 224 225 log.Println("[INFO] Initializing Cloudformation Connection") 226 client.cfconn = cloudformation.New(sess) 227 228 log.Println("[INFO] Initializing CloudWatch SDK connection") 229 client.cloudwatchconn = cloudwatch.New(sess) 230 231 log.Println("[INFO] Initializing CloudTrail connection") 232 client.cloudtrailconn = cloudtrail.New(sess) 233 234 log.Println("[INFO] Initializing CloudWatch Logs connection") 235 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 236 237 log.Println("[INFO] Initializing OpsWorks Connection") 238 client.opsworksconn = opsworks.New(usEast1Sess) 239 240 log.Println("[INFO] Initializing Directory Service connection") 241 client.dsconn = directoryservice.New(sess) 242 243 log.Println("[INFO] Initializing Glacier connection") 244 client.glacierconn = glacier.New(sess) 245 246 log.Println("[INFO] Initializing CodeDeploy Connection") 247 client.codedeployconn = codedeploy.New(sess) 248 249 log.Println("[INFO] Initializing CodeCommit SDK connection") 250 client.codecommitconn = codecommit.New(usEast1Sess) 251 252 log.Println("[INFO] Initializing Redshift SDK connection") 253 client.redshiftconn = redshift.New(sess) 254 255 } 256 257 if len(errs) > 0 { 258 return nil, &multierror.Error{Errors: errs} 259 } 260 261 return &client, nil 262 } 263 264 // ValidateRegion returns an error if the configured region is not a 265 // valid aws region and nil otherwise. 266 func (c *Config) ValidateRegion() error { 267 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 268 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 269 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 270 271 for _, valid := range regions { 272 if c.Region == valid { 273 return nil 274 } 275 } 276 return fmt.Errorf("Not a valid region: %s", c.Region) 277 } 278 279 // Validate credentials early and fail before we do any graph walking. 280 // In the case of an IAM role/profile with insuffecient privileges, fail 281 // silently 282 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 283 _, err := iamconn.GetUser(nil) 284 285 if awsErr, ok := err.(awserr.Error); ok { 286 287 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 288 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile") 289 // User may be an IAM instance profile, or otherwise IAM role without the 290 // GetUser permissions, so fail silently 291 return nil 292 } 293 294 if awsErr.Code() == "SignatureDoesNotMatch" { 295 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 296 } 297 } 298 299 return err 300 } 301 302 // ValidateAccountId returns a context-specific error if the configured account 303 // id is explicitly forbidden or not authorised; and nil if it is authorised. 304 func (c *Config) ValidateAccountId(iamconn *iam.IAM) error { 305 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 306 return nil 307 } 308 309 log.Printf("[INFO] Validating account ID") 310 311 out, err := iamconn.GetUser(nil) 312 313 if err != nil { 314 awsErr, _ := err.(awserr.Error) 315 if awsErr.Code() == "ValidationError" { 316 log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile") 317 // User may be an IAM instance profile, so fail silently. 318 // If it is an IAM instance profile 319 // validating account might be superfluous 320 return nil 321 } else { 322 return fmt.Errorf("Failed getting account ID from IAM: %s", err) 323 // return error if the account id is explicitly not authorised 324 } 325 } 326 327 account_id := strings.Split(*out.User.Arn, ":")[4] 328 329 if c.ForbiddenAccountIds != nil { 330 for _, id := range c.ForbiddenAccountIds { 331 if id == account_id { 332 return fmt.Errorf("Forbidden account ID (%s)", id) 333 } 334 } 335 } 336 337 if c.AllowedAccountIds != nil { 338 for _, id := range c.AllowedAccountIds { 339 if id == account_id { 340 return nil 341 } 342 } 343 return fmt.Errorf("Account ID not allowed (%s)", account_id) 344 } 345 346 return nil 347 } 348 349 // This function is responsible for reading credentials from the 350 // environment in the case that they're not explicitly specified 351 // in the Terraform configuration. 352 func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials { 353 // build a chain provider, lazy-evaulated by aws-sdk 354 providers := []awsCredentials.Provider{ 355 &awsCredentials.StaticProvider{Value: awsCredentials.Value{ 356 AccessKeyID: key, 357 SecretAccessKey: secret, 358 SessionToken: token, 359 }}, 360 &awsCredentials.EnvProvider{}, 361 &awsCredentials.SharedCredentialsProvider{ 362 Filename: credsfile, 363 Profile: profile, 364 }, 365 } 366 367 // We only look in the EC2 metadata API if we can connect 368 // to the metadata service within a reasonable amount of time 369 metadataURL := os.Getenv("AWS_METADATA_URL") 370 if metadataURL == "" { 371 metadataURL = "http://169.254.169.254:80/latest" 372 } 373 c := http.Client{ 374 Timeout: 100 * time.Millisecond, 375 } 376 377 r, err := c.Get(metadataURL) 378 // Flag to determine if we should add the EC2Meta data provider. Default false 379 var useIAM bool 380 if err == nil { 381 // AWS will add a "Server: EC2ws" header value for the metadata request. We 382 // check the headers for this value to ensure something else didn't just 383 // happent to be listening on that IP:Port 384 if r.Header["Server"] != nil && strings.Contains(r.Header["Server"][0], "EC2") { 385 useIAM = true 386 } 387 } 388 389 if useIAM { 390 log.Printf("[DEBUG] EC2 Metadata service found, adding EC2 Role Credential Provider") 391 providers = append(providers, &ec2rolecreds.EC2RoleProvider{ 392 Client: ec2metadata.New(session.New(&aws.Config{ 393 Endpoint: aws.String(metadataURL), 394 })), 395 }) 396 } else { 397 log.Printf("[DEBUG] EC2 Metadata service not found, not adding EC2 Role Credential Provider") 398 } 399 return awsCredentials.NewChainCredentials(providers) 400 }