Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.15 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.15
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect
github.com/aws/aws-sdk-go-v2/service/iam v1.19.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.4
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aws/smithy-go v1.13.5
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
Expand Down
71 changes: 71 additions & 0 deletions plugins/aws/access_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,77 @@ func TestSourceProfileLoop(t *testing.T) {
})
}

func TestSTSProvisionerYieldsOnSSOProfile(t *testing.T) {
t.Setenv("AWS_PROFILE", "")
t.Setenv("AWS_DEFAULT_REGION", "")
configPath := filepath.Join(t.TempDir(), "awsConfig")
t.Setenv("AWS_CONFIG_FILE", configPath)

file := ini.Empty()

profileLegacy, err := file.NewSection("profile sso-legacy")
require.NoError(t, err)
_, err = profileLegacy.NewKey("sso_start_url", "https://example.awsapps.com/start")
require.NoError(t, err)
_, err = profileLegacy.NewKey("sso_region", "us-east-1")
require.NoError(t, err)
_, err = profileLegacy.NewKey("sso_account_id", "111111111111")
require.NoError(t, err)
_, err = profileLegacy.NewKey("sso_role_name", "ReadOnly")
require.NoError(t, err)

ssoSession, err := file.NewSection("sso-session corp")
require.NoError(t, err)
_, err = ssoSession.NewKey("sso_start_url", "https://corp.awsapps.com/start")
require.NoError(t, err)
_, err = ssoSession.NewKey("sso_region", "eu-west-1")
require.NoError(t, err)

profileSession, err := file.NewSection("profile sso-new")
require.NoError(t, err)
_, err = profileSession.NewKey("sso_session", "corp")
require.NoError(t, err)
_, err = profileSession.NewKey("sso_account_id", "222222222222")
require.NoError(t, err)
_, err = profileSession.NewKey("sso_role_name", "PowerUser")
require.NoError(t, err)

err = file.SaveTo(configPath)
require.NoError(t, err)

// When the active profile is SSO-configured, the STS provisioner must yield
// silently — no env vars, no error — so the SSO Profile provisioner can run.
plugintest.TestProvisioner(t, STSProvisioner{
profileName: "sso-legacy",
newProviderFactory: func(cacheState sdk.CacheState, cacheOps sdk.CacheOperations, fields map[sdk.FieldName]string) STSProviderFactory {
return &mockProviderManager{}
},
}, map[string]plugintest.ProvisionCase{
"legacy SSO profile yields silently": {
ItemFields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "AKIAHPIZFMD5EEXAMPLE",
fieldname.SecretAccessKey: "lBfKB7P5ScmpxDeRoFLZvhJbqNGPoV0vIEXAMPLE",
},
ExpectedOutput: sdk.ProvisionOutput{},
},
})

plugintest.TestProvisioner(t, STSProvisioner{
profileName: "sso-new",
newProviderFactory: func(cacheState sdk.CacheState, cacheOps sdk.CacheOperations, fields map[sdk.FieldName]string) STSProviderFactory {
return &mockProviderManager{}
},
}, map[string]plugintest.ProvisionCase{
"sso_session profile yields silently": {
ItemFields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "AKIAHPIZFMD5EEXAMPLE",
fieldname.SecretAccessKey: "lBfKB7P5ScmpxDeRoFLZvhJbqNGPoV0vIEXAMPLE",
},
ExpectedOutput: sdk.ProvisionOutput{},
},
})
}

