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