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