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