github.com/kwoods/terraform@v0.6.11-0.20160809170336-13497db7138e/builtin/providers/aws/config.go (about)

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