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