github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/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 errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)) 124 return nil, &multierror.Error{Errors: errs} 125 } 126 awsConfig := &aws.Config{ 127 Credentials: creds, 128 Region: aws.String(c.Region), 129 MaxRetries: aws.Int(c.MaxRetries), 130 HTTPClient: cleanhttp.DefaultClient(), 131 } 132 133 log.Println("[INFO] Initializing IAM Connection") 134 sess := session.New(awsConfig) 135 client.iamconn = iam.New(sess) 136 137 err = c.ValidateCredentials(client.iamconn) 138 if err != nil { 139 errs = append(errs, err) 140 } 141 142 // Some services exist only in us-east-1, e.g. because they manage 143 // resources that can span across multiple regions, or because 144 // signature format v4 requires region to be us-east-1 for global 145 // endpoints: 146 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 147 usEast1AwsConfig := &aws.Config{ 148 Credentials: creds, 149 Region: aws.String("us-east-1"), 150 MaxRetries: aws.Int(c.MaxRetries), 151 HTTPClient: cleanhttp.DefaultClient(), 152 } 153 usEast1Sess := session.New(usEast1AwsConfig) 154 155 awsDynamoDBConfig := *awsConfig 156 awsDynamoDBConfig.Endpoint = aws.String(c.DynamoDBEndpoint) 157 158 log.Println("[INFO] Initializing DynamoDB connection") 159 dynamoSess := session.New(&awsDynamoDBConfig) 160 client.dynamodbconn = dynamodb.New(dynamoSess) 161 162 log.Println("[INFO] Initializing ELB connection") 163 client.elbconn = elb.New(sess) 164 165 log.Println("[INFO] Initializing S3 connection") 166 client.s3conn = s3.New(sess) 167 168 log.Println("[INFO] Initializing SQS connection") 169 client.sqsconn = sqs.New(sess) 170 171 log.Println("[INFO] Initializing SNS connection") 172 client.snsconn = sns.New(sess) 173 174 log.Println("[INFO] Initializing RDS Connection") 175 client.rdsconn = rds.New(sess) 176 177 awsKinesisConfig := *awsConfig 178 awsKinesisConfig.Endpoint = aws.String(c.KinesisEndpoint) 179 180 log.Println("[INFO] Initializing Kinesis Connection") 181 kinesisSess := session.New(&awsKinesisConfig) 182 client.kinesisconn = kinesis.New(kinesisSess) 183 184 authErr := c.ValidateAccountId(client.iamconn) 185 if authErr != nil { 186 errs = append(errs, authErr) 187 } 188 189 log.Println("[INFO] Initializing Kinesis Firehose Connection") 190 client.firehoseconn = firehose.New(sess) 191 192 log.Println("[INFO] Initializing AutoScaling connection") 193 client.autoscalingconn = autoscaling.New(sess) 194 195 log.Println("[INFO] Initializing EC2 Connection") 196 client.ec2conn = ec2.New(sess) 197 198 log.Println("[INFO] Initializing ECR Connection") 199 client.ecrconn = ecr.New(sess) 200 201 log.Println("[INFO] Initializing ECS Connection") 202 client.ecsconn = ecs.New(sess) 203 204 log.Println("[INFO] Initializing EFS Connection") 205 client.efsconn = efs.New(sess) 206 207 log.Println("[INFO] Initializing ElasticSearch Connection") 208 client.esconn = elasticsearch.New(sess) 209 210 log.Println("[INFO] Initializing Route 53 connection") 211 client.r53conn = route53.New(usEast1Sess) 212 213 log.Println("[INFO] Initializing Elasticache Connection") 214 client.elasticacheconn = elasticache.New(sess) 215 216 log.Println("[INFO] Initializing Lambda Connection") 217 client.lambdaconn = lambda.New(sess) 218 219 log.Println("[INFO] Initializing Cloudformation Connection") 220 client.cfconn = cloudformation.New(sess) 221 222 log.Println("[INFO] Initializing CloudWatch SDK connection") 223 client.cloudwatchconn = cloudwatch.New(sess) 224 225 log.Println("[INFO] Initializing CloudTrail connection") 226 client.cloudtrailconn = cloudtrail.New(sess) 227 228 log.Println("[INFO] Initializing CloudWatch Logs connection") 229 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 230 231 log.Println("[INFO] Initializing OpsWorks Connection") 232 client.opsworksconn = opsworks.New(usEast1Sess) 233 234 log.Println("[INFO] Initializing Directory Service connection") 235 client.dsconn = directoryservice.New(sess) 236 237 log.Println("[INFO] Initializing Glacier connection") 238 client.glacierconn = glacier.New(sess) 239 240 log.Println("[INFO] Initializing CodeDeploy Connection") 241 client.codedeployconn = codedeploy.New(sess) 242 243 log.Println("[INFO] Initializing CodeCommit SDK connection") 244 client.codecommitconn = codecommit.New(usEast1Sess) 245 246 log.Println("[INFO] Initializing Redshift SDK connection") 247 client.redshiftconn = redshift.New(sess) 248 249 } 250 251 if len(errs) > 0 { 252 return nil, &multierror.Error{Errors: errs} 253 } 254 255 return &client, nil 256 } 257 258 // ValidateRegion returns an error if the configured region is not a 259 // valid aws region and nil otherwise. 260 func (c *Config) ValidateRegion() error { 261 var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 262 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 263 "ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"} 264 265 for _, valid := range regions { 266 if c.Region == valid { 267 return nil 268 } 269 } 270 return fmt.Errorf("Not a valid region: %s", c.Region) 271 } 272 273 // Validate credentials early and fail before we do any graph walking. 274 // In the case of an IAM role/profile with insuffecient privileges, fail 275 // silently 276 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 277 _, err := iamconn.GetUser(nil) 278 279 if awsErr, ok := err.(awserr.Error); ok { 280 281 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 282 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile") 283 // User may be an IAM instance profile, or otherwise IAM role without the 284 // GetUser permissions, so fail silently 285 return nil 286 } 287 288 if awsErr.Code() == "SignatureDoesNotMatch" { 289 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 290 } 291 } 292 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(iamconn *iam.IAM) error { 299 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 300 return nil 301 } 302 303 log.Printf("[INFO] Validating account ID") 304 305 out, err := iamconn.GetUser(nil) 306 307 if err != nil { 308 awsErr, _ := err.(awserr.Error) 309 if awsErr.Code() == "ValidationError" { 310 log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile") 311 // User may be an IAM instance profile, so fail silently. 312 // If it is an IAM instance profile 313 // validating account might be superfluous 314 return nil 315 } else { 316 return fmt.Errorf("Failed getting account ID from IAM: %s", err) 317 // return error if the account id is explicitly not authorised 318 } 319 } 320 321 account_id := strings.Split(*out.User.Arn, ":")[4] 322 323 if c.ForbiddenAccountIds != nil { 324 for _, id := range c.ForbiddenAccountIds { 325 if id == account_id { 326 return fmt.Errorf("Forbidden account ID (%s)", id) 327 } 328 } 329 } 330 331 if c.AllowedAccountIds != nil { 332 for _, id := range c.AllowedAccountIds { 333 if id == account_id { 334 return nil 335 } 336 } 337 return fmt.Errorf("Account ID not allowed (%s)", account_id) 338 } 339 340 return nil 341 } 342 343 // This function is responsible for reading credentials from the 344 // environment in the case that they're not explicitly specified 345 // in the Terraform configuration. 346 func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials { 347 // build a chain provider, lazy-evaulated by aws-sdk 348 providers := []awsCredentials.Provider{ 349 &awsCredentials.StaticProvider{Value: awsCredentials.Value{ 350 AccessKeyID: key, 351 SecretAccessKey: secret, 352 SessionToken: token, 353 }}, 354 &awsCredentials.EnvProvider{}, 355 &awsCredentials.SharedCredentialsProvider{ 356 Filename: credsfile, 357 Profile: profile, 358 }, 359 } 360 361 // We only look in the EC2 metadata API if we can connect 362 // to the metadata service within a reasonable amount of time 363 metadataURL := os.Getenv("AWS_METADATA_URL") 364 if metadataURL == "" { 365 metadataURL = "http://169.254.169.254:80/latest" 366 } 367 c := http.Client{ 368 Timeout: 100 * time.Millisecond, 369 } 370 371 r, err := c.Get(metadataURL) 372 // Flag to determine if we should add the EC2Meta data provider. Default false 373 var useIAM bool 374 if err == nil { 375 // AWS will add a "Server: EC2ws" header value for the metadata request. We 376 // check the headers for this value to ensure something else didn't just 377 // happent to be listening on that IP:Port 378 if r.Header["Server"] != nil && strings.Contains(r.Header["Server"][0], "EC2") { 379 useIAM = true 380 } 381 } 382 383 if useIAM { 384 log.Printf("[DEBUG] EC2 Metadata service found, adding EC2 Role Credential Provider") 385 providers = append(providers, &ec2rolecreds.EC2RoleProvider{ 386 Client: ec2metadata.New(session.New(&aws.Config{ 387 Endpoint: aws.String(metadataURL), 388 })), 389 }) 390 } else { 391 log.Printf("[DEBUG] EC2 Metadata service not found, not adding EC2 Role Credential Provider") 392 } 393 return awsCredentials.NewChainCredentials(providers) 394 }