github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/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/autoscaling"
    22  	"github.com/aws/aws-sdk-go/service/cloudformation"
    23  	"github.com/aws/aws-sdk-go/service/cloudfront"
    24  	"github.com/aws/aws-sdk-go/service/cloudtrail"
    25  	"github.com/aws/aws-sdk-go/service/cloudwatch"
    26  	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
    27  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    28  	"github.com/aws/aws-sdk-go/service/codecommit"
    29  	"github.com/aws/aws-sdk-go/service/codedeploy"
    30  	"github.com/aws/aws-sdk-go/service/directoryservice"
    31  	"github.com/aws/aws-sdk-go/service/dynamodb"
    32  	"github.com/aws/aws-sdk-go/service/ec2"
    33  	"github.com/aws/aws-sdk-go/service/ecr"
    34  	"github.com/aws/aws-sdk-go/service/ecs"
    35  	"github.com/aws/aws-sdk-go/service/efs"
    36  	"github.com/aws/aws-sdk-go/service/elasticache"
    37  	"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
    38  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    39  	"github.com/aws/aws-sdk-go/service/elastictranscoder"
    40  	"github.com/aws/aws-sdk-go/service/elb"
    41  	"github.com/aws/aws-sdk-go/service/emr"
    42  	"github.com/aws/aws-sdk-go/service/firehose"
    43  	"github.com/aws/aws-sdk-go/service/glacier"
    44  	"github.com/aws/aws-sdk-go/service/iam"
    45  	"github.com/aws/aws-sdk-go/service/kinesis"
    46  	"github.com/aws/aws-sdk-go/service/kms"
    47  	"github.com/aws/aws-sdk-go/service/lambda"
    48  	"github.com/aws/aws-sdk-go/service/opsworks"
    49  	"github.com/aws/aws-sdk-go/service/rds"
    50  	"github.com/aws/aws-sdk-go/service/redshift"
    51  	"github.com/aws/aws-sdk-go/service/route53"
    52  	"github.com/aws/aws-sdk-go/service/s3"
    53  	"github.com/aws/aws-sdk-go/service/sns"
    54  	"github.com/aws/aws-sdk-go/service/sqs"
    55  	"github.com/aws/aws-sdk-go/service/sts"
    56  )
    57  
    58  type Config struct {
    59  	AccessKey     string
    60  	SecretKey     string
    61  	CredsFilename string
    62  	Profile       string
    63  	Token         string
    64  	Region        string
    65  	MaxRetries    int
    66  
    67  	AllowedAccountIds   []interface{}
    68  	ForbiddenAccountIds []interface{}
    69  
    70  	DynamoDBEndpoint string
    71  	KinesisEndpoint  string
    72  	Ec2Endpoint      string
    73  	IamEndpoint      string
    74  	ElbEndpoint      string
    75  	Insecure         bool
    76  }
    77  
    78  type AWSClient struct {
    79  	cfconn                *cloudformation.CloudFormation
    80  	cloudfrontconn        *cloudfront.CloudFront
    81  	cloudtrailconn        *cloudtrail.CloudTrail
    82  	cloudwatchconn        *cloudwatch.CloudWatch
    83  	cloudwatchlogsconn    *cloudwatchlogs.CloudWatchLogs
    84  	cloudwatcheventsconn  *cloudwatchevents.CloudWatchEvents
    85  	dsconn                *directoryservice.DirectoryService
    86  	dynamodbconn          *dynamodb.DynamoDB
    87  	ec2conn               *ec2.EC2
    88  	ecrconn               *ecr.ECR
    89  	ecsconn               *ecs.ECS
    90  	efsconn               *efs.EFS
    91  	elbconn               *elb.ELB
    92  	emrconn               *emr.EMR
    93  	esconn                *elasticsearch.ElasticsearchService
    94  	apigateway            *apigateway.APIGateway
    95  	autoscalingconn       *autoscaling.AutoScaling
    96  	s3conn                *s3.S3
    97  	sqsconn               *sqs.SQS
    98  	snsconn               *sns.SNS
    99  	stsconn               *sts.STS
   100  	redshiftconn          *redshift.Redshift
   101  	r53conn               *route53.Route53
   102  	accountid             string
   103  	region                string
   104  	rdsconn               *rds.RDS
   105  	iamconn               *iam.IAM
   106  	kinesisconn           *kinesis.Kinesis
   107  	kmsconn               *kms.KMS
   108  	firehoseconn          *firehose.Firehose
   109  	elasticacheconn       *elasticache.ElastiCache
   110  	elasticbeanstalkconn  *elasticbeanstalk.ElasticBeanstalk
   111  	elastictranscoderconn *elastictranscoder.ElasticTranscoder
   112  	lambdaconn            *lambda.Lambda
   113  	opsworksconn          *opsworks.OpsWorks
   114  	glacierconn           *glacier.Glacier
   115  	codedeployconn        *codedeploy.CodeDeploy
   116  	codecommitconn        *codecommit.CodeCommit
   117  }
   118  
   119  // Client configures and returns a fully initialized AWSClient
   120  func (c *Config) Client() (interface{}, error) {
   121  	// Get the auth and region. This can fail if keys/regions were not
   122  	// specified and we're attempting to use the environment.
   123  	var errs []error
   124  
   125  	log.Println("[INFO] Building AWS region structure")
   126  	err := c.ValidateRegion()
   127  	if err != nil {
   128  		errs = append(errs, err)
   129  	}
   130  
   131  	var client AWSClient
   132  	if len(errs) == 0 {
   133  		// store AWS region in client struct, for region specific operations such as
   134  		// bucket storage in S3
   135  		client.region = c.Region
   136  
   137  		log.Println("[INFO] Building AWS auth structure")
   138  		creds := GetCredentials(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename)
   139  		// Call Get to check for credential provider. If nothing found, we'll get an
   140  		// error, and we can present it nicely to the user
   141  		cp, err := creds.Get()
   142  		if err != nil {
   143  			if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
   144  				errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider.
   145    Please see https://terraform.io/docs/providers/aws/index.html for more information on
   146    providing credentials for the AWS Provider`))
   147  			} else {
   148  				errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
   149  			}
   150  			return nil, &multierror.Error{Errors: errs}
   151  		}
   152  
   153  		log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
   154  
   155  		awsConfig := &aws.Config{
   156  			Credentials: creds,
   157  			Region:      aws.String(c.Region),
   158  			MaxRetries:  aws.Int(c.MaxRetries),
   159  			HTTPClient:  cleanhttp.DefaultClient(),
   160  		}
   161  
   162  		if logging.IsDebugOrHigher() {
   163  			awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
   164  			awsConfig.Logger = awsLogger{}
   165  		}
   166  
   167  		if c.Insecure {
   168  			transport := awsConfig.HTTPClient.Transport.(*http.Transport)
   169  			transport.TLSClientConfig = &tls.Config{
   170  				InsecureSkipVerify: true,
   171  			}
   172  		}
   173  
   174  		// Set up base session
   175  		sess := session.New(awsConfig)
   176  		sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
   177  
   178  		log.Println("[INFO] Initializing IAM Connection")
   179  		awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   180  		client.iamconn = iam.New(awsIamSess)
   181  
   182  		log.Println("[INFO] Initializing STS connection")
   183  		client.stsconn = sts.New(sess)
   184  
   185  		err = c.ValidateCredentials(client.iamconn)
   186  		if err != nil {
   187  			errs = append(errs, err)
   188  			return nil, &multierror.Error{Errors: errs}
   189  		}
   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  		accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName)
   199  		if err == nil {
   200  			client.accountid = accountId
   201  		}
   202  
   203  		log.Println("[INFO] Initializing DynamoDB connection")
   204  		dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   205  		client.dynamodbconn = dynamodb.New(dynamoSess)
   206  
   207  		log.Println("[INFO] Initializing Cloudfront connection")
   208  		client.cloudfrontconn = cloudfront.New(sess)
   209  
   210  		log.Println("[INFO] Initializing ELB connection")
   211  		awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   212  		client.elbconn = elb.New(awsElbSess)
   213  
   214  		log.Println("[INFO] Initializing S3 connection")
   215  		client.s3conn = s3.New(sess)
   216  
   217  		log.Println("[INFO] Initializing SQS connection")
   218  		client.sqsconn = sqs.New(sess)
   219  
   220  		log.Println("[INFO] Initializing SNS connection")
   221  		client.snsconn = sns.New(sess)
   222  
   223  		log.Println("[INFO] Initializing RDS Connection")
   224  		client.rdsconn = rds.New(sess)
   225  
   226  		log.Println("[INFO] Initializing Kinesis Connection")
   227  		kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   228  		client.kinesisconn = kinesis.New(kinesisSess)
   229  
   230  		log.Println("[INFO] Initializing Elastic Beanstalk Connection")
   231  		client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   232  
   233  		log.Println("[INFO] Initializing Elastic Transcoder Connection")
   234  		client.elastictranscoderconn = elastictranscoder.New(sess)
   235  
   236  		authErr := c.ValidateAccountId(client.accountid)
   237  		if authErr != nil {
   238  			errs = append(errs, authErr)
   239  		}
   240  
   241  		log.Println("[INFO] Initializing Kinesis Firehose Connection")
   242  		client.firehoseconn = firehose.New(sess)
   243  
   244  		log.Println("[INFO] Initializing AutoScaling connection")
   245  		client.autoscalingconn = autoscaling.New(sess)
   246  
   247  		log.Println("[INFO] Initializing EC2 Connection")
   248  
   249  		awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   250  		client.ec2conn = ec2.New(awsEc2Sess)
   251  
   252  		log.Println("[INFO] Initializing ECR Connection")
   253  		client.ecrconn = ecr.New(sess)
   254  
   255  		log.Println("[INFO] Initializing API Gateway")
   256  		client.apigateway = apigateway.New(sess)
   257  
   258  		log.Println("[INFO] Initializing ECS Connection")
   259  		client.ecsconn = ecs.New(sess)
   260  
   261  		log.Println("[INFO] Initializing EFS Connection")
   262  		client.efsconn = efs.New(sess)
   263  
   264  		log.Println("[INFO] Initializing ElasticSearch Connection")
   265  		client.esconn = elasticsearch.New(sess)
   266  
   267  		log.Println("[INFO] Initializing EMR Connection")
   268  		client.emrconn = emr.New(sess)
   269  
   270  		log.Println("[INFO] Initializing Route 53 connection")
   271  		client.r53conn = route53.New(usEast1Sess)
   272  
   273  		log.Println("[INFO] Initializing Elasticache Connection")
   274  		client.elasticacheconn = elasticache.New(sess)
   275  
   276  		log.Println("[INFO] Initializing Lambda Connection")
   277  		client.lambdaconn = lambda.New(sess)
   278  
   279  		log.Println("[INFO] Initializing Cloudformation Connection")
   280  		client.cfconn = cloudformation.New(sess)
   281  
   282  		log.Println("[INFO] Initializing CloudWatch SDK connection")
   283  		client.cloudwatchconn = cloudwatch.New(sess)
   284  
   285  		log.Println("[INFO] Initializing CloudWatch Events connection")
   286  		client.cloudwatcheventsconn = cloudwatchevents.New(sess)
   287  
   288  		log.Println("[INFO] Initializing CloudTrail connection")
   289  		client.cloudtrailconn = cloudtrail.New(sess)
   290  
   291  		log.Println("[INFO] Initializing CloudWatch Logs connection")
   292  		client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
   293  
   294  		log.Println("[INFO] Initializing OpsWorks Connection")
   295  		client.opsworksconn = opsworks.New(usEast1Sess)
   296  
   297  		log.Println("[INFO] Initializing Directory Service connection")
   298  		client.dsconn = directoryservice.New(sess)
   299  
   300  		log.Println("[INFO] Initializing Glacier connection")
   301  		client.glacierconn = glacier.New(sess)
   302  
   303  		log.Println("[INFO] Initializing CodeDeploy Connection")
   304  		client.codedeployconn = codedeploy.New(sess)
   305  
   306  		log.Println("[INFO] Initializing CodeCommit SDK connection")
   307  		client.codecommitconn = codecommit.New(usEast1Sess)
   308  
   309  		log.Println("[INFO] Initializing Redshift SDK connection")
   310  		client.redshiftconn = redshift.New(sess)
   311  
   312  		log.Println("[INFO] Initializing KMS connection")
   313  		client.kmsconn = kms.New(sess)
   314  	}
   315  
   316  	if len(errs) > 0 {
   317  		return nil, &multierror.Error{Errors: errs}
   318  	}
   319  
   320  	return &client, nil
   321  }
   322  
   323  // ValidateRegion returns an error if the configured region is not a
   324  // valid aws region and nil otherwise.
   325  func (c *Config) ValidateRegion() error {
   326  	var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
   327  		"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
   328  		"ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"}
   329  
   330  	for _, valid := range regions {
   331  		if c.Region == valid {
   332  			return nil
   333  		}
   334  	}
   335  	return fmt.Errorf("Not a valid region: %s", c.Region)
   336  }
   337  
   338  // Validate credentials early and fail before we do any graph walking.
   339  // In the case of an IAM role/profile with insuffecient privileges, fail
   340  // silently
   341  func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
   342  	_, err := iamconn.GetUser(nil)
   343  
   344  	if awsErr, ok := err.(awserr.Error); ok {
   345  		if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" {
   346  			log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM role")
   347  			// User may be an IAM instance profile, or otherwise IAM role without the
   348  			// GetUser permissions, so fail silently
   349  			return nil
   350  		}
   351  
   352  		if awsErr.Code() == "SignatureDoesNotMatch" {
   353  			return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
   354  		}
   355  	}
   356  
   357  	return err
   358  }
   359  
   360  // ValidateAccountId returns a context-specific error if the configured account
   361  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   362  func (c *Config) ValidateAccountId(accountId string) error {
   363  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   364  		return nil
   365  	}
   366  
   367  	log.Printf("[INFO] Validating account ID")
   368  
   369  	if c.ForbiddenAccountIds != nil {
   370  		for _, id := range c.ForbiddenAccountIds {
   371  			if id == accountId {
   372  				return fmt.Errorf("Forbidden account ID (%s)", id)
   373  			}
   374  		}
   375  	}
   376  
   377  	if c.AllowedAccountIds != nil {
   378  		for _, id := range c.AllowedAccountIds {
   379  			if id == accountId {
   380  				return nil
   381  			}
   382  		}
   383  		return fmt.Errorf("Account ID not allowed (%s)", accountId)
   384  	}
   385  
   386  	return nil
   387  }
   388  
   389  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   390  // version information to requests made by the AWS SDK.
   391  var addTerraformVersionToUserAgent = request.NamedHandler{
   392  	Name: "terraform.TerraformVersionUserAgentHandler",
   393  	Fn: request.MakeAddToUserAgentHandler(
   394  		"terraform", terraform.Version, terraform.VersionPrerelease),
   395  }
   396  
   397  type awsLogger struct{}
   398  
   399  func (l awsLogger) Log(args ...interface{}) {
   400  	tokens := make([]string, 0, len(args))
   401  	for _, arg := range args {
   402  		if token, ok := arg.(string); ok {
   403  			tokens = append(tokens, token)
   404  		}
   405  	}
   406  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   407  }