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  }