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