github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/builtin/providers/aws/config.go (about)

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