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