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