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