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