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