diff --git a/CHANGELOG.md b/CHANGELOG.md index 4babb14a..c6f931ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Fix `$AWS_REGION` not set by `eval`/`exec` despite being documented #1277 * Fix bugs parsing AccountId's with leading zero's on the command line #1366 +* Default to `AuthWorkflow: device_code` when SSH or WSL sessions are detected #1371 ## [v2.2.2] - 2026-05-17 diff --git a/cmd/aws-sso/setup_wizard_cmd.go b/cmd/aws-sso/setup_wizard_cmd.go index bd1bc185..ae932e37 100644 --- a/cmd/aws-sso/setup_wizard_cmd.go +++ b/cmd/aws-sso/setup_wizard_cmd.go @@ -127,7 +127,7 @@ func setupWizard(ctx *RunContext, reconfig, addSSO, advanced bool) error { // check if we are in a ssh session or WSL2 promptedOpen := false - if prompt.IsRemoteHost() || os.Getenv("WSL_DISTRO_NAME") != "" { + if prompt.IsRemoteHost() || prompt.IsWSL() { // users need to modify the default open action promptOpen(s) promptedOpen = true diff --git a/docs/config.md b/docs/config.md index 4f76d33c..92092e78 100644 --- a/docs/config.md +++ b/docs/config.md @@ -158,7 +158,7 @@ Valid values: that `aws-sso` is running on the same machine as the browser.** It is not supported on remote/headless hosts; use `device_code` in those environments. -If `AuthWorkflow` is omitted, `pkce` is used. +If `AuthWorkflow` is omitted, `pkce` is used _unless_ a current SSH/WSL session are detected. ### Accounts diff --git a/internal/prompt/remote_host.go b/internal/prompt/remote_host.go index 16fd0e26..bc58d22d 100644 --- a/internal/prompt/remote_host.go +++ b/internal/prompt/remote_host.go @@ -29,3 +29,9 @@ func IsRemoteHost() bool { _, inSSHSession := os.LookupEnv("SSH_TTY") return inSSHSession } + +// IsWSL returns true when running in a WSL environment. +func IsWSL() bool { + _, inWSLSession := os.LookupEnv("WSL_DISTRO_NAME") + return inWSLSession +} diff --git a/internal/prompt/remote_host_test.go b/internal/prompt/remote_host_test.go index e45eaf07..8fdf8fae 100644 --- a/internal/prompt/remote_host_test.go +++ b/internal/prompt/remote_host_test.go @@ -34,10 +34,19 @@ func TestUtilsSuite(t *testing.T) { s := &UtilsTestSuite{} suite.Run(t, s) } + func TestIsRemoteHost(t *testing.T) { - os.Setenv("SSH_TTY", "FOOBAR") + t.Setenv("SSH_TTY", "FOOBAR") assert.True(t, IsRemoteHost()) - os.Unsetenv("SSH_TTY") + assert.NoError(t, os.Unsetenv("SSH_TTY")) assert.False(t, IsRemoteHost()) } + +func TestIsWSL(t *testing.T) { + t.Setenv("WSL_DISTRO_NAME", "Ubuntu") + assert.True(t, IsWSL()) + + assert.NoError(t, os.Unsetenv("WSL_DISTRO_NAME")) + assert.False(t, IsWSL()) +} diff --git a/internal/sso/auth/awssso_auth.go b/internal/sso/auth/awssso_auth.go index 0d739f29..6b2da74e 100644 --- a/internal/sso/auth/awssso_auth.go +++ b/internal/sso/auth/awssso_auth.go @@ -28,6 +28,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awssso "github.com/aws/aws-sdk-go-v2/service/sso" + "github.com/synfinatic/aws-sso-cli/internal/prompt" "github.com/synfinatic/aws-sso-cli/internal/sso/oidc" "github.com/synfinatic/aws-sso-cli/internal/storage" "github.com/synfinatic/aws-sso-cli/internal/uri" @@ -292,9 +293,16 @@ func (as *AWSSSO) saveToken(token storage.CreateTokenResponse) error { return nil } -// getAuthWorkflow returns the AuthWorkflow to use for this AWSSSO instance, defaulting -// to PKCE if not set. +// getAuthWorkflow returns the AuthWorkflow to use for this AWSSSO instance. +// In WSL/remote host sessions, we default to device_code when unset; otherwise +// we default to PKCE when unset. func (as *AWSSSO) getAuthWorkflow() oidc.AuthWorkflow { + if prompt.IsWSL() || prompt.IsRemoteHost() { + if as.SSOConfig == nil || as.SSOConfig.AuthWorkflow == "" { + return oidc.AuthWorkflowDeviceCode + } + } + if as.SSOConfig == nil { return oidc.AuthWorkflowPKCE } diff --git a/internal/sso/auth/awssso_auth_test.go b/internal/sso/auth/awssso_auth_test.go index 909f98dc..5674a3a7 100644 --- a/internal/sso/auth/awssso_auth_test.go +++ b/internal/sso/auth/awssso_auth_test.go @@ -105,6 +105,9 @@ func TestStoreKey(t *testing.T) { } func TestAuthWorkflowSelection(t *testing.T) { + assert.NoError(t, os.Unsetenv("WSL_DISTRO_NAME")) + assert.NoError(t, os.Unsetenv("SSH_TTY")) + as := &AWSSSO{} assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowPKCE) assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeAuthorizationCode), string(storage.GrantTypeRefreshToken)}) @@ -114,6 +117,37 @@ func TestAuthWorkflowSelection(t *testing.T) { assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowDeviceCode) assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeDeviceCode), string(storage.GrantTypeRefreshToken)}) assert.Equal(t, as.GrantTypes(), []storage.GrantType{storage.GrantTypeDeviceCode, storage.GrantTypeRefreshToken}) + + t.Setenv("WSL_DISTRO_NAME", "Ubuntu") + + as = &AWSSSO{} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowDeviceCode) + assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeDeviceCode), string(storage.GrantTypeRefreshToken)}) + assert.Equal(t, as.GrantTypes(), []storage.GrantType{storage.GrantTypeDeviceCode, storage.GrantTypeRefreshToken}) + + as.SSOConfig = &ssoconfig.SSOConfig{} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowDeviceCode) + assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeDeviceCode), string(storage.GrantTypeRefreshToken)}) + assert.Equal(t, as.GrantTypes(), []storage.GrantType{storage.GrantTypeDeviceCode, storage.GrantTypeRefreshToken}) + + as.SSOConfig = &ssoconfig.SSOConfig{AuthWorkflow: oidc.AuthWorkflowPKCE} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowPKCE) + assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeAuthorizationCode), string(storage.GrantTypeRefreshToken)}) + assert.Equal(t, as.GrantTypes(), []storage.GrantType{storage.GrantTypeAuthorizationCode, storage.GrantTypeRefreshToken}) + + assert.NoError(t, os.Unsetenv("WSL_DISTRO_NAME")) + t.Setenv("SSH_TTY", "/dev/pts/1") + + as = &AWSSSO{} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowDeviceCode) + assert.Equal(t, as.authGrantTypes(), []string{string(storage.GrantTypeDeviceCode), string(storage.GrantTypeRefreshToken)}) + assert.Equal(t, as.GrantTypes(), []storage.GrantType{storage.GrantTypeDeviceCode, storage.GrantTypeRefreshToken}) + + as.SSOConfig = &ssoconfig.SSOConfig{} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowDeviceCode) + + as.SSOConfig = &ssoconfig.SSOConfig{AuthWorkflow: oidc.AuthWorkflowPKCE} + assert.Equal(t, as.getAuthWorkflow(), oidc.AuthWorkflowPKCE) } func TestAuthenticateSteps(t *testing.T) {