github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/aws/config.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/http"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-cleanhttp"
    12  	"github.com/hashicorp/go-multierror"
    13  	"github.com/hashicorp/terraform/helper/logging"
    14  	"github.com/hashicorp/terraform/terraform"
    15  
    16  	"crypto/tls"
    17  
    18  	"github.com/aws/aws-sdk-go/aws"
    19  	"github.com/aws/aws-sdk-go/aws/awserr"
    20  	awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
    21  	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
    22  	"github.com/aws/aws-sdk-go/aws/ec2metadata"
    23  	"github.com/aws/aws-sdk-go/aws/request"
    24  	"github.com/aws/aws-sdk-go/aws/session"
    25  	"github.com/aws/aws-sdk-go/service/apigateway"
    26  	"github.com/aws/aws-sdk-go/service/autoscaling"
    27  	"github.com/aws/aws-sdk-go/service/cloudformation"
    28  	"github.com/aws/aws-sdk-go/service/cloudtrail"
    29  	"github.com/aws/aws-sdk-go/service/cloudwatch"
    30  	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
    31  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    32  	"github.com/aws/aws-sdk-go/service/codecommit"
    33  	"github.com/aws/aws-sdk-go/service/codedeploy"
    34  	"github.com/aws/aws-sdk-go/service/directoryservice"
    35  	"github.com/aws/aws-sdk-go/service/dynamodb"
    36  	"github.com/aws/aws-sdk-go/service/ec2"
    37  	"github.com/aws/aws-sdk-go/service/ecr"
    38  	"github.com/aws/aws-sdk-go/service/ecs"
    39  	"github.com/aws/aws-sdk-go/service/efs"
    40  	"github.com/aws/aws-sdk-go/service/elasticache"
    41  	"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
    42  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    43  	"github.com/aws/aws-sdk-go/service/elb"
    44  	"github.com/aws/aws-sdk-go/service/firehose"
    45  	"github.com/aws/aws-sdk-go/service/glacier"
    46  	"github.com/aws/aws-sdk-go/service/iam"
    47  	"github.com/aws/aws-sdk-go/service/kinesis"
    48  	"github.com/aws/aws-sdk-go/service/kms"
    49  	"github.com/aws/aws-sdk-go/service/lambda"
    50  	"github.com/aws/aws-sdk-go/service/opsworks"
    51  	"github.com/aws/aws-sdk-go/service/rds"
    52  	"github.com/aws/aws-sdk-go/service/redshift"
    53  	"github.com/aws/aws-sdk-go/service/route53"
    54  	"github.com/aws/aws-sdk-go/service/s3"
    55  	"github.com/aws/aws-sdk-go/service/sns"
    56  	"github.com/aws/aws-sdk-go/service/sqs"
    57  )
    58  
    59  type Config struct {
    60  	AccessKey     string
    61  	SecretKey     string
    62  	CredsFilename string
    63  	Profile       string
    64  	Token         string
    65  	Region        string
    66  	MaxRetries    int
    67  
    68  	AllowedAccountIds   []interface{}
    69  	ForbiddenAccountIds []interface{}
    70  
    71  	DynamoDBEndpoint string
    72  	KinesisEndpoint  string
    73  	Ec2Endpoint      string
    74  	IamEndpoint      string
    75  	ElbEndpoint      string
    76  	Insecure         bool
    77  }
    78  
    79  type AWSClient struct {
    80  	cfconn               *cloudformation.CloudFormation
    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  	esconn               *elasticsearch.ElasticsearchService
    93  	apigateway           *apigateway.APIGateway
    94  	autoscalingconn      *autoscaling.AutoScaling
    95  	s3conn               *s3.S3
    96  	sqsconn              *sqs.SQS
    97  	snsconn              *sns.SNS
    98  	redshiftconn         *redshift.Redshift
    99  	r53conn              *route53.Route53
   100  	region               string
   101  	rdsconn              *rds.RDS
   102  	iamconn              *iam.IAM
   103  	kinesisconn          *kinesis.Kinesis
   104  	kmsconn              *kms.KMS
   105  	firehoseconn         *firehose.Firehose
   106  	elasticacheconn      *elasticache.ElastiCache
   107  	elasticbeanstalkconn *elasticbeanstalk.ElasticBeanstalk
   108  	lambdaconn           *lambda.Lambda
   109  	opsworksconn         *opsworks.OpsWorks
   110  	glacierconn          *glacier.Glacier
   111  	codedeployconn       *codedeploy.CodeDeploy
   112  	codecommitconn       *codecommit.CodeCommit
   113  }
   114  
   115  // Client configures and returns a fully initialized AWSClient
   116  func (c *Config) Client() (interface{}, error) {
   117  	// Get the auth and region. This can fail if keys/regions were not
   118  	// specified and we're attempting to use the environment.
   119  	var errs []error
   120  
   121  	log.Println("[INFO] Building AWS region structure")
   122  	err := c.ValidateRegion()
   123  	if err != nil {
   124  		errs = append(errs, err)
   125  	}
   126  
   127  	var client AWSClient
   128  	if len(errs) == 0 {
   129  		// store AWS region in client struct, for region specific operations such as
   130  		// bucket storage in S3
   131  		client.region = c.Region
   132  
   133  		log.Println("[INFO] Building AWS auth structure")
   134  		creds := getCreds(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename)
   135  		// Call Get to check for credential provider. If nothing found, we'll get an
   136  		// error, and we can present it nicely to the user
   137  		_, err = creds.Get()
   138  		if err != nil {
   139  			if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
   140  				errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider. 
   141    Please see https://terraform.io/docs/providers/aws/index.html for more information on 
   142    providing credentials for the AWS Provider`))
   143  			} else {
   144  				errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
   145  			}
   146  			return nil, &multierror.Error{Errors: errs}
   147  		}
   148  		awsConfig := &aws.Config{
   149  			Credentials: creds,
   150  			Region:      aws.String(c.Region),
   151  			MaxRetries:  aws.Int(c.MaxRetries),
   152  			HTTPClient:  cleanhttp.DefaultClient(),
   153  		}
   154  
   155  		if logging.IsDebugOrHigher() {
   156  			awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
   157  			awsConfig.Logger = awsLogger{}
   158  		}
   159  
   160  		if c.Insecure {
   161  			transport := awsConfig.HTTPClient.Transport.(*http.Transport)
   162  			transport.TLSClientConfig = &tls.Config{
   163  				InsecureSkipVerify: true,
   164  			}
   165  		}
   166  
   167  		// Set up base session
   168  		sess := session.New(awsConfig)
   169  		sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
   170  
   171  		log.Println("[INFO] Initializing IAM Connection")
   172  		awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
   173  		client.iamconn = iam.New(awsIamSess)
   174  
   175  		err = c.ValidateCredentials(client.iamconn)
   176  		if err != nil {
   177  			errs = append(errs, err)
   178  		}
   179  
   180  		// Some services exist only in us-east-1, e.g. because they manage
   181  		// resources that can span across multiple regions, or because
   182  		// signature format v4 requires region to be us-east-1 for global
   183  		// endpoints:
   184  		// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   185  		usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
   186  
   187  		log.Println("[INFO] Initializing DynamoDB connection")
   188  		dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
   189  		client.dynamodbconn = dynamodb.New(dynamoSess)
   190  
   191  		log.Println("[INFO] Initializing ELB connection")
   192  		awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
   193  		client.elbconn = elb.New(awsElbSess)
   194  
   195  		log.Println("[INFO] Initializing S3 connection")
   196  		client.s3conn = s3.New(sess)
   197  
   198  		log.Println("[INFO] Initializing SQS connection")
   199  		client.sqsconn = sqs.New(sess)
   200  
   201  		log.Println("[INFO] Initializing SNS connection")
   202  		client.snsconn = sns.New(sess)
   203  
   204  		log.Println("[INFO] Initializing RDS Connection")
   205  		client.rdsconn = rds.New(sess)
   206  
   207  		log.Println("[INFO] Initializing Kinesis Connection")
   208  		kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
   209  		client.kinesisconn = kinesis.New(kinesisSess)
   210  
   211  		log.Println("[INFO] Initializing Elastic Beanstalk Connection")
   212  		client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
   213  
   214  		authErr := c.ValidateAccountId(client.iamconn)
   215  		if authErr != nil {
   216  			errs = append(errs, authErr)
   217  		}
   218  
   219  		log.Println("[INFO] Initializing Kinesis Firehose Connection")
   220  		client.firehoseconn = firehose.New(sess)
   221  
   222  		log.Println("[INFO] Initializing AutoScaling connection")
   223  		client.autoscalingconn = autoscaling.New(sess)
   224  
   225  		log.Println("[INFO] Initializing EC2 Connection")
   226  
   227  		awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
   228  		client.ec2conn = ec2.New(awsEc2Sess)
   229  
   230  		log.Println("[INFO] Initializing ECR Connection")
   231  		client.ecrconn = ecr.New(sess)
   232  
   233  		log.Println("[INFO] Initializing API Gateway")
   234  		client.apigateway = apigateway.New(sess)
   235  
   236  		log.Println("[INFO] Initializing ECS Connection")
   237  		client.ecsconn = ecs.New(sess)
   238  
   239  		log.Println("[INFO] Initializing EFS Connection")
   240  		client.efsconn = efs.New(sess)
   241  
   242  		log.Println("[INFO] Initializing ElasticSearch Connection")
   243  		client.esconn = elasticsearch.New(sess)
   244  
   245  		log.Println("[INFO] Initializing Route 53 connection")
   246  		client.r53conn = route53.New(usEast1Sess)
   247  
   248  		log.Println("[INFO] Initializing Elasticache Connection")
   249  		client.elasticacheconn = elasticache.New(sess)
   250  
   251  		log.Println("[INFO] Initializing Lambda Connection")
   252  		client.lambdaconn = lambda.New(sess)
   253  
   254  		log.Println("[INFO] Initializing Cloudformation Connection")
   255  		client.cfconn = cloudformation.New(sess)
   256  
   257  		log.Println("[INFO] Initializing CloudWatch SDK connection")
   258  		client.cloudwatchconn = cloudwatch.New(sess)
   259  
   260  		log.Println("[INFO] Initializing CloudWatch Events connection")
   261  		client.cloudwatcheventsconn = cloudwatchevents.New(sess)
   262  
   263  		log.Println("[INFO] Initializing CloudTrail connection")
   264  		client.cloudtrailconn = cloudtrail.New(sess)
   265  
   266  		log.Println("[INFO] Initializing CloudWatch Logs connection")
   267  		client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
   268  
   269  		log.Println("[INFO] Initializing OpsWorks Connection")
   270  		client.opsworksconn = opsworks.New(usEast1Sess)
   271  
   272  		log.Println("[INFO] Initializing Directory Service connection")
   273  		client.dsconn = directoryservice.New(sess)
   274  
   275  		log.Println("[INFO] Initializing Glacier connection")
   276  		client.glacierconn = glacier.New(sess)
   277  
   278  		log.Println("[INFO] Initializing CodeDeploy Connection")
   279  		client.codedeployconn = codedeploy.New(sess)
   280  
   281  		log.Println("[INFO] Initializing CodeCommit SDK connection")
   282  		client.codecommitconn = codecommit.New(usEast1Sess)
   283  
   284  		log.Println("[INFO] Initializing Redshift SDK connection")
   285  		client.redshiftconn = redshift.New(sess)
   286  
   287  		log.Println("[INFO] Initializing KMS connection")
   288  		client.kmsconn = kms.New(sess)
   289  	}
   290  
   291  	if len(errs) > 0 {
   292  		return nil, &multierror.Error{Errors: errs}
   293  	}
   294  
   295  	return &client, nil
   296  }
   297  
   298  // ValidateRegion returns an error if the configured region is not a
   299  // valid aws region and nil otherwise.
   300  func (c *Config) ValidateRegion() error {
   301  	var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
   302  		"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
   303  		"ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"}
   304  
   305  	for _, valid := range regions {
   306  		if c.Region == valid {
   307  			return nil
   308  		}
   309  	}
   310  	return fmt.Errorf("Not a valid region: %s", c.Region)
   311  }
   312  
   313  // Validate credentials early and fail before we do any graph walking.
   314  // In the case of an IAM role/profile with insuffecient privileges, fail
   315  // silently
   316  func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
   317  	_, err := iamconn.GetUser(nil)
   318  
   319  	if awsErr, ok := err.(awserr.Error); ok {
   320  		if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" {
   321  			log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile")
   322  			// User may be an IAM instance profile, or otherwise IAM role without the
   323  			// GetUser permissions, so fail silently
   324  			return nil
   325  		}
   326  
   327  		if awsErr.Code() == "SignatureDoesNotMatch" {
   328  			return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
   329  		}
   330  	}
   331  
   332  	return err
   333  }
   334  
   335  // ValidateAccountId returns a context-specific error if the configured account
   336  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   337  func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
   338  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   339  		return nil
   340  	}
   341  
   342  	log.Printf("[INFO] Validating account ID")
   343  
   344  	out, err := iamconn.GetUser(nil)
   345  
   346  	if err != nil {
   347  		awsErr, _ := err.(awserr.Error)
   348  		if awsErr.Code() == "ValidationError" {
   349  			log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile")
   350  			// User may be an IAM instance profile, so fail silently.
   351  			// If it is an IAM instance profile
   352  			// validating account might be superfluous
   353  			return nil
   354  		} else {
   355  			return fmt.Errorf("Failed getting account ID from IAM: %s", err)
   356  			// return error if the account id is explicitly not authorised
   357  		}
   358  	}
   359  
   360  	account_id := strings.Split(*out.User.Arn, ":")[4]
   361  
   362  	if c.ForbiddenAccountIds != nil {
   363  		for _, id := range c.ForbiddenAccountIds {
   364  			if id == account_id {
   365  				return fmt.Errorf("Forbidden account ID (%s)", id)
   366  			}
   367  		}
   368  	}
   369  
   370  	if c.AllowedAccountIds != nil {
   371  		for _, id := range c.AllowedAccountIds {
   372  			if id == account_id {
   373  				return nil
   374  			}
   375  		}
   376  		return fmt.Errorf("Account ID not allowed (%s)", account_id)
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  // This function is responsible for reading credentials from the
   383  // environment in the case that they're not explicitly specified
   384  // in the Terraform configuration.
   385  func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials {
   386  	// build a chain provider, lazy-evaulated by aws-sdk
   387  	providers := []awsCredentials.Provider{
   388  		&awsCredentials.StaticProvider{Value: awsCredentials.Value{
   389  			AccessKeyID:     key,
   390  			SecretAccessKey: secret,
   391  			SessionToken:    token,
   392  		}},
   393  		&awsCredentials.EnvProvider{},
   394  		&awsCredentials.SharedCredentialsProvider{
   395  			Filename: credsfile,
   396  			Profile:  profile,
   397  		},
   398  	}
   399  
   400  	// We only look in the EC2 metadata API if we can connect
   401  	// to the metadata service within a reasonable amount of time
   402  	metadataURL := os.Getenv("AWS_METADATA_URL")
   403  	if metadataURL == "" {
   404  		metadataURL = "http://169.254.169.254:80/latest"
   405  	}
   406  	c := http.Client{
   407  		Timeout: 100 * time.Millisecond,
   408  	}
   409  
   410  	r, err := c.Get(metadataURL)
   411  	// Flag to determine if we should add the EC2Meta data provider. Default false
   412  	var useIAM bool
   413  	if err == nil {
   414  		// AWS will add a "Server: EC2ws" header value for the metadata request. We
   415  		// check the headers for this value to ensure something else didn't just
   416  		// happent to be listening on that IP:Port
   417  		if r.Header["Server"] != nil && strings.Contains(r.Header["Server"][0], "EC2") {
   418  			useIAM = true
   419  		}
   420  	}
   421  
   422  	if useIAM {
   423  		log.Printf("[DEBUG] EC2 Metadata service found, adding EC2 Role Credential Provider")
   424  		providers = append(providers, &ec2rolecreds.EC2RoleProvider{
   425  			Client: ec2metadata.New(session.New(&aws.Config{
   426  				Endpoint: aws.String(metadataURL),
   427  			})),
   428  		})
   429  	} else {
   430  		log.Printf("[DEBUG] EC2 Metadata service not found, not adding EC2 Role Credential Provider")
   431  	}
   432  	return awsCredentials.NewChainCredentials(providers)
   433  }
   434  
   435  // addTerraformVersionToUserAgent is a named handler that will add Terraform's
   436  // version information to requests made by the AWS SDK.
   437  var addTerraformVersionToUserAgent = request.NamedHandler{
   438  	Name: "terraform.TerraformVersionUserAgentHandler",
   439  	Fn: request.MakeAddToUserAgentHandler(
   440  		"terraform", terraform.Version, terraform.VersionPrerelease),
   441  }
   442  
   443  type awsLogger struct{}
   444  
   445  func (l awsLogger) Log(args ...interface{}) {
   446  	tokens := make([]string, 0, len(args))
   447  	for _, arg := range args {
   448  		if token, ok := arg.(string); ok {
   449  			tokens = append(tokens, token)
   450  		}
   451  	}
   452  	log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " "))
   453  }