2014-06-24 04:01:57 +02:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2014-07-30 00:22:37 +02:00
|
|
|
"fmt"
|
2014-11-21 17:58:34 +01:00
|
|
|
"log"
|
2015-04-20 00:54:42 +02:00
|
|
|
"strings"
|
2014-06-24 04:01:57 +02:00
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/multierror"
|
2015-02-12 17:48:48 +01:00
|
|
|
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
2015-07-14 23:39:50 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
|
|
"github.com/aws/aws-sdk-go/service/autoscaling"
|
2015-06-07 11:23:32 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
2015-06-04 02:12:41 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/dynamodb"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2015-05-04 23:48:09 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/ecs"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/elasticache"
|
|
|
|
"github.com/aws/aws-sdk-go/service/elb"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
|
|
|
"github.com/aws/aws-sdk-go/service/kinesis"
|
2015-06-09 21:12:47 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/lambda"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/rds"
|
|
|
|
"github.com/aws/aws-sdk-go/service/route53"
|
|
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
|
|
|
"github.com/aws/aws-sdk-go/service/sns"
|
|
|
|
"github.com/aws/aws-sdk-go/service/sqs"
|
2014-06-24 04:01:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Config struct {
|
2015-05-03 11:08:47 +02:00
|
|
|
AccessKey string
|
|
|
|
SecretKey string
|
|
|
|
Token string
|
|
|
|
Region string
|
|
|
|
MaxRetries int
|
2015-04-20 00:54:42 +02:00
|
|
|
|
|
|
|
AllowedAccountIds []interface{}
|
|
|
|
ForbiddenAccountIds []interface{}
|
2015-07-22 23:57:29 +02:00
|
|
|
|
|
|
|
DynamoDBEndpoint string
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type AWSClient struct {
|
2015-06-07 11:23:32 +02:00
|
|
|
cloudwatchconn *cloudwatch.CloudWatch
|
2015-06-04 02:05:02 +02:00
|
|
|
dynamodbconn *dynamodb.DynamoDB
|
2015-02-20 23:26:43 +01:00
|
|
|
ec2conn *ec2.EC2
|
2015-05-04 23:48:09 +02:00
|
|
|
ecsconn *ecs.ECS
|
2015-02-20 23:26:43 +01:00
|
|
|
elbconn *elb.ELB
|
|
|
|
autoscalingconn *autoscaling.AutoScaling
|
|
|
|
s3conn *s3.S3
|
2015-05-20 13:21:23 +02:00
|
|
|
sqsconn *sqs.SQS
|
2015-05-23 06:19:43 +02:00
|
|
|
snsconn *sns.SNS
|
2015-02-20 23:26:43 +01:00
|
|
|
r53conn *route53.Route53
|
|
|
|
region string
|
2015-02-27 17:50:21 +01:00
|
|
|
rdsconn *rds.RDS
|
2015-03-24 21:34:13 +01:00
|
|
|
iamconn *iam.IAM
|
2015-05-27 21:17:26 +02:00
|
|
|
kinesisconn *kinesis.Kinesis
|
2015-04-26 03:53:21 +02:00
|
|
|
elasticacheconn *elasticache.ElastiCache
|
2015-06-01 18:33:22 +02:00
|
|
|
lambdaconn *lambda.Lambda
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
2015-07-22 23:57:29 +02:00
|
|
|
// Client configures and returns a fully initialized AWSClient
|
2014-11-21 17:58:34 +01:00
|
|
|
func (c *Config) Client() (interface{}, error) {
|
|
|
|
var client AWSClient
|
|
|
|
|
|
|
|
// Get the auth and region. This can fail if keys/regions were not
|
|
|
|
// specified and we're attempting to use the environment.
|
|
|
|
var errs []error
|
|
|
|
|
|
|
|
log.Println("[INFO] Building AWS region structure")
|
2015-03-13 15:42:50 +01:00
|
|
|
err := c.ValidateRegion()
|
2014-11-21 17:58:34 +01:00
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(errs) == 0 {
|
2015-02-19 22:38:56 +01:00
|
|
|
// store AWS region in client struct, for region specific operations such as
|
|
|
|
// bucket storage in S3
|
|
|
|
client.region = c.Region
|
2015-02-20 15:55:54 +01:00
|
|
|
|
2015-03-13 15:42:50 +01:00
|
|
|
log.Println("[INFO] Building AWS auth structure")
|
2015-06-29 07:46:49 +02:00
|
|
|
// We fetched all credential sources in Provider. If they are
|
|
|
|
// available, they'll already be in c. See Provider definition.
|
2015-05-07 05:02:09 +02:00
|
|
|
creds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
2015-04-16 22:02:04 +02:00
|
|
|
awsConfig := &aws.Config{
|
|
|
|
Credentials: creds,
|
2015-07-28 22:29:46 +02:00
|
|
|
Region: aws.String(c.Region),
|
|
|
|
MaxRetries: aws.Int(c.MaxRetries),
|
2015-04-13 21:01:41 +02:00
|
|
|
}
|
|
|
|
|
2015-07-14 23:39:50 +02:00
|
|
|
log.Println("[INFO] Initializing IAM Connection")
|
|
|
|
client.iamconn = iam.New(awsConfig)
|
|
|
|
|
|
|
|
err := c.ValidateCredentials(client.iamconn)
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
2015-04-13 21:01:41 +02:00
|
|
|
}
|
2015-07-30 17:21:03 +02:00
|
|
|
|
2015-07-22 23:57:29 +02:00
|
|
|
awsDynamoDBConfig := &aws.Config{
|
|
|
|
Credentials: creds,
|
2015-07-30 17:21:03 +02:00
|
|
|
Region: aws.String(c.Region),
|
|
|
|
MaxRetries: aws.Int(c.MaxRetries),
|
|
|
|
Endpoint: aws.String(c.DynamoDBEndpoint),
|
2015-07-22 23:57:29 +02:00
|
|
|
}
|
2015-04-13 21:01:41 +02:00
|
|
|
|
2015-06-04 02:05:02 +02:00
|
|
|
log.Println("[INFO] Initializing DynamoDB connection")
|
2015-07-22 23:57:29 +02:00
|
|
|
client.dynamodbconn = dynamodb.New(awsDynamoDBConfig)
|
2015-06-04 02:05:02 +02:00
|
|
|
|
2015-04-16 22:02:04 +02:00
|
|
|
log.Println("[INFO] Initializing ELB connection")
|
2015-04-13 21:01:41 +02:00
|
|
|
client.elbconn = elb.New(awsConfig)
|
2015-04-14 23:41:36 +02:00
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
log.Println("[INFO] Initializing S3 connection")
|
2015-04-16 20:51:12 +02:00
|
|
|
client.s3conn = s3.New(awsConfig)
|
2015-02-20 15:55:54 +01:00
|
|
|
|
2015-05-12 23:34:10 +02:00
|
|
|
log.Println("[INFO] Initializing SQS connection")
|
|
|
|
client.sqsconn = sqs.New(awsConfig)
|
|
|
|
|
2015-05-15 01:17:18 +02:00
|
|
|
log.Println("[INFO] Initializing SNS connection")
|
|
|
|
client.snsconn = sns.New(awsConfig)
|
|
|
|
|
2015-04-16 22:02:04 +02:00
|
|
|
log.Println("[INFO] Initializing RDS Connection")
|
2015-04-13 22:45:55 +02:00
|
|
|
client.rdsconn = rds.New(awsConfig)
|
2015-04-13 21:01:41 +02:00
|
|
|
|
2015-05-27 21:17:26 +02:00
|
|
|
log.Println("[INFO] Initializing Kinesis Connection")
|
|
|
|
client.kinesisconn = kinesis.New(awsConfig)
|
|
|
|
|
2015-07-14 23:39:50 +02:00
|
|
|
authErr := c.ValidateAccountId(client.iamconn)
|
|
|
|
if authErr != nil {
|
|
|
|
errs = append(errs, authErr)
|
2015-04-20 00:54:42 +02:00
|
|
|
}
|
|
|
|
|
2015-04-16 22:02:04 +02:00
|
|
|
log.Println("[INFO] Initializing AutoScaling connection")
|
2015-04-16 14:10:17 +02:00
|
|
|
client.autoscalingconn = autoscaling.New(awsConfig)
|
2015-04-14 16:51:23 +02:00
|
|
|
|
2015-04-16 22:02:04 +02:00
|
|
|
log.Println("[INFO] Initializing EC2 Connection")
|
|
|
|
client.ec2conn = ec2.New(awsConfig)
|
2015-04-14 16:51:23 +02:00
|
|
|
|
2015-05-04 23:48:09 +02:00
|
|
|
log.Println("[INFO] Initializing ECS Connection")
|
|
|
|
client.ecsconn = ecs.New(awsConfig)
|
|
|
|
|
2015-04-14 16:51:23 +02:00
|
|
|
// aws-sdk-go uses v4 for signing requests, which requires all global
|
|
|
|
// endpoints to use 'us-east-1'.
|
|
|
|
// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
|
2015-04-16 22:18:01 +02:00
|
|
|
log.Println("[INFO] Initializing Route 53 connection")
|
2015-04-16 22:02:04 +02:00
|
|
|
client.r53conn = route53.New(&aws.Config{
|
|
|
|
Credentials: creds,
|
2015-07-28 22:29:46 +02:00
|
|
|
Region: aws.String("us-east-1"),
|
|
|
|
MaxRetries: aws.Int(c.MaxRetries),
|
2015-04-14 16:51:23 +02:00
|
|
|
})
|
|
|
|
|
2015-04-26 03:53:21 +02:00
|
|
|
log.Println("[INFO] Initializing Elasticache Connection")
|
|
|
|
client.elasticacheconn = elasticache.New(awsConfig)
|
2015-06-01 18:33:22 +02:00
|
|
|
|
|
|
|
log.Println("[INFO] Initializing Lambda Connection")
|
|
|
|
client.lambdaconn = lambda.New(awsConfig)
|
2015-06-07 11:23:32 +02:00
|
|
|
|
|
|
|
log.Println("[INFO] Initializing CloudWatch SDK connection")
|
|
|
|
client.cloudwatchconn = cloudwatch.New(awsConfig)
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
return nil, &multierror.Error{Errors: errs}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &client, nil
|
2014-06-24 04:01:57 +02:00
|
|
|
}
|
|
|
|
|
2015-04-27 20:06:49 +02:00
|
|
|
// ValidateRegion returns an error if the configured region is not a
|
|
|
|
// valid aws region and nil otherwise.
|
2015-03-13 15:42:50 +01:00
|
|
|
func (c *Config) ValidateRegion() error {
|
2014-10-30 15:07:12 +01:00
|
|
|
var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
|
|
|
|
"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
|
|
|
|
"sa-east-1", "cn-north-1", "us-gov-west-1"}
|
2014-07-30 00:22:37 +02:00
|
|
|
|
|
|
|
for _, valid := range regions {
|
|
|
|
if c.Region == valid {
|
2015-03-13 15:42:50 +01:00
|
|
|
return nil
|
2014-07-30 00:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
2015-03-13 15:42:50 +01:00
|
|
|
return fmt.Errorf("Not a valid region: %s", c.Region)
|
2014-06-24 04:01:57 +02:00
|
|
|
}
|
2015-04-20 00:54:42 +02:00
|
|
|
|
2015-08-07 16:49:59 +02:00
|
|
|
// Validate credentials early and fail before we do any graph walking.
|
|
|
|
// In the case of an IAM role/profile with insuffecient privileges, fail
|
|
|
|
// silently
|
2015-07-14 23:39:50 +02:00
|
|
|
func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
|
|
|
|
_, err := iamconn.GetUser(nil)
|
2015-07-21 15:57:59 +02:00
|
|
|
|
|
|
|
if awsErr, ok := err.(awserr.Error); ok {
|
2015-08-07 16:49:59 +02:00
|
|
|
|
2015-08-07 18:55:44 +02:00
|
|
|
if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" {
|
2015-08-07 16:49:59 +02:00
|
|
|
log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile")
|
|
|
|
// User may be an IAM instance profile, or otherwise IAM role without the
|
|
|
|
// GetUser permissions, so fail silently
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-21 15:57:59 +02:00
|
|
|
if awsErr.Code() == "SignatureDoesNotMatch" {
|
|
|
|
return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
|
2015-07-14 23:39:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-21 15:57:59 +02:00
|
|
|
return err
|
2015-07-14 23:39:50 +02:00
|
|
|
}
|
|
|
|
|
2015-04-27 20:06:49 +02:00
|
|
|
// ValidateAccountId returns a context-specific error if the configured account
|
|
|
|
// id is explicitly forbidden or not authorised; and nil if it is authorised.
|
2015-04-20 00:54:42 +02:00
|
|
|
func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
|
|
|
|
if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[INFO] Validating account ID")
|
|
|
|
|
|
|
|
out, err := iamconn.GetUser(nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed getting account ID from IAM: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-08-17 20:27:16 +02:00
|
|
|
account_id := strings.Split(*out.User.Arn, ":")[4]
|
2015-04-20 00:54:42 +02:00
|
|
|
|
|
|
|
if c.ForbiddenAccountIds != nil {
|
|
|
|
for _, id := range c.ForbiddenAccountIds {
|
|
|
|
if id == account_id {
|
|
|
|
return fmt.Errorf("Forbidden account ID (%s)", id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.AllowedAccountIds != nil {
|
|
|
|
for _, id := range c.AllowedAccountIds {
|
|
|
|
if id == account_id {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Account ID not allowed (%s)", account_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|