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