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