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