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