Repository: 99designs/aws-vault (language: go)

Issue:
v7.0.0 doesn't support `credentials_process` from shared profile when no session is active
- [x] I am using the latest release of AWS Vault
- [x] I have provided my `.aws/config` (redacted if necessary)
- [x] I have provided the debug output using `aws-vault --debug` (redacted if necessary)

I've tried the new release and can't use profiles that assume roles.

My config:
```
[profile my-shared-base-profile]
credential_process=aws-vault exec my-shared-base-profile -j
mfa_serial=arn:aws:iam::1234567890:mfa/danielholz
region=eu-west-1

[profile profile-with-role]
source_profile=my-shared-base-profile
include_profile=my-shared-base-profile
region=eu-west-1
role_arn=arn:aws:iam::12345678901:role/allow-view-only-access-from-other-accounts
```

I ran `aws-vault clear` to drop any active sessions. After than, `aws-vault` seems to hang when trying to use it to get the credentials:

```
$ aws-vault --debug exec my-shared-base-profile -- aws sts get-caller-identity
2023/03/06 17:09:14 aws-vault v7.0.0
2023/03/06 17:09:14 Using prompt driver: osascript
2023/03/06 17:09:14 Loading config file /Users/danielholz/.aws/config
2023/03/06 17:09:14 Parsing config file /Users/danielholz/.aws/config
2023/03/06 17:09:14 [keyring] Considering backends: [keychain]
2023/03/06 17:09:14 Using region "eu-west-1" from AWS_DEFAULT_REGION
2023/03/06 17:09:14 Profile 'default' missing in config file
2023/03/06 17:09:14 profile my-shared-base-profile: using credential process
2023/03/06 17:09:14 Setting subprocess env: AWS_REGION=eu-west-1, AWS_DEFAULT_REGION=eu-west-1
2023/03/06 17:09:14 [keyring] Querying keychain for service="aws-vault", keychain="login.keychain"
2023/03/06 17:09:14 [keyring] Found 2 results
2023/03/06 17:09:14 [keyring] Querying keychain for service="aws-vault", keychain="login.keychain"
2023/03/06 17:09:14 [keyring] Found 2 results
2023/03/06 17:09:14 [keyring] Querying keychain for service="aws-vault", account="credential_process,ZGV2a2l0LXNlY3VyaXR5,,-62135596800", keychain="login.keychain"
2023/03/06 17:09:14 [keyring] No results found
[waited 2 minutes, Ctrl-C]
```

I can get it to work by logging in with a previous version of `aws-vault` first & not clearing the session.

I can also get it to work by removing the `credentials_process` line from the `my-shared-base-profile`, but then I can't use `AWS_PROFILE` or `aws --profile`:

```
$ aws --profile profile-with-role sts get-caller-identity
The source profile "my-shared-base-profile" must have credentials.
```

I see this use case documented in USAGE.md, and I can't see how what I'm doing differs. Is this still working?


Discussion / hints:
Switching the command to the new `aws-vault export --format=json my-shared-base-profile` didn't change the behaviour, nor did using `--no-session` (with `exec` and with `export`)
I'm facing the same problem today. I ran `brew update && brew upgrade` this morning and now aws-cli commands no longer work - I also use `credential_process` on my profiles (in `~/.aws/config`).

A simple `aws --profile MYPROFILE s3 ls` (or using `aws-vault` directly) takes various minutes to show the following error:
> aws-vault: error: exec: Failed to get credentials for MYPROFILE: running command "aws-vault exec MYPROFILE --json --no-session": exit status 1

Removing the `credential_process` fixes the issue, but unfortunately I depend on `credential_process` for other reasons.
> ```
> [profile my-shared-base-profile]
> credential_process=aws-vault exec my-shared-base-role -j
> ```

@dgholz your config doesn't show any `[profile my-shared-base-role]` as is referred to in your credential_process. Can you provide it?

What does running `aws-vault exec my-shared-base-role -j` yourself give you?

> I see this use case documented in USAGE.md, and I can't see how what I'm doing differs. Is this still working?

Where in USAGE do you see this?




> @dgholz your config doesn't show any `[profile my-shared-base-role]` as is referred to in your credential_process. Can you provide it?

typo when redacting, should have been `my-shared-base-profile`. I updated the config I shared in the original post.

> Where in USAGE do you see this?

https://github.com/99designs/aws-vault/blob/master/USAGE.md#invoking-aws-vault-via-credential_process

Fix patch:
diff --git a/vault/config.go b/vault/config.go
index f769b772d..a4fdc1e7c 100644
--- a/vault/config.go
+++ b/vault/config.go
@@ -680,35 +680,3 @@ func (c *ProfileConfig) GetSessionTokenDuration() time.Duration {
 	}
 	return c.NonChainedGetSessionTokenDuration
 }
-
-func (c *ProfileConfig) Validate() error {
-	if c.HasSSOSession() && !c.HasSSOStartURL() {
-		return fmt.Errorf("profile '%s' has sso_session but no sso_start_url", c.ProfileName)
-	}
-
-	n := 0
-	if c.HasSSOStartURL() {
-		n++
-	}
-	if c.HasWebIdentity() {
-		n++
-	}
-	if c.HasCredentialProcess() {
-		n++
-	}
-	if c.HasSourceProfile() {
-		n++
-	}
-	if c.HasRole() &&
-		// these cases require the role to be set in addition, so it's part of
-		// their credential.
-		!c.HasSourceProfile() &&
-		!c.HasWebIdentity() {
-		n++
-	}
-	if n > 1 {
-		return fmt.Errorf("profile '%s' has more than one source of credentials", c.ProfileName)
-	}
-
-	return nil
-}
diff --git a/vault/vault.go b/vault/vault.go
index 300f3e711..5480dad25 100644
--- a/vault/vault.go
+++ b/vault/vault.go
@@ -237,46 +237,44 @@ type tempCredsCreator struct {
 	chainedMfa string
 }
 
