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