github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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 }