-func (t *tempCredsCreator) getSourceCreds(config *ProfileConfig) (sourcecredsProvider aws.CredentialsProvider, err error) {
-	if config.HasSourceProfile() {
-		log.Printf("profile %s: sourcing credentials from profile %s", config.ProfileName, config.SourceProfile.ProfileName)
-		return t.GetProviderForProfile(config.SourceProfile)
-	}
-
-	hasStoredCredentials, err := t.Keyring.Has(config.ProfileName)
-	if err != nil {
-		return nil, err
-	}
-
+func (t *tempCredsCreator) getSourceCreds(config *ProfileConfig, hasStoredCredentials bool) (sourcecredsProvider aws.CredentialsProvider, err error) {
 	if hasStoredCredentials {
 		log.Printf("profile %s: using stored credentials", config.ProfileName)
 		return NewMasterCredentialsProvider(t.Keyring, config.ProfileName), nil
 	}
 
+	if config.HasSourceProfile() {
+		log.Printf("profile %s: sourcing credentials from profile %s", config.ProfileName, config.SourceProfile.ProfileName)
+		return t.GetProviderForProfile(config.SourceProfile)
+	}
+
 	return nil, fmt.Errorf("profile %s: credentials missing", config.ProfileName)
 }
 
 func (t *tempCredsCreator) GetProviderForProfile(config *ProfileConfig) (aws.CredentialsProvider, error) {
-	if err := config.Validate(); err != nil {
+	hasStoredCredentials, err := t.Keyring.Has(config.ProfileName)
+	if err != nil {
 		return nil, err
 	}
 
-	if config.HasSSOStartURL() {
-		log.Printf("profile %s: using SSO role credentials", config.ProfileName)
-		return NewSSORoleCredentialsProvider(t.Keyring.Keyring, config, !t.DisableCache)
-	}
+	if !hasStoredCredentials {
+		if config.HasSSOStartURL() {
+			log.Printf("profile %s: using SSO role credentials", config.ProfileName)
+			return NewSSORoleCredentialsProvider(t.Keyring.Keyring, config, !t.DisableCache)
+		}
 
-	if config.HasWebIdentity() {
-		log.Printf("profile %s: using web identity", config.ProfileName)
-		return NewAssumeRoleWithWebIdentityProvider(t.Keyring.Keyring, config, !t.DisableCache)
-	}
+		if config.HasWebIdentity() {
+			log.Printf("profile %s: using web identity", config.ProfileName)
+			return NewAssumeRoleWithWebIdentityProvider(t.Keyring.Keyring, config, !t.DisableCache)
+		}
 
-	if config.HasCredentialProcess() {
-		log.Printf("profile %s: using credential process", config.ProfileName)
-		return NewCredentialProcessProvider(t.Keyring.Keyring, config, !t.DisableCache)
+		if config.HasCredentialProcess() {
+			log.Printf("profile %s: using credential process", config.ProfileName)
+			return NewCredentialProcessProvider(t.Keyring.Keyring, config, !t.DisableCache)
+		}
 	}
 
-	sourcecredsProvider, err := t.getSourceCreds(config)
+	sourcecredsProvider, err := t.getSourceCreds(config, hasStoredCredentials)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/vault/config_test.go b/vault/config_test.go
index c69c116db..b9f10375b 100644
--- a/vault/config_test.go
+++ b/vault/config_test.go
@@ -616,49 +616,3 @@ source_profile = interim
 		t.Fatalf("Expected transitive_session_tags to be empty, got %+v", baseConfig.TransitiveSessionTags)
 	}
 }
-
-func TestValidConfigValidation(t *testing.T) {
-	f := newConfigFile(t, []byte(`
-[profile foo]
-region         = eu-west-1
-mfa_serial     = arn:aws:iam::9999999999999:mfa/david
-
-[profile foo:staging]
-role_arn       = arn:aws:iam::1111111111111:role/admin
-source_profile = foo
-region         = eu-west-2
-mfa_serial     = arn:aws:iam::9999999999999:mfa/david
-
-[profile foo:production]
-role_arn       = arn:aws:iam::1111111111111:role/admin
-source_profile = foo
-region         = eu-west-2
-mfa_serial     = arn:aws:iam::9999999999999:mfa/david
-credential_process = true
-
-[profile withwebidentity]
-role_arn = arn:aws:iam::123457890:role/foo
-web_identity_token_process = oidccli -issuer=https://example.com -client-id=aws -client-secret=localonly raw
-`))
-	defer os.Remove(f)
-	configFile, _ := vault.LoadConfig(f)
-	configLoader := &vault.ConfigLoader{File: configFile}
-
-	config, _ := configLoader.GetProfileConfig("foo:staging")
-	err := config.Validate()
-	if err != nil {
-		t.Fatalf("Should have validated: %v", err)
-	}
-
-	config, _ = configLoader.GetProfileConfig("foo:production")
-	err = config.Validate()
-	if err == nil {
-		t.Fatalf("Should have failed validation: %v", err)
-	}
-
-	config, _ = configLoader.GetProfileConfig("withwebidentity")
-	err = config.Validate()
-	if err != nil {
-		t.Fatalf("Should have validated withwebidentity: %v", err)
-	}
-}