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 }