github.com/icebourg/terraform@v0.6.5-0.20151015205227-263cc1b85535/builtin/providers/aws/config.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/go-multierror"
     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/credentials"
    13  	"github.com/aws/aws-sdk-go/service/autoscaling"
    14  	"github.com/aws/aws-sdk-go/service/cloudwatch"
    15  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    16  	"github.com/aws/aws-sdk-go/service/directoryservice"
    17  	"github.com/aws/aws-sdk-go/service/dynamodb"
    18  	"github.com/aws/aws-sdk-go/service/ec2"
    19  	"github.com/aws/aws-sdk-go/service/ecs"
    20  	"github.com/aws/aws-sdk-go/service/efs"
    21  	"github.com/aws/aws-sdk-go/service/elasticache"
    22  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    23  	"github.com/aws/aws-sdk-go/service/elb"
    24  	"github.com/aws/aws-sdk-go/service/glacier"
    25  	"github.com/aws/aws-sdk-go/service/iam"
    26  	"github.com/aws/aws-sdk-go/service/kinesis"
    27  	"github.com/aws/aws-sdk-go/service/lambda"
    28  	"github.com/aws/aws-sdk-go/service/opsworks"
    29  	"github.com/aws/aws-sdk-go/service/rds"
    30  	"github.com/aws/aws-sdk-go/service/route53"
    31  	"github.com/aws/aws-sdk-go/service/s3"
    32  	"github.com/aws/aws-sdk-go/service/sns"
    33  	"github.com/aws/aws-sdk-go/service/sqs"
    34  )
    35  
    36  type Config struct {
    37  	AccessKey  string
    38  	SecretKey  string
    39  	Token      string
    40  	Region     string
    41  	MaxRetries int
    42  
    43  	AllowedAccountIds   []interface{}
    44  	ForbiddenAccountIds []interface{}
    45  
    46  	DynamoDBEndpoint string
    47  }
    48  
    49  type AWSClient struct {
    50  	cloudwatchconn     *cloudwatch.CloudWatch
    51  	cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs
    52  	dsconn             *directoryservice.DirectoryService
    53  	dynamodbconn       *dynamodb.DynamoDB
    54  	ec2conn            *ec2.EC2
    55  	ecsconn            *ecs.ECS
    56  	efsconn            *efs.EFS
    57  	elbconn            *elb.ELB
    58  	esconn             *elasticsearch.ElasticsearchService
    59  	autoscalingconn    *autoscaling.AutoScaling
    60  	s3conn             *s3.S3
    61  	sqsconn            *sqs.SQS
    62  	snsconn            *sns.SNS
    63  	r53conn            *route53.Route53
    64  	region             string
    65  	rdsconn            *rds.RDS
    66  	iamconn            *iam.IAM
    67  	kinesisconn        *kinesis.Kinesis
    68  	elasticacheconn    *elasticache.ElastiCache
    69  	lambdaconn         *lambda.Lambda
    70  	opsworksconn       *opsworks.OpsWorks
    71  	glacierconn        *glacier.Glacier
    72  }
    73  
    74  // Client configures and returns a fully initialized AWSClient
    75  func (c *Config) Client() (interface{}, error) {
    76  	var client AWSClient
    77  
    78  	// Get the auth and region. This can fail if keys/regions were not
    79  	// specified and we're attempting to use the environment.
    80  	var errs []error
    81  
    82  	log.Println("[INFO] Building AWS region structure")
    83  	err := c.ValidateRegion()
    84  	if err != nil {
    85  		errs = append(errs, err)
    86  	}
    87  
    88  	if len(errs) == 0 {
    89  		// store AWS region in client struct, for region specific operations such as
    90  		// bucket storage in S3
    91  		client.region = c.Region
    92  
    93  		log.Println("[INFO] Building AWS auth structure")
    94  		// We fetched all credential sources in Provider. If they are
    95  		// available, they'll already be in c. See Provider definition.
    96  		creds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
    97  		awsConfig := &aws.Config{
    98  			Credentials: creds,
    99  			Region:      aws.String(c.Region),
   100  			MaxRetries:  aws.Int(c.MaxRetries),
   101  		}
   102  
   103  		log.Println("[INFO] Initializing IAM Connection")
   104  		client.iamconn = iam.New(awsConfig)
   105  
   106  		err := c.ValidateCredentials(client.iamconn)
   107  		if err != nil {
   108  			errs = append(errs, err)
   109  		}
   110  
   111  		awsDynamoDBConfig := &aws.Config{
   112  			Credentials: creds,
   113  			Region:      aws.String(c.Region),
   114  			MaxRetries:  aws.Int(c.MaxRetries),
   115  			Endpoint:    aws.String(c.DynamoDBEndpoint),
   116  		}
   117  		// Some services exist only in us-east-1, e.g. because they manage
   118  		// resources that can span across multiple regions, or because
   119  		// signature format v4 requires region to be us-east-1 for global
   120  		// endpoints:
   121  		// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
   122  		usEast1AwsConfig := &aws.Config{
   123  			Credentials: creds,
   124  			Region:      aws.String("us-east-1"),
   125  			MaxRetries:  aws.Int(c.MaxRetries),
   126  		}
   127  
   128  		log.Println("[INFO] Initializing DynamoDB connection")
   129  		client.dynamodbconn = dynamodb.New(awsDynamoDBConfig)
   130  
   131  		log.Println("[INFO] Initializing ELB connection")
   132  		client.elbconn = elb.New(awsConfig)
   133  
   134  		log.Println("[INFO] Initializing S3 connection")
   135  		client.s3conn = s3.New(awsConfig)
   136  
   137  		log.Println("[INFO] Initializing SQS connection")
   138  		client.sqsconn = sqs.New(awsConfig)
   139  
   140  		log.Println("[INFO] Initializing SNS connection")
   141  		client.snsconn = sns.New(awsConfig)
   142  
   143  		log.Println("[INFO] Initializing RDS Connection")
   144  		client.rdsconn = rds.New(awsConfig)
   145  
   146  		log.Println("[INFO] Initializing Kinesis Connection")
   147  		client.kinesisconn = kinesis.New(awsConfig)
   148  
   149  		authErr := c.ValidateAccountId(client.iamconn)
   150  		if authErr != nil {
   151  			errs = append(errs, authErr)
   152  		}
   153  
   154  		log.Println("[INFO] Initializing AutoScaling connection")
   155  		client.autoscalingconn = autoscaling.New(awsConfig)
   156  
   157  		log.Println("[INFO] Initializing EC2 Connection")
   158  		client.ec2conn = ec2.New(awsConfig)
   159  
   160  		log.Println("[INFO] Initializing ECS Connection")
   161  		client.ecsconn = ecs.New(awsConfig)
   162  
   163  		log.Println("[INFO] Initializing EFS Connection")
   164  		client.efsconn = efs.New(awsConfig)
   165  
   166  		log.Println("[INFO] Initializing ElasticSearch Connection")
   167  		client.esconn = elasticsearch.New(awsConfig)
   168  
   169  		log.Println("[INFO] Initializing Route 53 connection")
   170  		client.r53conn = route53.New(usEast1AwsConfig)
   171  
   172  		log.Println("[INFO] Initializing Elasticache Connection")
   173  		client.elasticacheconn = elasticache.New(awsConfig)
   174  
   175  		log.Println("[INFO] Initializing Lambda Connection")
   176  		client.lambdaconn = lambda.New(awsConfig)
   177  
   178  		log.Println("[INFO] Initializing CloudWatch SDK connection")
   179  		client.cloudwatchconn = cloudwatch.New(awsConfig)
   180  
   181  		log.Println("[INFO] Initializing CloudWatch Logs connection")
   182  		client.cloudwatchlogsconn = cloudwatchlogs.New(awsConfig)
   183  
   184  		log.Println("[INFO] Initializing OpsWorks Connection")
   185  		client.opsworksconn = opsworks.New(usEast1AwsConfig)
   186  
   187  		log.Println("[INFO] Initializing Directory Service connection")
   188  		client.dsconn = directoryservice.New(awsConfig)
   189  
   190  		log.Println("[INFO] Initializing Glacier connection")
   191  		client.glacierconn = glacier.New(awsConfig)
   192  	}
   193  
   194  	if len(errs) > 0 {
   195  		return nil, &multierror.Error{Errors: errs}
   196  	}
   197  
   198  	return &client, nil
   199  }
   200  
   201  // ValidateRegion returns an error if the configured region is not a
   202  // valid aws region and nil otherwise.
   203  func (c *Config) ValidateRegion() error {
   204  	var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
   205  		"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
   206  		"sa-east-1", "cn-north-1", "us-gov-west-1"}
   207  
   208  	for _, valid := range regions {
   209  		if c.Region == valid {
   210  			return nil
   211  		}
   212  	}
   213  	return fmt.Errorf("Not a valid region: %s", c.Region)
   214  }
   215  
   216  // Validate credentials early and fail before we do any graph walking.
   217  // In the case of an IAM role/profile with insuffecient privileges, fail
   218  // silently
   219  func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
   220  	_, err := iamconn.GetUser(nil)
   221  
   222  	if awsErr, ok := err.(awserr.Error); ok {
   223  
   224  		if awsErr.Code() == "AccessDenied" || awsErr.Code() == "ValidationError" {
   225  			log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile")
   226  			// User may be an IAM instance profile, or otherwise IAM role without the
   227  			// GetUser permissions, so fail silently
   228  			return nil
   229  		}
   230  
   231  		if awsErr.Code() == "SignatureDoesNotMatch" {
   232  			return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
   233  		}
   234  	}
   235  
   236  	return err
   237  }
   238  
   239  // ValidateAccountId returns a context-specific error if the configured account
   240  // id is explicitly forbidden or not authorised; and nil if it is authorised.
   241  func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
   242  	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
   243  		return nil
   244  	}
   245  
   246  	log.Printf("[INFO] Validating account ID")
   247  
   248  	out, err := iamconn.GetUser(nil)
   249  
   250  	if err != nil {
   251  		awsErr, _ := err.(awserr.Error)
   252  		if awsErr.Code() == "ValidationError" {
   253  			log.Printf("[WARN] ValidationError with iam.GetUser, assuming its an IAM profile")
   254  			// User may be an IAM instance profile, so fail silently.
   255  			// If it is an IAM instance profile
   256  			// validating account might be superfluous
   257  			return nil
   258  		} else {
   259  			return fmt.Errorf("Failed getting account ID from IAM: %s", err)
   260  			// return error if the account id is explicitly not authorised
   261  		}
   262  	}
   263  
   264  	account_id := strings.Split(*out.User.Arn, ":")[4]
   265  
   266  	if c.ForbiddenAccountIds != nil {
   267  		for _, id := range c.ForbiddenAccountIds {
   268  			if id == account_id {
   269  				return fmt.Errorf("Forbidden account ID (%s)", id)
   270  			}
   271  		}
   272  	}
   273  
   274  	if c.AllowedAccountIds != nil {
   275  		for _, id := range c.AllowedAccountIds {
   276  			if id == account_id {
   277  				return nil
   278  			}
   279  		}
   280  		return fmt.Errorf("Account ID not allowed (%s)", account_id)
   281  	}
   282  
   283  	return nil
   284  }