github.com/IBM-Cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/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  	// Some services exist only in us-east-1, e.g. because they manage
   273  	// resources that can span across multiple regions, or because
   274  	// signature format v4 requires region to be us-east-1 for global
   275  	// endpoints:
   276  	// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   277  	usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
   278  
   279  	// Some services have user-configurable endpoints
   280  	awsCfSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudFormationEndpoint)})
   281  	awsCwSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEndpoint)})
   282  	awsCweSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEventsEndpoint)})
   283  	awsCwlSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchLogsEndpoint)})
   284  	awsDynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   285  	awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   286  	awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   287  	awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   288  	awsKinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   289  	awsKmsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KmsEndpoint)})
   290  	awsRdsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.RdsEndpoint)})
   291  	awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
   292  	awsSnsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SnsEndpoint)})
   293  	awsSqsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SqsEndpoint)})
   294  	awsDeviceFarmSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DeviceFarmEndpoint)})
   295  
   296  	log.Println("[INFO] Initializing DeviceFarm SDK connection")
   297  	client.devicefarmconn = devicefarm.New(awsDeviceFarmSess)
   298  
   299  	// These two services need to be set up early so we can check on AccountID
   300  	client.iamconn = iam.New(awsIamSess)
   301  	client.stsconn = sts.New(sess)
   302  
   303  	if !c.SkipCredsValidation {
   304  		err = c.ValidateCredentials(client.stsconn)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  	}
   309  
   310  	if !c.SkipRequestingAccountId {
   311  		partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName)
   312  		if err == nil {
   313  			client.partition = partition
   314  			client.accountid = accountId
   315  		}
   316  	}
   317  
   318  	authErr := c.ValidateAccountId(client.accountid)
   319  	if authErr != nil {
   320  		return nil, authErr
   321  	}
   322  
   323  	client.ec2conn = ec2.New(awsEc2Sess)
   324  
   325  	if !c.SkipGetEC2Platforms {
   326  		supportedPlatforms, err := GetSupportedEC2Platforms(client.ec2conn)
   327  		if err != nil {
   328  			// We intentionally fail *silently* because there's a chance
   329  			// user just doesn't have ec2:DescribeAccountAttributes permissions
   330  			log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err)
   331  		} else {
   332  			client.supportedplatforms = supportedPlatforms
   333  		}
   334  	}
   335  
   336  	client.acmconn = acm.New(sess)
   337  	client.apigateway = apigateway.New(sess)
   338  	client.appautoscalingconn = applicationautoscaling.New(sess)
   339  	client.autoscalingconn = autoscaling.New(sess)
   340  	client.cfconn = cloudformation.New(awsCfSess)
   341  	client.cloudfrontconn = cloudfront.New(sess)
   342  	client.cloudtrailconn = cloudtrail.New(sess)
   343  	client.cloudwatchconn = cloudwatch.New(awsCwSess)
   344  	client.cloudwatcheventsconn = cloudwatchevents.New(awsCweSess)
   345  	client.cloudwatchlogsconn = cloudwatchlogs.New(awsCwlSess)
   346  	client.codecommitconn = codecommit.New(sess)
   347  	client.codebuildconn = codebuild.New(sess)
   348  	client.codedeployconn = codedeploy.New(sess)
   349  	client.configconn = configservice.New(sess)
   350  	client.cognitoconn = cognitoidentity.New(sess)
   351  	client.dmsconn = databasemigrationservice.New(sess)
   352  	client.codepipelineconn = codepipeline.New(sess)
   353  	client.dsconn = directoryservice.New(sess)
   354  	client.dynamodbconn = dynamodb.New(awsDynamoSess)
   355  	client.ecrconn = ecr.New(sess)
   356  	client.ecsconn = ecs.New(sess)
   357  	client.efsconn = efs.New(sess)
   358  	client.elasticacheconn = elasticache.New(sess)
   359  	client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   360  	client.elastictranscoderconn = elastictranscoder.New(sess)
   361  	client.elbconn = elb.New(awsElbSess)
   362  	client.elbv2conn = elbv2.New(awsElbSess)
   363  	client.emrconn = emr.New(sess)
   364  	client.esconn = elasticsearch.New(sess)
   365  	client.firehoseconn = firehose.New(sess)
   366  	client.inspectorconn = inspector.New(sess)
   367  	client.glacierconn = glacier.New(sess)
   368  	client.kinesisconn = kinesis.New(awsKinesisSess)
   369  	client.kmsconn = kms.New(awsKmsSess)
   370  	client.lambdaconn = lambda.New(sess)
   371  	client.lightsailconn = lightsail.New(usEast1Sess)
   372  	client.opsworksconn = opsworks.New(sess)
   373  	client.r53conn = route53.New(usEast1Sess)
   374  	client.rdsconn = rds.New(awsRdsSess)
   375  	client.redshiftconn = redshift.New(sess)
   376  	client.simpledbconn = simpledb.New(sess)
   377  	client.s3conn = s3.New(awsS3Sess)
   378  	client.sesConn = ses.New(sess)
   379  	client.sfnconn = sfn.New(sess)
   380  	client.snsconn = sns.New(awsSnsSess)
   381  	client.sqsconn = sqs.New(awsSqsSess)
   382  	client.ssmconn = ssm.New(sess)
   383  	client.wafconn = waf.New(sess)
   384  	client.wafregionalconn = wafregional.New(sess)
   385  
   386  	return &client, nil
   387  }
   388  
   389  // ValidateRegion returns an error if the configured region is not a
   390  // valid aws region and nil otherwise.
   391  func (c *Config) ValidateRegion() error {
   392  	var regions = []string{
   393  		"ap-northeast-1",
   394  		"ap-northeast-2",
   395  		"ap-south-1",
   396  		"ap-southeast-1",
   397  		"ap-southeast-2",
   398  		"ca-central-1",
   399  		"cn-north-1",
   400  		"eu-central-1",
   401  		"eu-west-1",
   402  		"eu-west-2",
   403  		"sa-east-1",
   404  		"us-east-1",
   405  		"us-east-2",
   406  		"us-gov-west-1",
   407  		"us-west-1",
   408  		"us-west-2",
   409  	}
   410  
   411  	for _, valid := range regions {
   412  		if c.Region == valid {
   413  			return nil
   414  		}
   415  	}
   416  	return fmt.Errorf("Not a valid region: %s", c.Region)
   417  }
   418  
   419  // Validate credentials early and fail before we do any graph walking.
   420  func (c *Config) ValidateCredentials(stsconn *sts.STS) error {
   421  	_, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{})
   422  	return err
   423  }
   424  
   425  // ValidateAccountId returns a context-specific error if the configured account
   426  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   427  func (c *Config) ValidateAccountId(accountId string) error {
   428  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   429  		return nil
   430  	}
   431  
   432  	log.Println("[INFO] Validating account ID")
   433  
   434  	if c.ForbiddenAccountIds != nil {
   435  		for _, id := range c.ForbiddenAccountIds {
   436  			if id == accountId {
   437  				return fmt.Errorf("Forbidden account ID (%s)", id)
   438  			}
   439  		}
   440  	}
   441  
   442  	if c.AllowedAccountIds != nil {
   443  		for _, id := range c.AllowedAccountIds {
   444  			if id == accountId {
   445  				return nil
   446  			}
   447  		}
   448  		return fmt.Errorf("Account ID not allowed (%s)", accountId)
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) {
   455  	attrName := "supported-platforms"
   456  
   457  	input := ec2.DescribeAccountAttributesInput{
   458  		AttributeNames: []*string{aws.String(attrName)},
   459  	}
   460  	attributes, err := conn.DescribeAccountAttributes(&input)
   461  	if err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	var platforms []string
   466  	for _, attr := range attributes.AccountAttributes {
   467  		if *attr.AttributeName == attrName {
   468  			for _, v := range attr.AttributeValues {
   469  				platforms = append(platforms, *v.AttributeValue)
   470  			}
   471  			break
   472  		}
   473  	}
   474  
   475  	if len(platforms) == 0 {
   476  		return nil, fmt.Errorf("No EC2 platforms detected")
   477  	}
   478  
   479  	return platforms, nil
   480  }
   481  
   482  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   483  // version information to requests made by the AWS SDK.
   484  var addTerraformVersionToUserAgent = request.NamedHandler{
   485  	Name: "terraform.TerraformVersionUserAgentHandler",
   486  	Fn: request.MakeAddToUserAgentHandler(
   487  		"APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()),
   488  }
   489  
   490  var debugAuthFailure = request.NamedHandler{
   491  	Name: "terraform.AuthFailureAdditionalDebugHandler",
   492  	Fn: func(req *request.Request) {
   493  		if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") {
   494  			log.Printf("[INFO] Additional AuthFailure Debugging Context")
   495  			log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC())
   496  			log.Printf("[INFO] Request object: %s", spew.Sdump(req))
   497  		}
   498  	},
   499  }
   500  
   501  type awsLogger struct{}
   502  
   503  func (l awsLogger) Log(args ...interface{}) {
   504  	tokens := make([]string, 0, len(args))
   505  	for _, arg := range args {
   506  		if token, ok := arg.(string); ok {
   507  			tokens = append(tokens, token)
   508  		}
   509  	}
   510  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   511  }