Skip to content

Commit 869aa34

Browse files
committed
[login] allow to run aws-vault login with non-temporary credentials in the environment
1 parent add7709 commit 869aa34

File tree

4 files changed

+73
-31
lines changed

4 files changed

+73
-31
lines changed

USAGE.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -307,11 +307,13 @@ You can use the `aws-vault login` command to open a browser window and login to
307307
$ aws-vault login work
308308
```
309309

310-
If you have temporary STS credentials already available in your environment, you can have aws-vault use these credentials to sign you in.
311-
This is useful when you had to use something else than aws-vault to retrieve temporary credentials:
310+
If you have credentials already available in your environment, you can have aws-vault use these credentials to sign you in.
311+
This is useful when you had to use something else than aws-vault to retrieve credentials:
312312

313313
```shell
314-
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN must be set in your environment prior to running the below
314+
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN must be set in your environment prior to running the below
315+
# If AWS_SESSION_TOKEN is not set, a call to sts:GetFederationToken will be issued to retrieve temporary credentials,
316+
# require to be able to generate a sign-in link to the AWS console
315317
$ aws-vault login
316318
```
317319

cli/login.go

+38-12
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ func LoginCommand(input LoginCommandInput, f *vault.ConfigFile, keyring keyring.
9292
}
9393

9494
var credsProvider aws.CredentialsProvider
95+
var creds aws.Credentials
9596

97+
// Use a profile from the AWS config file
98+
ckr := &vault.CredentialKeyring{Keyring: keyring}
9699
if input.ProfileName == "" {
97-
// When no profile is specified, source credentials from the environment
98-
credsProvider = vault.NewEnvironmentCredentialsProvider()
100+
creds, err = retrieveTemporaryCredsFromEnvironment(config)
99101
} else {
100-
// Use a profile from the AWS config file
101-
ckr := &vault.CredentialKeyring{Keyring: keyring}
102102
if config.HasRole() || config.HasSSOStartURL() {
103103
// If AssumeRole or sso.GetRoleCredentials isn't used, GetFederationToken has to be used for IAM credentials
104104
credsProvider, err = vault.NewTempCredentialsProvider(config, ckr)
@@ -108,22 +108,15 @@ func LoginCommand(input LoginCommandInput, f *vault.ConfigFile, keyring keyring.
108108
if err != nil {
109109
return fmt.Errorf("profile %s: %w", input.ProfileName, err)
110110
}
111+
creds, err = credsProvider.Retrieve(context.TODO())
111112
}
112113

113-
creds, err := credsProvider.Retrieve(context.TODO())
114114
if err != nil {
115115
return fmt.Errorf("Failed to get credentials: %w", err)
116116
}
117117
if creds.AccessKeyID == "" && input.ProfileName == "" {
118118
return fmt.Errorf("argument 'profile' not provided, nor any AWS env vars found. Try --help")
119119
}
120-
if creds.SessionToken == "" {
121-
// When sourcing credentials from the environment, it's possible a session token wasn't set
122-
// Generating a sign-in link requires temporary credentials, so we return an error
123-
// NOTE: We deliberately chose to have this logic here rather than in 'EnvironmentVariablesCredentialsProvider'
124-
// to make it possible to reuse it for other commands than `aws-vault login` in the future
125-
return fmt.Errorf("failed to retrieve a session token. Cannot generate a login URL without it")
126-
}
127120

128121
jsonBytes, err := json.Marshal(map[string]string{
129122
"sessionId": creds.AccessKeyID,
@@ -191,6 +184,39 @@ func LoginCommand(input LoginCommandInput, f *vault.ConfigFile, keyring keyring.
191184
return nil
192185
}
193186

187+
// retrieveTemporaryCredsFromEnvironment contains the logic to retrieve the proper credentials
188+
// from the environment.
189+
// - Case 1: Temporary credentials are available - these are directly returned
190+
// - Case 2: Non-temporary credentials are available. A call to sts:GetFederation is made, and the resulting temporary
191+
// credentials returned
192+
func retrieveTemporaryCredsFromEnvironment(config *vault.Config) (aws.Credentials, error) {
193+
// When no profile is specified, source credentials from the environment
194+
credsProvider := vault.NewEnvironmentCredentialsProvider()
195+
creds, err := credsProvider.Retrieve(context.TODO())
196+
if err != nil {
197+
return aws.Credentials{}, fmt.Errorf("unable to find credentials in your environment")
198+
}
199+
200+
// If the credentials we found in the environment aren't temporary,
201+
// use sts:GetFederationToken to get temporary credentials
202+
// allowing to generate a sign-in link.
203+
// Non-temporary credentials cannot be used for this purpose
204+
if creds.SessionToken == "" {
205+
credsProvider, err := vault.NewFederationTokenCredentialsProviderFromCredentials(&creds, config)
206+
if err != nil {
207+
return aws.Credentials{}, err
208+
}
209+
210+
creds, err = credsProvider.Retrieve(context.TODO())
211+
if err != nil {
212+
err = fmt.Errorf("non-temporary credentials found in your environment, and calling GetFederationToken resulted in: " + err.Error())
213+
return aws.Credentials{}, err
214+
}
215+
}
216+
217+
return creds, nil
218+
}
219+
194220
func generateLoginURL(region string, path string) (string, string) {
195221
loginURLPrefix := "https://signin.aws.amazon.com/federation"
196222
destination := "https://console.aws.amazon.com/"

go.mod

+13-13
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ go 1.17
55
require (
66
github.com/99designs/keyring v1.2.1
77
github.com/alecthomas/kingpin v0.0.0-20200323085623-b6657d9477a6
8-
github.com/aws/aws-sdk-go-v2 v1.16.2
9-
github.com/aws/aws-sdk-go-v2/config v1.15.3
10-
github.com/aws/aws-sdk-go-v2/service/iam v1.18.3
11-
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3
12-
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.12.3
13-
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3
8+
github.com/aws/aws-sdk-go-v2 v1.15.0
9+
github.com/aws/aws-sdk-go-v2/config v1.14.0
10+
github.com/aws/aws-sdk-go-v2/credentials v1.9.0
11+
github.com/aws/aws-sdk-go-v2/service/iam v1.18.0
12+
github.com/aws/aws-sdk-go-v2/service/sso v1.11.0
13+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.12.0
14+
github.com/aws/aws-sdk-go-v2/service/sts v1.15.0
1415
github.com/google/go-cmp v0.5.7
1516
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
1617
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
@@ -22,13 +23,12 @@ require (
2223
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
2324
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
2425
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
25-
github.com/aws/aws-sdk-go-v2/credentials v1.11.2 // indirect
26-
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 // indirect
27-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect
28-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 // indirect
29-
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 // indirect
30-
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 // indirect
31-
github.com/aws/smithy-go v1.11.2 // indirect
26+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.11.0 // indirect
27+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 // indirect
28+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 // indirect
29+
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.6 // indirect
30+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.8.0 // indirect
31+
github.com/aws/smithy-go v1.11.1 // indirect
3232
github.com/danieljoos/wincred v1.1.2 // indirect
3333
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
3434
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect

vault/vault.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/99designs/aws-vault/v6/prompt"
1111
"github.com/99designs/keyring"
1212
"github.com/aws/aws-sdk-go-v2/aws"
13+
"github.com/aws/aws-sdk-go-v2/credentials"
1314
"github.com/aws/aws-sdk-go-v2/service/sso"
1415
"github.com/aws/aws-sdk-go-v2/service/ssooidc"
1516
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -276,16 +277,29 @@ func NewFederationTokenCredentialsProvider(profileName string, k *CredentialKeyr
276277
}
277278

278279
masterCreds := NewMasterCredentialsProvider(k, credentialsName)
279-
cfg := NewAwsConfigWithCredsProvider(masterCreds, config.Region, config.STSRegionalEndpoints)
280+
awsConfig := NewAwsConfigWithCredsProvider(masterCreds, config.Region, config.STSRegionalEndpoints)
280281

281-
currentUsername, err := GetUsernameFromSession(cfg)
282+
return newFederationTokenCredentialsProvider(awsConfig, config)
283+
}
284+
285+
func NewFederationTokenCredentialsProviderFromCredentials(creds *aws.Credentials, config *Config) (aws.CredentialsProvider, error) {
286+
credentialsProvider := credentials.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, "")
287+
awsConfig := NewAwsConfigWithCredsProvider(credentialsProvider, config.Region, config.STSRegionalEndpoints)
288+
289+
return newFederationTokenCredentialsProvider(awsConfig, config)
290+
}
291+
292+
// utility function to avoid code duplication
293+
// in NewFederationTokenCredentialsProvider and NewFederationTokenCredentialsProviderFromCredentials
294+
func newFederationTokenCredentialsProvider(awsConfig aws.Config, config *Config) (aws.CredentialsProvider, error) {
295+
currentUsername, err := GetUsernameFromSession(awsConfig)
282296
if err != nil {
283297
return nil, err
284298
}
285299

286300
log.Printf("Using GetFederationToken for credentials")
287301
return &FederationTokenProvider{
288-
StsClient: sts.NewFromConfig(cfg),
302+
StsClient: sts.NewFromConfig(awsConfig),
289303
Name: currentUsername,
290304
Duration: config.GetFederationTokenDuration,
291305
}, nil

0 commit comments

Comments
 (0)