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