github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/builtin/providers/aws/auth_helpers.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
    13  	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
    14  	"github.com/aws/aws-sdk-go/aws/ec2metadata"
    15  	"github.com/aws/aws-sdk-go/aws/session"
    16  	"github.com/aws/aws-sdk-go/service/iam"
    17  	"github.com/aws/aws-sdk-go/service/sts"
    18  	"github.com/hashicorp/errwrap"
    19  	"github.com/hashicorp/go-cleanhttp"
    20  )
    21  
    22  func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (string, error) {
    23  	// If we have creds from instance profile, we can use metadata API
    24  	if authProviderName == ec2rolecreds.ProviderName {
    25  		log.Println("[DEBUG] Trying to get account ID via AWS Metadata API")
    26  
    27  		cfg := &aws.Config{}
    28  		setOptionalEndpoint(cfg)
    29  		sess, err := session.NewSession(cfg)
    30  		if err != nil {
    31  			return "", errwrap.Wrapf("Error creating AWS session: %s", err)
    32  		}
    33  
    34  		metadataClient := ec2metadata.New(sess)
    35  		info, err := metadataClient.IAMInfo()
    36  		if err != nil {
    37  			// This can be triggered when no IAM Role is assigned
    38  			// or AWS just happens to return invalid response
    39  			return "", fmt.Errorf("Failed getting EC2 IAM info: %s", err)
    40  		}
    41  
    42  		return parseAccountIdFromArn(info.InstanceProfileArn)
    43  	}
    44  
    45  	// Then try IAM GetUser
    46  	log.Println("[DEBUG] Trying to get account ID via iam:GetUser")
    47  	outUser, err := iamconn.GetUser(nil)
    48  	if err == nil {
    49  		return parseAccountIdFromArn(*outUser.User.Arn)
    50  	}
    51  
    52  	awsErr, ok := err.(awserr.Error)
    53  	// AccessDenied and ValidationError can be raised
    54  	// if credentials belong to federated profile, so we ignore these
    55  	if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError") {
    56  		return "", fmt.Errorf("Failed getting account ID via 'iam:GetUser': %s", err)
    57  	}
    58  	log.Printf("[DEBUG] Getting account ID via iam:GetUser failed: %s", err)
    59  
    60  	// Then try STS GetCallerIdentity
    61  	log.Println("[DEBUG] Trying to get account ID via sts:GetCallerIdentity")
    62  	outCallerIdentity, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{})
    63  	if err == nil {
    64  		return *outCallerIdentity.Account, nil
    65  	}
    66  	log.Printf("[DEBUG] Getting account ID via sts:GetCallerIdentity failed: %s", err)
    67  
    68  	// Then try IAM ListRoles
    69  	log.Println("[DEBUG] Trying to get account ID via iam:ListRoles")
    70  	outRoles, err := iamconn.ListRoles(&iam.ListRolesInput{
    71  		MaxItems: aws.Int64(int64(1)),
    72  	})
    73  	if err != nil {
    74  		return "", fmt.Errorf("Failed getting account ID via 'iam:ListRoles': %s", err)
    75  	}
    76  
    77  	if len(outRoles.Roles) < 1 {
    78  		return "", fmt.Errorf("Failed getting account ID via 'iam:ListRoles': No roles available")
    79  	}
    80  
    81  	return parseAccountIdFromArn(*outRoles.Roles[0].Arn)
    82  }
    83  
    84  func parseAccountIdFromArn(arn string) (string, error) {
    85  	parts := strings.Split(arn, ":")
    86  	if len(parts) < 5 {
    87  		return "", fmt.Errorf("Unable to parse ID from invalid ARN: %q", arn)
    88  	}
    89  	return parts[4], nil
    90  }
    91  
    92  // This function is responsible for reading credentials from the
    93  // environment in the case that they're not explicitly specified
    94  // in the Terraform configuration.
    95  func GetCredentials(c *Config) *awsCredentials.Credentials {
    96  	// build a chain provider, lazy-evaulated by aws-sdk
    97  	providers := []awsCredentials.Provider{
    98  		&awsCredentials.StaticProvider{Value: awsCredentials.Value{
    99  			AccessKeyID:     c.AccessKey,
   100  			SecretAccessKey: c.SecretKey,
   101  			SessionToken:    c.Token,
   102  		}},
   103  		&awsCredentials.EnvProvider{},
   104  		&awsCredentials.SharedCredentialsProvider{
   105  			Filename: c.CredsFilename,
   106  			Profile:  c.Profile,
   107  		},
   108  	}
   109  
   110  	// Build isolated HTTP client to avoid issues with globally-shared settings
   111  	client := cleanhttp.DefaultClient()
   112  
   113  	// Keep the timeout low as we don't want to wait in non-EC2 environments
   114  	client.Timeout = 100 * time.Millisecond
   115  	cfg := &aws.Config{
   116  		HTTPClient: client,
   117  	}
   118  	usedEndpoint := setOptionalEndpoint(cfg)
   119  
   120  	if !c.SkipMetadataApiCheck {
   121  		// Real AWS should reply to a simple metadata request.
   122  		// We check it actually does to ensure something else didn't just
   123  		// happen to be listening on the same IP:Port
   124  		metadataClient := ec2metadata.New(session.New(cfg))
   125  		if metadataClient.Available() {
   126  			providers = append(providers, &ec2rolecreds.EC2RoleProvider{
   127  				Client: metadataClient,
   128  			})
   129  			log.Printf("[INFO] AWS EC2 instance detected via default metadata" +
   130  				" API endpoint, EC2RoleProvider added to the auth chain")
   131  		} else {
   132  			if usedEndpoint == "" {
   133  				usedEndpoint = "default location"
   134  			}
   135  			log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+
   136  				"as it doesn't return any instance-id", usedEndpoint)
   137  		}
   138  	}
   139  
   140  	return awsCredentials.NewChainCredentials(providers)
   141  }
   142  
   143  func setOptionalEndpoint(cfg *aws.Config) string {
   144  	endpoint := os.Getenv("AWS_METADATA_URL")
   145  	if endpoint != "" {
   146  		log.Printf("[INFO] Setting custom metadata endpoint: %q", endpoint)
   147  		cfg.Endpoint = aws.String(endpoint)
   148  		return endpoint
   149  	}
   150  	return ""
   151  }