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