github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/auth_helpers.go (about) 1 package aws 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 awsCredentials "github.com/aws/aws-sdk-go/aws/credentials" 14 "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" 15 "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 16 "github.com/aws/aws-sdk-go/aws/ec2metadata" 17 "github.com/aws/aws-sdk-go/aws/session" 18 "github.com/aws/aws-sdk-go/service/iam" 19 "github.com/aws/aws-sdk-go/service/sts" 20 "github.com/hashicorp/errwrap" 21 "github.com/hashicorp/go-cleanhttp" 22 ) 23 24 func GetAccountInfo(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (string, string, error) { 25 // If we have creds from instance profile, we can use metadata API 26 if authProviderName == ec2rolecreds.ProviderName { 27 log.Println("[DEBUG] Trying to get account ID via AWS Metadata API") 28 29 cfg := &aws.Config{} 30 setOptionalEndpoint(cfg) 31 sess, err := session.NewSession(cfg) 32 if err != nil { 33 return "", "", errwrap.Wrapf("Error creating AWS session: {{err}}", err) 34 } 35 36 metadataClient := ec2metadata.New(sess) 37 info, err := metadataClient.IAMInfo() 38 if err != nil { 39 // This can be triggered when no IAM Role is assigned 40 // or AWS just happens to return invalid response 41 return "", "", fmt.Errorf("Failed getting EC2 IAM info: %s", err) 42 } 43 44 return parseAccountInfoFromArn(info.InstanceProfileArn) 45 } 46 47 // Then try IAM GetUser 48 log.Println("[DEBUG] Trying to get account ID via iam:GetUser") 49 outUser, err := iamconn.GetUser(nil) 50 if err == nil { 51 return parseAccountInfoFromArn(*outUser.User.Arn) 52 } 53 54 awsErr, ok := err.(awserr.Error) 55 // AccessDenied and ValidationError can be raised 56 // if credentials belong to federated profile, so we ignore these 57 if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError") { 58 return "", "", fmt.Errorf("Failed getting account ID via 'iam:GetUser': %s", err) 59 } 60 log.Printf("[DEBUG] Getting account ID via iam:GetUser failed: %s", err) 61 62 // Then try STS GetCallerIdentity 63 log.Println("[DEBUG] Trying to get account ID via sts:GetCallerIdentity") 64 outCallerIdentity, err := stsconn.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 65 if err == nil { 66 return parseAccountInfoFromArn(*outCallerIdentity.Arn) 67 } 68 log.Printf("[DEBUG] Getting account ID via sts:GetCallerIdentity failed: %s", err) 69 70 // Then try IAM ListRoles 71 log.Println("[DEBUG] Trying to get account ID via iam:ListRoles") 72 outRoles, err := iamconn.ListRoles(&iam.ListRolesInput{ 73 MaxItems: aws.Int64(int64(1)), 74 }) 75 if err != nil { 76 return "", "", fmt.Errorf("Failed getting account ID via 'iam:ListRoles': %s", err) 77 } 78 79 if len(outRoles.Roles) < 1 { 80 return "", "", errors.New("Failed getting account ID via 'iam:ListRoles': No roles available") 81 } 82 83 return parseAccountInfoFromArn(*outRoles.Roles[0].Arn) 84 } 85 86 func parseAccountInfoFromArn(arn string) (string, string, error) { 87 parts := strings.Split(arn, ":") 88 if len(parts) < 5 { 89 return "", "", fmt.Errorf("Unable to parse ID from invalid ARN: %q", arn) 90 } 91 return parts[1], parts[4], nil 92 } 93 94 // This function is responsible for reading credentials from the 95 // environment in the case that they're not explicitly specified 96 // in the Terraform configuration. 97 func GetCredentials(c *Config) (*awsCredentials.Credentials, error) { 98 // build a chain provider, lazy-evaulated by aws-sdk 99 providers := []awsCredentials.Provider{ 100 &awsCredentials.StaticProvider{Value: awsCredentials.Value{ 101 AccessKeyID: c.AccessKey, 102 SecretAccessKey: c.SecretKey, 103 SessionToken: c.Token, 104 }}, 105 &awsCredentials.EnvProvider{}, 106 &awsCredentials.SharedCredentialsProvider{ 107 Filename: c.CredsFilename, 108 Profile: c.Profile, 109 }, 110 } 111 112 // Build isolated HTTP client to avoid issues with globally-shared settings 113 client := cleanhttp.DefaultClient() 114 115 // Keep the timeout low as we don't want to wait in non-EC2 environments 116 client.Timeout = 100 * time.Millisecond 117 cfg := &aws.Config{ 118 HTTPClient: client, 119 } 120 usedEndpoint := setOptionalEndpoint(cfg) 121 122 if !c.SkipMetadataApiCheck { 123 // Real AWS should reply to a simple metadata request. 124 // We check it actually does to ensure something else didn't just 125 // happen to be listening on the same IP:Port 126 metadataClient := ec2metadata.New(session.New(cfg)) 127 if metadataClient.Available() { 128 providers = append(providers, &ec2rolecreds.EC2RoleProvider{ 129 Client: metadataClient, 130 }) 131 log.Print("[INFO] AWS EC2 instance detected via default metadata" + 132 " API endpoint, EC2RoleProvider added to the auth chain") 133 } else { 134 if usedEndpoint == "" { 135 usedEndpoint = "default location" 136 } 137 log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+ 138 "as it doesn't return any instance-id", usedEndpoint) 139 } 140 } 141 142 // This is the "normal" flow (i.e. not assuming a role) 143 if c.AssumeRoleARN == "" { 144 return awsCredentials.NewChainCredentials(providers), nil 145 } 146 147 // Otherwise we need to construct and STS client with the main credentials, and verify 148 // that we can assume the defined role. 149 log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q, Policy: %q)", 150 c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID, c.AssumeRolePolicy) 151 152 creds := awsCredentials.NewChainCredentials(providers) 153 cp, err := creds.Get() 154 if err != nil { 155 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 156 return nil, errors.New(`No valid credential sources found for AWS Provider. 157 Please see https://terraform.io/docs/providers/aws/index.html for more information on 158 providing credentials for the AWS Provider`) 159 } 160 161 return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err) 162 } 163 164 log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName) 165 166 awsConfig := &aws.Config{ 167 Credentials: creds, 168 Region: aws.String(c.Region), 169 MaxRetries: aws.Int(c.MaxRetries), 170 HTTPClient: cleanhttp.DefaultClient(), 171 S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle), 172 } 173 174 stsclient := sts.New(session.New(awsConfig)) 175 assumeRoleProvider := &stscreds.AssumeRoleProvider{ 176 Client: stsclient, 177 RoleARN: c.AssumeRoleARN, 178 } 179 if c.AssumeRoleSessionName != "" { 180 assumeRoleProvider.RoleSessionName = c.AssumeRoleSessionName 181 } 182 if c.AssumeRoleExternalID != "" { 183 assumeRoleProvider.ExternalID = aws.String(c.AssumeRoleExternalID) 184 } 185 if c.AssumeRolePolicy != "" { 186 assumeRoleProvider.Policy = aws.String(c.AssumeRolePolicy) 187 } 188 189 providers = []awsCredentials.Provider{assumeRoleProvider} 190 191 assumeRoleCreds := awsCredentials.NewChainCredentials(providers) 192 _, err = assumeRoleCreds.Get() 193 if err != nil { 194 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 195 return nil, fmt.Errorf("The role %q cannot be assumed.\n\n"+ 196 " There are a number of possible causes of this - the most common are:\n"+ 197 " * The credentials used in order to assume the role are invalid\n"+ 198 " * The credentials do not have appropriate permission to assume the role\n"+ 199 " * The role ARN is not valid", 200 c.AssumeRoleARN) 201 } 202 203 return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err) 204 } 205 206 return assumeRoleCreds, nil 207 } 208 209 func setOptionalEndpoint(cfg *aws.Config) string { 210 endpoint := os.Getenv("AWS_METADATA_URL") 211 if endpoint != "" { 212 log.Printf("[INFO] Setting custom metadata endpoint: %q", endpoint) 213 cfg.Endpoint = aws.String(endpoint) 214 return endpoint 215 } 216 return "" 217 }