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