func TestResolveLocalAnd1PasswordConfigurations(t *testing.T) {
for _, scenario := range []struct {
description string
Expand Down
25 changes: 25 additions & 0 deletions plugins/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,37 @@ func AWSCLI() schema.Executable {
NeedsAuth: needsauth.IfAll(
needsauth.NotForHelpOrVersion(),
needsauth.NotWithoutArgs(),
// Each entry below intentionally bypasses provisioning. Subcommands not listed here
// (e.g. `aws configure import`, `aws configure export-credentials`) still flow through
// the provisioner because they exchange or mutate credentials and benefit from the
// 1Password-managed surface.
//
// `aws sso login`/`logout` write/erase the very token the SSO Profile provisioner reads
// from disk; provisioning before they run creates a chicken-and-egg failure.
needsauth.NotWhenContainsArgs("sso", "login"),
needsauth.NotWhenContainsArgs("sso", "logout"),
// `aws configure sso` and `aws configure sso-session` set up SSO configuration in
// ~/.aws/config; they touch no AWS APIs and need no provisioned credentials.
needsauth.NotWhenContainsArgs("configure", "sso"),
needsauth.NotWhenContainsArgs("configure", "sso-session"),
// `aws configure list` and `list-profiles` are read-only diagnostic queries against
// the local config; `get` and `set` mutate ~/.aws/config or ~/.aws/credentials but
// make no remote API call. Provisioning would be a no-op at best and a confusing
// "missing credentials" error at worst when the user is just inspecting state.
needsauth.NotWhenContainsArgs("configure", "list"),
needsauth.NotWhenContainsArgs("configure", "list-profiles"),
needsauth.NotWhenContainsArgs("configure", "get"),
needsauth.NotWhenContainsArgs("configure", "set"),
),
Uses: []schema.CredentialUsage{
{
Name: credname.AccessKey,
Provisioner: CLIProvisioner{},
},
{
Name: credname.SSOProfile,
Provisioner: SSOCLIProvisioner{},
},
},
}
}
4 changes: 4 additions & 0 deletions plugins/aws/awslogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func awslogsCli() schema.Executable {
Name: credname.AccessKey,
Provisioner: CLIProvisioner{},
},
{
Name: credname.SSOProfile,
Provisioner: SSOCLIProvisioner{},
},
},
}
}
5 changes: 5 additions & 0 deletions plugins/aws/cache_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
mfaCacheKeyID = "sts-mfa"
assumeRoleCacheKeyID = "sts-assume-role"
ssoRoleCacheKeyID = "sso-role"
)

// stsCacheWriter writes aws temp credentials to cache using the awsCacheKey
Expand All @@ -36,3 +37,7 @@ func getRoleCacheKey(roleArn string, accessKeyID string) string {
func getMfaCacheKey(accessKeyID string) string {
return fmt.Sprintf("%s|%s", mfaCacheKeyID, accessKeyID)
}

func getSSORoleCacheKey(accountID, roleName, sessionKey string) string {
return fmt.Sprintf("%s|%s|%s|%s", ssoRoleCacheKeyID, accountID, roleName, sessionKey)
}
4 changes: 4 additions & 0 deletions plugins/aws/cdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func AWSCDKToolkit() schema.Executable {
Name: credname.AccessKey,
Provisioner: CLIProvisioner{},
},
{
Name: credname.SSOProfile,
Provisioner: SSOCLIProvisioner{},
},
},
}
}
26 changes: 26 additions & 0 deletions plugins/aws/cli_provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,29 @@ func (p CLIProvisioner) Deprovision(ctx context.Context, in sdk.DeprovisionInput
func (p CLIProvisioner) Description() string {
return "Provision environment variables with master credentials or temporary STS credentials AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN"
}

// SSOCLIProvisioner provisions SSO role credentials when the AWS CLI is invoked,
// after stripping any --profile flag from the command line so the AWS CLI does
// not try to assume a role on its own.
type SSOCLIProvisioner struct {
}

func (p SSOCLIProvisioner) Provision(ctx context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) {
profile, editedCommandLine, err := stripAndReturnProfileFlag(out.CommandLine)
if err != nil {
out.AddError(err)
return
}
if editedCommandLine != nil {
out.CommandLine = editedCommandLine
}
NewSSOProvisioner(profile).Provision(ctx, in, out)
}

func (p SSOCLIProvisioner) Deprovision(ctx context.Context, in sdk.DeprovisionInput, out *sdk.DeprovisionOutput) {
// Nothing to do here: environment variables get wiped automatically when the process exits.
}

func (p SSOCLIProvisioner) Description() string {
return "Provision environment variables with temporary AWS IAM Identity Center role credentials AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN"
}
4 changes: 4 additions & 0 deletions plugins/aws/eksctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func eksctlCLI() schema.Executable {
Name: credname.AccessKey,
Provisioner: CLIProvisioner{},
},
{
Name: credname.SSOProfile,
Provisioner: SSOCLIProvisioner{},
},
},
}
}
1 change: 1 addition & 0 deletions plugins/aws/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func New() schema.Plugin {
},
Credentials: []schema.CredentialType{
AccessKey(),
SSOProfile(),
},
Executables: []schema.Executable{
AWSCLI(),
Expand Down
4 changes: 4 additions & 0 deletions plugins/aws/sam.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func AWSSAMCLI() schema.Executable {
Name: credname.AccessKey,
Provisioner: CLIProvisioner{},
},
{
Name: credname.SSOProfile,
Provisioner: SSOCLIProvisioner{},
},
},
}
}
Loading