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