github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/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/codecommit"
    28  	"github.com/aws/aws-sdk-go/service/codedeploy"
    29  	"github.com/aws/aws-sdk-go/service/directoryservice"
    30  	"github.com/aws/aws-sdk-go/service/dynamodb"
    31  	"github.com/aws/aws-sdk-go/service/ec2"
    32  	"github.com/aws/aws-sdk-go/service/ecr"
    33  	"github.com/aws/aws-sdk-go/service/ecs"
    34  	"github.com/aws/aws-sdk-go/service/efs"
    35  	"github.com/aws/aws-sdk-go/service/elasticache"
    36  	"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
    37  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    38  	"github.com/aws/aws-sdk-go/service/elastictranscoder"
    39  	"github.com/aws/aws-sdk-go/service/elb"
    40  	"github.com/aws/aws-sdk-go/service/elbv2"
    41  	"github.com/aws/aws-sdk-go/service/emr"
    42  	"github.com/aws/aws-sdk-go/service/firehose"
    43  	"github.com/aws/aws-sdk-go/service/glacier"
    44  	"github.com/aws/aws-sdk-go/service/iam"
    45  	"github.com/aws/aws-sdk-go/service/inspector"
    46  	"github.com/aws/aws-sdk-go/service/kinesis"
    47  	"github.com/aws/aws-sdk-go/service/kms"
    48  	"github.com/aws/aws-sdk-go/service/lambda"
    49  	"github.com/aws/aws-sdk-go/service/lightsail"
    50  	"github.com/aws/aws-sdk-go/service/opsworks"
    51  	"github.com/aws/aws-sdk-go/service/rds"
    52  	"github.com/aws/aws-sdk-go/service/redshift"
    53  	"github.com/aws/aws-sdk-go/service/route53"
    54  	"github.com/aws/aws-sdk-go/service/s3"
    55  	"github.com/aws/aws-sdk-go/service/ses"
    56  	"github.com/aws/aws-sdk-go/service/sfn"
    57  	"github.com/aws/aws-sdk-go/service/simpledb"
    58  	"github.com/aws/aws-sdk-go/service/sns"
    59  	"github.com/aws/aws-sdk-go/service/sqs"
    60  	"github.com/aws/aws-sdk-go/service/ssm"
    61  	"github.com/aws/aws-sdk-go/service/sts"
    62  	"github.com/aws/aws-sdk-go/service/waf"
    63  	"github.com/davecgh/go-spew/spew"
    64  	"github.com/hashicorp/errwrap"
    65  	"github.com/hashicorp/go-cleanhttp"
    66  	"github.com/hashicorp/terraform/helper/logging"
    67  	"github.com/hashicorp/terraform/terraform"
    68  )
    69  
    70  type Config struct {
    71  	AccessKey     string
    72  	SecretKey     string
    73  	CredsFilename string
    74  	Profile       string
    75  	Token         string
    76  	Region        string
    77  	MaxRetries    int
    78  
    79  	AssumeRoleARN         string
    80  	AssumeRoleExternalID  string
    81  	AssumeRoleSessionName string
    82  
    83  	AllowedAccountIds   []interface{}
    84  	ForbiddenAccountIds []interface{}
    85  
    86  	DynamoDBEndpoint string
    87  	KinesisEndpoint  string
    88  	Ec2Endpoint      string
    89  	IamEndpoint      string
    90  	ElbEndpoint      string
    91  	S3Endpoint       string
    92  	Insecure         bool
    93  
    94  	SkipCredsValidation     bool
    95  	SkipRegionValidation    bool
    96  	SkipRequestingAccountId bool
    97  	SkipMetadataApiCheck    bool
    98  	S3ForcePathStyle        bool
    99  }
   100  
   101  type AWSClient struct {
   102  	cfconn                *cloudformation.CloudFormation
   103  	cloudfrontconn        *cloudfront.CloudFront
   104  	cloudtrailconn        *cloudtrail.CloudTrail
   105  	cloudwatchconn        *cloudwatch.CloudWatch
   106  	cloudwatchlogsconn    *cloudwatchlogs.CloudWatchLogs
   107  	cloudwatcheventsconn  *cloudwatchevents.CloudWatchEvents
   108  	dsconn                *directoryservice.DirectoryService
   109  	dynamodbconn          *dynamodb.DynamoDB
   110  	ec2conn               *ec2.EC2
   111  	ecrconn               *ecr.ECR
   112  	ecsconn               *ecs.ECS
   113  	efsconn               *efs.EFS
   114  	elbconn               *elb.ELB
   115  	elbv2conn             *elbv2.ELBV2
   116  	emrconn               *emr.EMR
   117  	esconn                *elasticsearch.ElasticsearchService
   118  	acmconn               *acm.ACM
   119  	apigateway            *apigateway.APIGateway
   120  	appautoscalingconn    *applicationautoscaling.ApplicationAutoScaling
   121  	autoscalingconn       *autoscaling.AutoScaling
   122  	s3conn                *s3.S3
   123  	sesConn               *ses.SES
   124  	simpledbconn          *simpledb.SimpleDB
   125  	sqsconn               *sqs.SQS
   126  	snsconn               *sns.SNS
   127  	stsconn               *sts.STS
   128  	redshiftconn          *redshift.Redshift
   129  	r53conn               *route53.Route53
   130  	partition             string
   131  	accountid             string
   132  	region                string
   133  	rdsconn               *rds.RDS
   134  	iamconn               *iam.IAM
   135  	kinesisconn           *kinesis.Kinesis
   136  	kmsconn               *kms.KMS
   137  	firehoseconn          *firehose.Firehose
   138  	inspectorconn         *inspector.Inspector
   139  	elasticacheconn       *elasticache.ElastiCache
   140  	elasticbeanstalkconn  *elasticbeanstalk.ElasticBeanstalk
   141  	elastictranscoderconn *elastictranscoder.ElasticTranscoder
   142  	lambdaconn            *lambda.Lambda
   143  	lightsailconn         *lightsail.Lightsail
   144  	opsworksconn          *opsworks.OpsWorks
   145  	glacierconn           *glacier.Glacier
   146  	codedeployconn        *codedeploy.CodeDeploy
   147  	codecommitconn        *codecommit.CodeCommit
   148  	sfnconn               *sfn.SFN
   149  	ssmconn               *ssm.SSM
   150  	wafconn               *waf.WAF
   151  }
   152  
   153  // Client configures and returns a fully initialized AWSClient
   154  func (c *Config) Client() (interface{}, error) {
   155  	// Get the auth and region. This can fail if keys/regions were not
   156  	// specified and we're attempting to use the environment.
   157  	if c.SkipRegionValidation {
   158  		log.Println("[INFO] Skipping region validation")
   159  	} else {
   160  		log.Println("[INFO] Building AWS region structure")
   161  		err := c.ValidateRegion()
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  	}
   166  
   167  	var client AWSClient
   168  	// store AWS region in client struct, for region specific operations such as
   169  	// bucket storage in S3
   170  	client.region = c.Region
   171  
   172  	log.Println("[INFO] Building AWS auth structure")
   173  	creds, err := GetCredentials(c)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	// Call Get to check for credential provider. If nothing found, we'll get an
   178  	// error, and we can present it nicely to the user
   179  	cp, err := creds.Get()
   180  	if err != nil {
   181  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
   182  			return nil, errors.New(`No valid credential sources found for AWS Provider.
   183    Please see https://terraform.io/docs/providers/aws/index.html for more information on
   184    providing credentials for the AWS Provider`)
   185  		}
   186  
   187  		return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
   188  	}
   189  
   190  	log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
   191  
   192  	awsConfig := &aws.Config{
   193  		Credentials:      creds,
   194  		Region:           aws.String(c.Region),
   195  		MaxRetries:       aws.Int(c.MaxRetries),
   196  		HTTPClient:       cleanhttp.DefaultClient(),
   197  		S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
   198  	}
   199  
   200  	if logging.IsDebugOrHigher() {
   201  		awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
   202  		awsConfig.Logger = awsLogger{}
   203  	}
   204  
   205  	if c.Insecure {
   206  		transport := awsConfig.HTTPClient.Transport.(*http.Transport)
   207  		transport.TLSClientConfig = &tls.Config{
   208  			InsecureSkipVerify: true,
   209  		}
   210  	}
   211  
   212  	// Set up base session
   213  	sess, err := session.NewSession(awsConfig)
   214  	if err != nil {
   215  		return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
   216  	}
   217  
   218  	// Removes the SDK Version handler, so we only have the provider User-Agent
   219  	// Ex: "User-Agent: APN/1.0 HashiCorp/1.0 Terraform/0.7.9-dev"
   220  	sess.Handlers.Build.Remove(request.NamedHandler{Name: "core.SDKVersionUserAgentHandler"})
   221  	sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
   222  
   223  	if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" {
   224  		sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure)
   225  	}
   226  
   227  	// Some services exist only in us-east-1, e.g. because they manage
   228  	// resources that can span across multiple regions, or because
   229  	// signature format v4 requires region to be us-east-1 for global
   230  	// endpoints:
   231  	// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   232  	usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
   233  
   234  	// Some services have user-configurable endpoints
   235  	awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   236  	awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   237  	awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   238  	awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
   239  	dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   240  	kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   241  
   242  	// These two services need to be set up early so we can check on AccountID
   243  	client.iamconn = iam.New(awsIamSess)
   244  	client.stsconn = sts.New(sess)
   245  
   246  	if !c.SkipCredsValidation {
   247  		err = c.ValidateCredentials(client.stsconn)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  	}
   252  
   253  	if !c.SkipRequestingAccountId {
   254  		partition, accountId, err := GetAccountInfo(client.iamconn, client.stsconn, cp.ProviderName)
   255  		if err == nil {
   256  			client.partition = partition
   257  			client.accountid = accountId
   258  		}
   259  	}
   260  
   261  	authErr := c.ValidateAccountId(client.accountid)
   262  	if authErr != nil {
   263  		return nil, authErr
   264  	}
   265  
   266  	client.acmconn = acm.New(sess)
   267  	client.apigateway = apigateway.New(sess)
   268  	client.appautoscalingconn = applicationautoscaling.New(sess)
   269  	client.autoscalingconn = autoscaling.New(sess)
   270  	client.cfconn = cloudformation.New(sess)
   271  	client.cloudfrontconn = cloudfront.New(sess)
   272  	client.cloudtrailconn = cloudtrail.New(sess)
   273  	client.cloudwatchconn = cloudwatch.New(sess)
   274  	client.cloudwatcheventsconn = cloudwatchevents.New(sess)
   275  	client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
   276  	client.codecommitconn = codecommit.New(sess)
   277  	client.codedeployconn = codedeploy.New(sess)
   278  	client.dsconn = directoryservice.New(sess)
   279  	client.dynamodbconn = dynamodb.New(dynamoSess)
   280  	client.ec2conn = ec2.New(awsEc2Sess)
   281  	client.ecrconn = ecr.New(sess)
   282  	client.ecsconn = ecs.New(sess)
   283  	client.efsconn = efs.New(sess)
   284  	client.elasticacheconn = elasticache.New(sess)
   285  	client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   286  	client.elastictranscoderconn = elastictranscoder.New(sess)
   287  	client.elbconn = elb.New(awsElbSess)
   288  	client.elbv2conn = elbv2.New(awsElbSess)
   289  	client.emrconn = emr.New(sess)
   290  	client.esconn = elasticsearch.New(sess)
   291  	client.firehoseconn = firehose.New(sess)
   292  	client.inspectorconn = inspector.New(sess)
   293  	client.glacierconn = glacier.New(sess)
   294  	client.kinesisconn = kinesis.New(kinesisSess)
   295  	client.kmsconn = kms.New(sess)
   296  	client.lambdaconn = lambda.New(sess)
   297  	client.lightsailconn = lightsail.New(usEast1Sess)
   298  	client.opsworksconn = opsworks.New(usEast1Sess)
   299  	client.r53conn = route53.New(usEast1Sess)
   300  	client.rdsconn = rds.New(sess)
   301  	client.redshiftconn = redshift.New(sess)
   302  	client.simpledbconn = simpledb.New(sess)
   303  	client.s3conn = s3.New(awsS3Sess)
   304  	client.sesConn = ses.New(sess)
   305  	client.sfnconn = sfn.New(sess)
   306  	client.snsconn = sns.New(sess)
   307  	client.sqsconn = sqs.New(sess)
   308  	client.ssmconn = ssm.New(sess)
   309  	client.wafconn = waf.New(sess)
   310  
   311  	return &client, nil
   312  }
   313  
   314  // ValidateRegion returns an error if the configured region is not a
   315  // valid aws region and nil otherwise.
   316  func (c *Config) ValidateRegion() error {
   317  	var regions = []string{
   318  		"ap-northeast-1",
   319  		"ap-northeast-2",
   320  		"ap-south-1",
   321  		"ap-southeast-1",
   322  		"ap-southeast-2",
   323  		"ca-central-1",
   324  		"cn-north-1",
   325  		"eu-central-1",
   326  		"eu-west-1",
   327  		"eu-west-2",
   328  		"sa-east-1",
   329  		"us-east-1",
   330  		"us-east-2",
   331  		"us-gov-west-1",
   332  		"us-west-1",
   333  		"us-west-2",
   334  	}
   335  
   336  	for _, valid := range regions {
   337  		if c.Region == valid {
   338  			return nil
   339  		}
   340  	}
   341  	return fmt.Errorf("Not a valid region: %s", c.Region)
   342  }
   343  
   344  // Validate credentials early and fail before we do any graph walking.
   345  func (c *Config) ValidateCredentials(stsconn *sts.STS) error {
   346  	_, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{})
   347  	return err
   348  }
   349  
   350  // ValidateAccountId returns a context-specific error if the configured account
   351  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   352  func (c *Config) ValidateAccountId(accountId string) error {
   353  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   354  		return nil
   355  	}
   356  
   357  	log.Println("[INFO] Validating account ID")
   358  
   359  	if c.ForbiddenAccountIds != nil {
   360  		for _, id := range c.ForbiddenAccountIds {
   361  			if id == accountId {
   362  				return fmt.Errorf("Forbidden account ID (%s)", id)
   363  			}
   364  		}
   365  	}
   366  
   367  	if c.AllowedAccountIds != nil {
   368  		for _, id := range c.AllowedAccountIds {
   369  			if id == accountId {
   370  				return nil
   371  			}
   372  		}
   373  		return fmt.Errorf("Account ID not allowed (%s)", accountId)
   374  	}
   375  
   376  	return nil
   377  }
   378  
   379  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   380  // version information to requests made by the AWS SDK.
   381  var addTerraformVersionToUserAgent = request.NamedHandler{
   382  	Name: "terraform.TerraformVersionUserAgentHandler",
   383  	Fn: request.MakeAddToUserAgentHandler(
   384  		"APN/1.0 HashiCorp/1.0 Terraform", terraform.VersionString()),
   385  }
   386  
   387  var debugAuthFailure = request.NamedHandler{
   388  	Name: "terraform.AuthFailureAdditionalDebugHandler",
   389  	Fn: func(req *request.Request) {
   390  		if isAWSErr(req.Error, "AuthFailure", "AWS was not able to validate the provided access credentials") {
   391  			log.Printf("[INFO] Additional AuthFailure Debugging Context")
   392  			log.Printf("[INFO] Current system UTC time: %s", time.Now().UTC())
   393  			log.Printf("[INFO] Request object: %s", spew.Sdump(req))
   394  		}
   395  	},
   396  }
   397  
   398  type awsLogger struct{}
   399  
   400  func (l awsLogger) Log(args ...interface{}) {
   401  	tokens := make([]string, 0, len(args))
   402  	for _, arg := range args {
   403  		if token, ok := arg.(string); ok {
   404  			tokens = append(tokens, token)
   405  		}
   406  	}
   407  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   408  }