github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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  	// Removes the SDK Version handler, so we only have the provider User-Agent
   229  	// Ex: "User-Agent: APN/1.0 HashiCorp/1.0 Terraform/0.7.9-dev"
   230  	sess.Handlers.Build.Remove(request.NamedHandler{Name: "core.SDKVersionUserAgentHandler"})
   231  	sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
   232  
   233  	if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" {
   234  		sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure)
   235  	}
   236  
   237  	// Some services exist only in us-east-1, e.g. because they manage
   238  	// resources that can span across multiple regions, or because
   239  	// signature format v4 requires region to be us-east-1 for global
   240  	// endpoints:
   241  	// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   242  	usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
   243  
   244  	// Some services have user-configurable endpoints
   245  	awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   246  	awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   247  	awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   248  	awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
   249  	dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   250  	kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   251  
   252  	// These two services need to be set up early so we can check on AccountID
   253  	client.iamconn = iam.New(awsIamSess)
   254  	client.stsconn = sts.New(sess)
   255  
   256  	if !c.SkipCredsValidation {
   257  		err = c.ValidateCredentials(client.stsconn)
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  	}
   262  
   263  	if !c.SkipRequestingAccountId {
   264  		partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName)
   265  		if err == nil {
   266  			client.partition = partition
   267  			client.accountid = accountId
   268  		}
   269  	}
   270  
   271  	authErr := c.ValidateAccountId(client.accountid)
   272  	if authErr != nil {
   273  		return nil, authErr
   274  	}
   275  
   276  	client.ec2conn = ec2.New(awsEc2Sess)
   277  
   278  	supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn)
   279  	if err != nil {
   280  		// We intentionally fail *silently* because there's a chance
   281  		// user just doesn't have ec2:DescribeAccountAttributes permissions
   282  		log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err)
   283  	} else {
   284  		client.supportedplatforms = supportedPlatforms
   285  	}
   286  
   287  	client.acmconn = acm.New(sess)
   288  	client.apigateway = apigateway.New(sess)
   289  	client.appautoscalingconn = applicationautoscaling.New(sess)
   290  	client.autoscalingconn = autoscaling.New(sess)
   291  	client.cfconn = cloudformation.New(sess)
   292  	client.cloudfrontconn = cloudfront.New(sess)
   293  	client.cloudtrailconn = cloudtrail.New(sess)
   294  	client.cloudwatchconn = cloudwatch.New(sess)
   295  	client.cloudwatcheventsconn = cloudwatchevents.New(sess)
   296  	client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
   297  	client.codecommitconn = codecommit.New(sess)
   298  	client.codebuildconn = codebuild.New(sess)
   299  	client.codedeployconn = codedeploy.New(sess)
   300  	client.configconn = configservice.New(sess)
   301  	client.dmsconn = databasemigrationservice.New(sess)
   302  	client.codepipelineconn = codepipeline.New(sess)
   303  	client.dsconn = directoryservice.New(sess)
   304  	client.dynamodbconn = dynamodb.New(dynamoSess)
   305  	client.ecrconn = ecr.New(sess)
   306  	client.ecsconn = ecs.New(sess)
   307  	client.efsconn = efs.New(sess)
   308  	client.elasticacheconn = elasticache.New(sess)
   309  	client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   310  	client.elastictranscoderconn = elastictranscoder.New(sess)
   311  	client.elbconn = elb.New(awsElbSess)
   312  	client.elbv2conn = elbv2.New(awsElbSess)
   313  	client.emrconn = emr.New(sess)
   314  	client.esconn = elasticsearch.New(sess)
   315  	client.firehoseconn = firehose.New(sess)
   316  	client.inspectorconn = inspector.New(sess)
   317  	client.glacierconn = glacier.New(sess)
   318  	client.kinesisconn = kinesis.New(kinesisSess)
   319  	client.kmsconn = kms.New(sess)
   320  	client.lambdaconn = lambda.New(sess)
   321  	client.lightsailconn = lightsail.New(usEast1Sess)
   322  	client.opsworksconn = opsworks.New(sess)
   323  	client.r53conn = route53.New(usEast1Sess)
   324  	client.rdsconn = rds.New(sess)
   325  	client.redshiftconn = redshift.New(sess)
   326  	client.simpledbconn = simpledb.New(sess)
   327  	client.s3conn = s3.New(awsS3Sess)
   328  	client.sesConn = ses.New(sess)
   329  	client.sfnconn = sfn.New(sess)
   330  	client.snsconn = sns.New(sess)
   331  	client.sqsconn = sqs.New(sess)
   332  	client.ssmconn = ssm.New(sess)
   333  	client.wafconn = waf.New(sess)
   334  
   335  	return &client, nil
   336  }
   337  
   338  // ValidateRegion returns an error if the configured region is not a
   339  // valid aws region and nil otherwise.
   340  func (c *Config) ValidateRegion() error {
   341  	var regions = []string{
   342  		"ap-northeast-1",
   343  		"ap-northeast-2",
   344  		"ap-south-1",
   345  		"ap-southeast-1",
   346  		"ap-southeast-2",
   347  		"ca-central-1",
   348  		"cn-north-1",
   349  		"eu-central-1",
   350  		"eu-west-1",
   351  		"eu-west-2",
   352  		"sa-east-1",
   353  		"us-east-1",
   354  		"us-east-2",
   355  		"us-gov-west-1",
   356  		"us-west-1",
   357  		"us-west-2",
   358  	}
   359  
   360  	for _, valid := range regions {
   361  		if c.Region == valid {
   362  			return nil
   363  		}
   364  	}
   365  	return fmt.Errorf("Not a valid region: %s", c.Region)
   366  }
   367  
   368  // Validate credentials early and fail before we do any graph walking.
   369  func (c *Config) ValidateCredentials(stsconn *sts.STS) error {
   370  	_, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{})
   371  	return err
   372  }
   373  
   374  // ValidateAccountId returns a context-specific error if the configured account
   375  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   376  func (c *Config) ValidateAccountId(accountId string) error {
   377  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   378  		return nil
   379  	}
   380  
   381  	log.Println("[INFO] Validating account ID")
   382  
   383  	if c.ForbiddenAccountIds != nil {
   384  		for _, id := range c.ForbiddenAccountIds {
   385  			if id == accountId {
   386  				return fmt.Errorf("Forbidden account ID (%s)", id)
   387  			}
   388  		}
   389  	}
   390  
   391  	if c.AllowedAccountIds != nil {
   392  		for _, id := range c.AllowedAccountIds {
   393  			if id == accountId {
   394  				return nil
   395  			}
   396  		}
   397  		return fmt.Errorf("Account ID not allowed (%s)", accountId)
   398  	}
   399  
   400  	return nil
   401  }
   402  
   403  func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) {
   404  	attrName := "supported-platforms"
   405  
   406  	input := ec2.DescribeAccountAttributesInput{
   407  		AttributeNames: []*string{aws.String(attrName)},
   408  	}
   409  	attributes, err := conn.DescribeAccountAttributes(&input)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	var platforms []string
   415  	for _, attr := range attributes.AccountAttributes {
   416  		if *attr.AttributeName == attrName {
   417  			for _, v := range attr.AttributeValues {
   418  				platforms = append(platforms, *v.AttributeValue)
   419  			}
   420  			break
   421  		}
   422  	}
   423  
   424  	if len(platforms) == 0 {
   425  		return nil, fmt.Errorf("No EC2 platforms detected")
   426  	}
   427  
   428  	return platforms, nil
   429  }
   430  
   431  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   432  // version information to requests made by the AWS SDK.
   433  var addTerraformVersionToUserAgent = request.NamedHandler{
   434  	Name: "terraform.TerraformVersionUserAgentHandler",
   435  	Fn: request.MakeAddToUserAgentHandler(
   436  		"APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()),
   437  }
   438  
   439  var debugAuthFailure = request.NamedHandler{
   440  	Name: "terraform.AuthFailureAdditionalDebugHandler",
   441  	Fn: func(req *request.Request) {
   442  		if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") {
   443  			log.Printf("[INFO] Additional AuthFailure Debugging Context")
   444  			log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC())
   445  			log.Printf("[INFO] Request object: %s", spew.Sdump(req))
   446  		}
   447  	},
   448  }
   449  
   450  type awsLogger struct{}
   451  
   452  func (l awsLogger) Log(args ...interface{}) {
   453  	tokens := make([]string, 0, len(args))
   454  	for _, arg := range args {
   455  		if token, ok := arg.(string); ok {
   456  			tokens = append(tokens, token)
   457  		}
   458  	}
   459  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   460  }