github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/config.go (about)

     1  package aws
     2  
     3  import (
     4  	"crypto/tls"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/aws/request"
    16  	"github.com/aws/aws-sdk-go/aws/session"
    17  	"github.com/aws/aws-sdk-go/service/acm"
    18  	"github.com/aws/aws-sdk-go/service/apigateway"
    19  	"github.com/aws/aws-sdk-go/service/applicationautoscaling"
    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/cloudfront"
    23  	"github.com/aws/aws-sdk-go/service/cloudtrail"
    24  	"github.com/aws/aws-sdk-go/service/cloudwatch"
    25  	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
    26  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    27  	"github.com/aws/aws-sdk-go/service/codebuild"
    28  	"github.com/aws/aws-sdk-go/service/codecommit"
    29  	"github.com/aws/aws-sdk-go/service/codedeploy"
    30  	"github.com/aws/aws-sdk-go/service/codepipeline"
    31  	"github.com/aws/aws-sdk-go/service/cognitoidentity"
    32  	"github.com/aws/aws-sdk-go/service/configservice"
    33  	"github.com/aws/aws-sdk-go/service/databasemigrationservice"
    34  	"github.com/aws/aws-sdk-go/service/directoryservice"
    35  	"github.com/aws/aws-sdk-go/service/dynamodb"
    36  	"github.com/aws/aws-sdk-go/service/ec2"
    37  	"github.com/aws/aws-sdk-go/service/ecr"
    38  	"github.com/aws/aws-sdk-go/service/ecs"
    39  	"github.com/aws/aws-sdk-go/service/efs"
    40  	"github.com/aws/aws-sdk-go/service/elasticache"
    41  	"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
    42  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    43  	"github.com/aws/aws-sdk-go/service/elastictranscoder"
    44  	"github.com/aws/aws-sdk-go/service/elb"
    45  	"github.com/aws/aws-sdk-go/service/elbv2"
    46  	"github.com/aws/aws-sdk-go/service/emr"
    47  	"github.com/aws/aws-sdk-go/service/firehose"
    48  	"github.com/aws/aws-sdk-go/service/glacier"
    49  	"github.com/aws/aws-sdk-go/service/iam"
    50  	"github.com/aws/aws-sdk-go/service/inspector"
    51  	"github.com/aws/aws-sdk-go/service/kinesis"
    52  	"github.com/aws/aws-sdk-go/service/kms"
    53  	"github.com/aws/aws-sdk-go/service/lambda"
    54  	"github.com/aws/aws-sdk-go/service/lightsail"
    55  	"github.com/aws/aws-sdk-go/service/opsworks"
    56  	"github.com/aws/aws-sdk-go/service/rds"
    57  	"github.com/aws/aws-sdk-go/service/redshift"
    58  	"github.com/aws/aws-sdk-go/service/route53"
    59  	"github.com/aws/aws-sdk-go/service/s3"
    60  	"github.com/aws/aws-sdk-go/service/ses"
    61  	"github.com/aws/aws-sdk-go/service/sfn"
    62  	"github.com/aws/aws-sdk-go/service/simpledb"
    63  	"github.com/aws/aws-sdk-go/service/sns"
    64  	"github.com/aws/aws-sdk-go/service/sqs"
    65  	"github.com/aws/aws-sdk-go/service/ssm"
    66  	"github.com/aws/aws-sdk-go/service/sts"
    67  	"github.com/aws/aws-sdk-go/service/waf"
    68  	"github.com/davecgh/go-spew/spew"
    69  	"github.com/hashicorp/errwrap"
    70  	"github.com/hashicorp/go-cleanhttp"
    71  	"github.com/hashicorp/terraform/helper/logging"
    72  	"github.com/hashicorp/terraform/terraform"
    73  )
    74  
    75  type Config struct {
    76  	AccessKey     string
    77  	SecretKey     string
    78  	CredsFilename string
    79  	Profile       string
    80  	Token         string
    81  	Region        string
    82  	MaxRetries    int
    83  
    84  	AssumeRoleARN         string
    85  	AssumeRoleExternalID  string
    86  	AssumeRoleSessionName string
    87  	AssumeRolePolicy      string
    88  
    89  	AllowedAccountIds   []interface{}
    90  	ForbiddenAccountIds []interface{}
    91  
    92  	CloudFormationEndpoint   string
    93  	CloudWatchEndpoint       string
    94  	CloudWatchEventsEndpoint string
    95  	CloudWatchLogsEndpoint   string
    96  	DynamoDBEndpoint         string
    97  	Ec2Endpoint              string
    98  	ElbEndpoint              string
    99  	IamEndpoint              string
   100  	KinesisEndpoint          string
   101  	KmsEndpoint              string
   102  	RdsEndpoint              string
   103  	S3Endpoint               string
   104  	SnsEndpoint              string
   105  	SqsEndpoint              string
   106  	Insecure                 bool
   107  
   108  	SkipCredsValidation     bool
   109  	SkipGetEC2Platforms     bool
   110  	SkipRegionValidation    bool
   111  	SkipRequestingAccountId bool
   112  	SkipMetadataApiCheck    bool
   113  	S3ForcePathStyle        bool
   114  }
   115  
   116  type AWSClient struct {
   117  	cfconn                *cloudformation.CloudFormation
   118  	cloudfrontconn        *cloudfront.CloudFront
   119  	cloudtrailconn        *cloudtrail.CloudTrail
   120  	cloudwatchconn        *cloudwatch.CloudWatch
   121  	cloudwatchlogsconn    *cloudwatchlogs.CloudWatchLogs
   122  	cloudwatcheventsconn  *cloudwatchevents.CloudWatchEvents
   123  	cognitoconn           *cognitoidentity.CognitoIdentity
   124  	configconn            *configservice.ConfigService
   125  	dmsconn               *databasemigrationservice.DatabaseMigrationService
   126  	dsconn                *directoryservice.DirectoryService
   127  	dynamodbconn          *dynamodb.DynamoDB
   128  	ec2conn               *ec2.EC2
   129  	ecrconn               *ecr.ECR
   130  	ecsconn               *ecs.ECS
   131  	efsconn               *efs.EFS
   132  	elbconn               *elb.ELB
   133  	elbv2conn             *elbv2.ELBV2
   134  	emrconn               *emr.EMR
   135  	esconn                *elasticsearch.ElasticsearchService
   136  	acmconn               *acm.ACM
   137  	apigateway            *apigateway.APIGateway
   138  	appautoscalingconn    *applicationautoscaling.ApplicationAutoScaling
   139  	autoscalingconn       *autoscaling.AutoScaling
   140  	s3conn                *s3.S3
   141  	sesConn               *ses.SES
   142  	simpledbconn          *simpledb.SimpleDB
   143  	sqsconn               *sqs.SQS
   144  	snsconn               *sns.SNS
   145  	stsconn               *sts.STS
   146  	redshiftconn          *redshift.Redshift
   147  	r53conn               *route53.Route53
   148  	partition             string
   149  	accountid             string
   150  	supportedplatforms    []string
   151  	region                string
   152  	rdsconn               *rds.RDS
   153  	iamconn               *iam.IAM
   154  	kinesisconn           *kinesis.Kinesis
   155  	kmsconn               *kms.KMS
   156  	firehoseconn          *firehose.Firehose
   157  	inspectorconn         *inspector.Inspector
   158  	elasticacheconn       *elasticache.ElastiCache
   159  	elasticbeanstalkconn  *elasticbeanstalk.ElasticBeanstalk
   160  	elastictranscoderconn *elastictranscoder.ElasticTranscoder
   161  	lambdaconn            *lambda.Lambda
   162  	lightsailconn         *lightsail.Lightsail
   163  	opsworksconn          *opsworks.OpsWorks
   164  	glacierconn           *glacier.Glacier
   165  	codebuildconn         *codebuild.CodeBuild
   166  	codedeployconn        *codedeploy.CodeDeploy
   167  	codecommitconn        *codecommit.CodeCommit
   168  	codepipelineconn      *codepipeline.CodePipeline
   169  	sfnconn               *sfn.SFN
   170  	ssmconn               *ssm.SSM
   171  	wafconn               *waf.WAF
   172  }
   173  
   174  func (c *AWSClient) S3() *s3.S3 {
   175  	return c.s3conn
   176  }
   177  
   178  func (c *AWSClient) DynamoDB() *dynamodb.DynamoDB {
   179  	return c.dynamodbconn
   180  }
   181  
   182  func (c *AWSClient) IsGovCloud() bool {
   183  	if c.region == "us-gov-west-1" {
   184  		return true
   185  	}
   186  	return false
   187  }
   188  
   189  func (c *AWSClient) IsChinaCloud() bool {
   190  	if c.region == "cn-north-1" {
   191  		return true
   192  	}
   193  	return false
   194  }
   195  
   196  // Client configures and returns a fully initialized AWSClient
   197  func (c *Config) Client() (interface{}, error) {
   198  	// Get the auth and region. This can fail if keys/regions were not
   199  	// specified and we're attempting to use the environment.
   200  	if c.SkipRegionValidation {
   201  		log.Println("[INFO] Skipping region validation")
   202  	} else {
   203  		log.Println("[INFO] Building AWS region structure")
   204  		err := c.ValidateRegion()
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  	}
   209  
   210  	var client AWSClient
   211  	// store AWS region in client struct, for region specific operations such as
   212  	// bucket storage in S3
   213  	client.region = c.Region
   214  
   215  	log.Println("[INFO] Building AWS auth structure")
   216  	creds, err := GetCredentials(c)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	// Call Get to check for credential provider. If nothing found, we'll get an
   221  	// error, and we can present it nicely to the user
   222  	cp, err := creds.Get()
   223  	if err != nil {
   224  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
   225  			return nil, errors.New(`No valid credential sources found for AWS Provider.
   226    Please see https://terraform.io/docs/providers/aws/index.html for more information on
   227    providing credentials for the AWS Provider`)
   228  		}
   229  
   230  		return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
   231  	}
   232  
   233  	log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
   234  
   235  	awsConfig := &aws.Config{
   236  		Credentials:      creds,
   237  		Region:           aws.String(c.Region),
   238  		MaxRetries:       aws.Int(c.MaxRetries),
   239  		HTTPClient:       cleanhttp.DefaultClient(),
   240  		S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
   241  	}
   242  
   243  	if logging.IsDebugOrHigher() {
   244  		awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
   245  		awsConfig.Logger = awsLogger{}
   246  	}
   247  
   248  	if c.Insecure {
   249  		transport := awsConfig.HTTPClient.Transport.(*http.Transport)
   250  		transport.TLSClientConfig = &tls.Config{
   251  			InsecureSkipVerify: true,
   252  		}
   253  	}
   254  
   255  	// Set up base session
   256  	sess, err := session.NewSession(awsConfig)
   257  	if err != nil {
   258  		return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
   259  	}
   260  
   261  	sess.Handlers.Build.PushBackNamed(addTerraformVersionToUserAgent)
   262  
   263  	if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" {
   264  		sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure)
   265  	}
   266  
   267  	// Some services exist only in us-east-1, e.g. because they manage
   268  	// resources that can span across multiple regions, or because
   269  	// signature format v4 requires region to be us-east-1 for global
   270  	// endpoints:
   271  	// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   272  	usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
   273  
   274  	// Some services have user-configurable endpoints
   275  	awsCfSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudFormationEndpoint)})
   276  	awsCwSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEndpoint)})
   277  	awsCweSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEventsEndpoint)})
   278  	awsCwlSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchLogsEndpoint)})
   279  	awsDynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   280  	awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   281  	awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   282  	awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   283  	awsKinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   284  	awsKmsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KmsEndpoint)})
   285  	awsRdsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.RdsEndpoint)})
   286  	awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
   287  	awsSnsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SnsEndpoint)})
   288  	awsSqsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SqsEndpoint)})
   289  
   290  	// These two services need to be set up early so we can check on AccountID
   291  	client.iamconn = iam.New(awsIamSess)
   292  	client.stsconn = sts.New(sess)
   293  
   294  	if !c.SkipCredsValidation {
   295  		err = c.ValidateCredentials(client.stsconn)
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  	}
   300  
   301  	if !c.SkipRequestingAccountId {
   302  		partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName)
   303  		if err == nil {
   304  			client.partition = partition
   305  			client.accountid = accountId
   306  		}
   307  	}
   308  
   309  	authErr := c.ValidateAccountId(client.accountid)
   310  	if authErr != nil {
   311  		return nil, authErr
   312  	}
   313  
   314  	client.ec2conn = ec2.New(awsEc2Sess)
   315  
   316  	if !c.SkipGetEC2Platforms {
   317  		supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn)
   318  		if err != nil {
   319  			// We intentionally fail *silently* because there's a chance
   320  			// user just doesn't have ec2:DescribeAccountAttributes permissions
   321  			log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err)
   322  		} else {
   323  			client.supportedplatforms = supportedPlatforms
   324  		}
   325  	}
   326  
   327  	client.acmconn = acm.New(sess)
   328  	client.apigateway = apigateway.New(sess)
   329  	client.appautoscalingconn = applicationautoscaling.New(sess)
   330  	client.autoscalingconn = autoscaling.New(sess)
   331  	client.cfconn = cloudformation.New(awsCfSess)
   332  	client.cloudfrontconn = cloudfront.New(sess)
   333  	client.cloudtrailconn = cloudtrail.New(sess)
   334  	client.cloudwatchconn = cloudwatch.New(awsCwSess)
   335  	client.cloudwatcheventsconn = cloudwatchevents.New(awsCweSess)
   336  	client.cloudwatchlogsconn = cloudwatchlogs.New(awsCwlSess)
   337  	client.codecommitconn = codecommit.New(sess)
   338  	client.codebuildconn = codebuild.New(sess)
   339  	client.codedeployconn = codedeploy.New(sess)
   340  	client.configconn = configservice.New(sess)
   341  	client.cognitoconn = cognitoidentity.New(sess)
   342  	client.dmsconn = databasemigrationservice.New(sess)
   343  	client.codepipelineconn = codepipeline.New(sess)
   344  	client.dsconn = directoryservice.New(sess)
   345  	client.dynamodbconn = dynamodb.New(awsDynamoSess)
   346  	client.ecrconn = ecr.New(sess)
   347  	client.ecsconn = ecs.New(sess)
   348  	client.efsconn = efs.New(sess)
   349  	client.elasticacheconn = elasticache.New(sess)
   350  	client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   351  	client.elastictranscoderconn = elastictranscoder.New(sess)
   352  	client.elbconn = elb.New(awsElbSess)
   353  	client.elbv2conn = elbv2.New(awsElbSess)
   354  	client.emrconn = emr.New(sess)
   355  	client.esconn = elasticsearch.New(sess)
   356  	client.firehoseconn = firehose.New(sess)
   357  	client.inspectorconn = inspector.New(sess)
   358  	client.glacierconn = glacier.New(sess)
   359  	client.kinesisconn = kinesis.New(awsKinesisSess)
   360  	client.kmsconn = kms.New(awsKmsSess)
   361  	client.lambdaconn = lambda.New(sess)
   362  	client.lightsailconn = lightsail.New(usEast1Sess)
   363  	client.opsworksconn = opsworks.New(sess)
   364  	client.r53conn = route53.New(usEast1Sess)
   365  	client.rdsconn = rds.New(awsRdsSess)
   366  	client.redshiftconn = redshift.New(sess)
   367  	client.simpledbconn = simpledb.New(sess)
   368  	client.s3conn = s3.New(awsS3Sess)
   369  	client.sesConn = ses.New(sess)
   370  	client.sfnconn = sfn.New(sess)
   371  	client.snsconn = sns.New(awsSnsSess)
   372  	client.sqsconn = sqs.New(awsSqsSess)
   373  	client.ssmconn = ssm.New(sess)
   374  	client.wafconn = waf.New(sess)
   375  
   376  	return &client, nil
   377  }
   378  
   379  // ValidateRegion returns an error if the configured region is not a
   380  // valid aws region and nil otherwise.
   381  func (c *Config) ValidateRegion() error {
   382  	var regions = []string{
   383  		"ap-northeast-1",
   384  		"ap-northeast-2",
   385  		"ap-south-1",
   386  		"ap-southeast-1",
   387  		"ap-southeast-2",
   388  		"ca-central-1",
   389  		"cn-north-1",
   390  		"eu-central-1",
   391  		"eu-west-1",
   392  		"eu-west-2",
   393  		"sa-east-1",
   394  		"us-east-1",
   395  		"us-east-2",
   396  		"us-gov-west-1",
   397  		"us-west-1",
   398  		"us-west-2",
   399  	}
   400  
   401  	for _, valid := range regions {
   402  		if c.Region == valid {
   403  			return nil
   404  		}
   405  	}
   406  	return fmt.Errorf("Not a valid region: %s", c.Region)
   407  }
   408  
   409  // Validate credentials early and fail before we do any graph walking.
   410  func (c *Config) ValidateCredentials(stsconn *sts.STS) error {
   411  	_, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{})
   412  	return err
   413  }
   414  
   415  // ValidateAccountId returns a context-specific error if the configured account
   416  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   417  func (c *Config) ValidateAccountId(accountId string) error {
   418  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   419  		return nil
   420  	}
   421  
   422  	log.Println("[INFO] Validating account ID")
   423  
   424  	if c.ForbiddenAccountIds != nil {
   425  		for _, id := range c.ForbiddenAccountIds {
   426  			if id == accountId {
   427  				return fmt.Errorf("Forbidden account ID (%s)", id)
   428  			}
   429  		}
   430  	}
   431  
   432  	if c.AllowedAccountIds != nil {
   433  		for _, id := range c.AllowedAccountIds {
   434  			if id == accountId {
   435  				return nil
   436  			}
   437  		}
   438  		return fmt.Errorf("Account ID not allowed (%s)", accountId)
   439  	}
   440  
   441  	return nil
   442  }
   443  
   444  func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) {
   445  	attrName := "supported-platforms"
   446  
   447  	input := ec2.DescribeAccountAttributesInput{
   448  		AttributeNames: []*string{aws.String(attrName)},
   449  	}
   450  	attributes, err := conn.DescribeAccountAttributes(&input)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  
   455  	var platforms []string
   456  	for _, attr := range attributes.AccountAttributes {
   457  		if *attr.AttributeName == attrName {
   458  			for _, v := range attr.AttributeValues {
   459  				platforms = append(platforms, *v.AttributeValue)
   460  			}
   461  			break
   462  		}
   463  	}
   464  
   465  	if len(platforms) == 0 {
   466  		return nil, fmt.Errorf("No EC2 platforms detected")
   467  	}
   468  
   469  	return platforms, nil
   470  }
   471  
   472  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   473  // version information to requests made by the AWS SDK.
   474  var addTerraformVersionToUserAgent = request.NamedHandler{
   475  	Name: "terraform.TerraformVersionUserAgentHandler",
   476  	Fn: request.MakeAddToUserAgentHandler(
   477  		"APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()),
   478  }
   479  
   480  var debugAuthFailure = request.NamedHandler{
   481  	Name: "terraform.AuthFailureAdditionalDebugHandler",
   482  	Fn: func(req *request.Request) {
   483  		if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") {
   484  			log.Printf("[INFO] Additional AuthFailure Debugging Context")
   485  			log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC())
   486  			log.Printf("[INFO] Request object: %s", spew.Sdump(req))
   487  		}
   488  	},
   489  }
   490  
   491  type awsLogger struct{}
   492  
   493  func (l awsLogger) Log(args ...interface{}) {
   494  	tokens := make([]string, 0, len(args))
   495  	for _, arg := range args {
   496  		if token, ok := arg.(string); ok {
   497  			tokens = append(tokens, token)
   498  		}
   499  	}
   500  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   501  }