github.com/wangzhucn/terraform@v0.6.7-0.20151109233120-4eea011b56b3/builtin/providers/aws/config.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/go-cleanhttp" 9 "github.com/hashicorp/go-multierror" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/aws/credentials" 14 "github.com/aws/aws-sdk-go/aws/session" 15 "github.com/aws/aws-sdk-go/service/autoscaling" 16 "github.com/aws/aws-sdk-go/service/cloudformation" 17 "github.com/aws/aws-sdk-go/service/cloudtrail" 18 "github.com/aws/aws-sdk-go/service/cloudwatch" 19 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 20 "github.com/aws/aws-sdk-go/service/codecommit" 21 "github.com/aws/aws-sdk-go/service/codedeploy" 22 "github.com/aws/aws-sdk-go/service/directoryservice" 23 "github.com/aws/aws-sdk-go/service/dynamodb" 24 "github.com/aws/aws-sdk-go/service/ec2" 25 "github.com/aws/aws-sdk-go/service/ecs" 26 "github.com/aws/aws-sdk-go/service/efs" 27 "github.com/aws/aws-sdk-go/service/elasticache" 28 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 29 "github.com/aws/aws-sdk-go/service/elb" 30 "github.com/aws/aws-sdk-go/service/glacier" 31 "github.com/aws/aws-sdk-go/service/iam" 32 "github.com/aws/aws-sdk-go/service/kinesis" 33 "github.com/aws/aws-sdk-go/service/lambda" 34 "github.com/aws/aws-sdk-go/service/opsworks" 35 "github.com/aws/aws-sdk-go/service/rds" 36 "github.com/aws/aws-sdk-go/service/route53" 37 "github.com/aws/aws-sdk-go/service/s3" 38 "github.com/aws/aws-sdk-go/service/sns" 39 "github.com/aws/aws-sdk-go/service/sqs" 40 ) 41 42 type Config struct { 43 AccessKey string 44 SecretKey string 45 Token string 46 Region string 47 MaxRetries int 48 49 AllowedAccountIds []interface{} 50 ForbiddenAccountIds []interface{} 51 52 DynamoDBEndpoint string 53 KinesisEndpoint string 54 } 55 56 type AWSClient struct { 57 cfconn *cloudformation.CloudFormation 58 cloudtrailconn *cloudtrail.CloudTrail 59 cloudwatchconn *cloudwatch.CloudWatch 60 cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs 61 dsconn *directoryservice.DirectoryService 62 dynamodbconn *dynamodb.DynamoDB 63 ec2conn *ec2.EC2 64 ecsconn *ecs.ECS 65 efsconn *efs.EFS 66 elbconn *elb.ELB 67 esconn *elasticsearch.ElasticsearchService 68 autoscalingconn *autoscaling.AutoScaling 69 s3conn *s3.S3 70 sqsconn *sqs.SQS 71 snsconn *sns.SNS 72 r53conn *route53.Route53 73 region string 74 rdsconn *rds.RDS 75 iamconn *iam.IAM 76 kinesisconn *kinesis.Kinesis 77 elasticacheconn *elasticache.ElastiCache 78 lambdaconn *lambda.Lambda 79 opsworksconn *opsworks.OpsWorks 80 glacierconn *glacier.Glacier 81 codedeployconn *codedeploy.CodeDeploy 82 codecommitconn *codecommit.CodeCommit 83 } 84 85 // Client configures and returns a fully initialized AWSClient 86 func (c *Config) Client() (interface{}, error) { 87 var client AWSClient 88 89 // Get the auth and region. This can fail if keys/regions were not 90 // specified and we're attempting to use the environment. 91 var errs []error 92 93 log.Println("[INFO] Building AWS region structure") 94 err := c.ValidateRegion() 95 if err != nil { 96 errs = append(errs, err) 97 } 98 99 if len(errs) == 0 { 100 // store AWS region in client struct, for region specific operations such as 101 // bucket storage in S3 102 client.region = c.Region 103 104 log.Println("[INFO] Building AWS auth structure") 105 // We fetched all credential sources in Provider. If they are 106 // available, they'll already be in c. See Provider definition. 107 creds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token) 108 awsConfig := &aws.Config{ 109 Credentials: creds, 110 Region: aws.String(c.Region), 111 MaxRetries: aws.Int(c.MaxRetries), 112 HTTPClient: cleanhttp.DefaultClient(), 113 } 114 115 log.Println("[INFO] Initializing IAM Connection") 116 sess := session.New(awsConfig) 117 client.iamconn = iam.New(sess) 118 119 err := c.ValidateCredentials(client.iamconn) 120 if err != nil { 121 errs = append(errs, err) 122 } 123 124 // Some services exist only in us-east-1, e.g. because they manage 125 // resources that can span across multiple regions, or because 126 // signature format v4 requires region to be us-east-1 for global 127 // endpoints: 128 // http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html 129 usEast1AwsConfig := &aws.Config{ 130 Credentials: creds, 131 Region: aws.String("us-east-1"), 132 MaxRetries: aws.Int(c.MaxRetries), 133 HTTPClient: cleanhttp.DefaultClient(), 134 } 135 usEast1Sess := session.New(usEast1AwsConfig) 136 137 awsDynamoDBConfig := *awsConfig 138 awsDynamoDBConfig.Endpoint = aws.String(c.DynamoDBEndpoint) 139 140 log.Println("[INFO] Initializing DynamoDB connection") 141 dynamoSess := session.New(&awsDynamoDBConfig) 142 client.dynamodbconn = dynamodb.New(dynamoSess) 143 144 log.Println("[INFO] Initializing ELB connection") 145 client.elbconn = elb.New(sess) 146 147 log.Println("[INFO] Initializing S3 connection") 148 client.s3conn = s3.New(sess) 149 150 log.Println("[INFO] Initializing SQS connection") 151 client.sqsconn = sqs.New(sess) 152 153 log.Println("[INFO] Initializing SNS connection") 154 client.snsconn = sns.New(sess) 155 156 log.Println("[INFO] Initializing RDS Connection") 157 client.rdsconn = rds.New(sess) 158 159 awsKinesisConfig := *awsConfig 160 awsKinesisConfig.Endpoint = aws.String(c.KinesisEndpoint) 161 162 log.Println("[INFO] Initializing Kinesis Connection") 163 kinesisSess := session.New(&awsKinesisConfig) 164 client.kinesisconn = kinesis.New(kinesisSess) 165 166 authErr := c.ValidateAccountId(client.iamconn) 167 if authErr != nil { 168 errs = append(errs, authErr) 169 } 170 171 log.Println("[INFO] Initializing AutoScaling connection") 172 client.autoscalingconn = autoscaling.New(sess) 173 174 log.Println("[INFO] Initializing EC2 Connection") 175 client.ec2conn = ec2.New(sess) 176 177 log.Println("[INFO] Initializing ECS Connection") 178 client.ecsconn = ecs.New(sess) 179 180 log.Println("[INFO] Initializing EFS Connection") 181 client.efsconn = efs.New(sess) 182 183 log.Println("[INFO] Initializing ElasticSearch Connection") 184 client.esconn = elasticsearch.New(sess) 185 186 log.Println("[INFO] Initializing Route 53 connection") 187 client.r53conn = route53.New(usEast1Sess) 188 189 log.Println("[INFO] Initializing Elasticache Connection") 190 client.elasticacheconn = elasticache.New(sess) 191 192 log.Println("[INFO] Initializing Lambda Connection") 193 client.lambdaconn = lambda.New(sess) 194 195 log.Println("[INFO] Initializing Cloudformation Connection") 196 client.cfconn = cloudformation.New(sess) 197 198 log.Println("[INFO] Initializing CloudWatch SDK connection") 199 client.cloudwatchconn = cloudwatch.New(sess) 200 201 log.Println("[INFO] Initializing CloudTrail connection") 202 client.cloudtrailconn = cloudtrail.New(sess) 203 204 log.Println("[INFO] Initializing CloudWatch Logs connection") 205 client.cloudwatchlogsconn = cloudwatchlogs.New(sess) 206 207 log.Println("[INFO] Initializing OpsWorks Connection") 208 client.opsworksconn = opsworks.New(usEast1Sess) 209 210 log.Println("[INFO] Initializing Directory Service connection") 211 client.dsconn = directoryservice.New(sess) 212 213 log.Println("[INFO] Initializing Glacier connection") 214 client.glacierconn = glacier.New(sess) 215 216 log.Println("[INFO] Initializing CodeDeploy Connection") 217 client.codedeployconn = codedeploy.New(sess) 218 219 log.Println("[INFO] Initializing CodeCommit SDK connection") 220 client.codecommitconn = codecommit.New(usEast1Sess) 221 } 222 223 if len(errs) > 0 { 224 return nil, &multierror.Error{Errors: errs} 225 } 226 227 return &client, nil 228 } 229 230 // ValidateRegion returns an error if the configured region is not a 231 // valid aws region and nil otherwise. 232 func (c *Config) ValidateRegion() error { 233 var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1", 234 "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", 235 "sa-east-1", "cn-north-1", "us-gov-west-1"} 236 237 for _, valid := range regions { 238 if c.Region == valid { 239 return nil 240 } 241 } 242 return fmt.Errorf("Not a valid region: %s", c.Region) 243 } 244 245 // Validate credentials early and fail before we do any graph walking. 246 // In the case of an IAM role/profile with insuffecient privileges, fail 247 // silently 248 func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { 249 _, err := iamconn.GetUser(nil) 250 251 if awsErr, ok := err.(awserr.Error); ok { 252 253 if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" { 254 log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile") 255 // User may be an IAM instance profile, or otherwise IAM role without the 256 // GetUser permissions, so fail silently 257 return nil 258 } 259 260 if awsErr.Code() == "SignatureDoesNotMatch" { 261 return fmt.Errorf("Failed authenticating with AWS: please verify credentials") 262 } 263 } 264 265 return err 266 } 267 268 // ValidateAccountId returns a context-specific error if the configured account 269 // id is explicitly forbidden or not authorised; and nil if it is authorised. 270 func (c *Config) ValidateAccountId(iamconn *iam.IAM) error { 271 if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil { 272 return nil 273 } 274 275 log.Printf("[INFO] Validating account ID") 276 277 out, err := iamconn.GetUser(nil) 278 279 if err != nil { 280 awsErr, _ := err.(awserr.Error) 281 if awsErr.Code() == "ValidationError" { 282 log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile") 283 // User may be an IAM instance profile, so fail silently. 284 // If it is an IAM instance profile 285 // validating account might be superfluous 286 return nil 287 } else { 288 return fmt.Errorf("Failed getting account ID from IAM: %s", err) 289 // return error if the account id is explicitly not authorised 290 } 291 } 292 293 account_id := strings.Split(*out.User.Arn, ":")[4] 294 295 if c.ForbiddenAccountIds != nil { 296 for _, id := range c.ForbiddenAccountIds { 297 if id == account_id { 298 return fmt.Errorf("Forbidden account ID (%s)", id) 299 } 300 } 301 } 302 303 if c.AllowedAccountIds != nil { 304 for _, id := range c.AllowedAccountIds { 305 if id == account_id { 306 return nil 307 } 308 } 309 return fmt.Errorf("Account ID not allowed (%s)", account_id) 310 } 311 312 return nil 313 }