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