diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd97e0a..655b87ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Guided setup is now more simple unless user provides the `--advanced` flag #530 * Guided setup now strips leading and trailing spaces for string input + * Revert #491 so SSO auth uses Firefox containers ### New Features @@ -25,6 +26,8 @@ * `config-profiles` now supports the `--aws-config` flag * Added [ecs list](docs/ecs-server.md#listing-profiles) command to list profiles in named slots #517 + * Add [AuthUrlAction](docs/config.md#authurlaction) to override [UrlAction](docs/config.md#urlaction) + during SSO Authentication. #524 ## [v1.12.0] - 2023-08-12 diff --git a/docs/config.md b/docs/config.md index 176c169a..1975a379 100644 --- a/docs/config.md +++ b/docs/config.md @@ -15,6 +15,7 @@ SSOConfig: SSORegion: StartUrl: DefaultRegion: + AuthUrlAction: [clip|exec|print|printurl|open|granted-containers|open-url-in-container] Accounts: # optional block for specifying tags & overrides : Name: @@ -129,6 +130,12 @@ selected (most specific to most generic): 1. At the AWS SSO Instance level: `SSOConfig -> ` 1. At the config file level (default is `us-east-1`) +### AuthUrlAction + +Override the global [UrlAction](#urlaction) when authenticating with your SSO provider +to retrieve an AWS SSO token. Generally only useful when you wish to use your default +browser with one `SSOConfig` block to re-use your existing SSO browser authentication cookie. + ### Accounts The `Accounts` block is completely optional! The only purpose of this block diff --git a/sso/awssso_auth.go b/sso/awssso_auth.go index 00edd3d9..2a3c4c83 100644 --- a/sso/awssso_auth.go +++ b/sso/awssso_auth.go @@ -110,8 +110,13 @@ func (as *AWSSSO) reauthenticate() error { return fmt.Errorf("Unable to get device auth info from AWS SSO: %s", err.Error()) } - urlOpener := url.NewHandleUrl(url.SSOAuthAction(as.urlAction), auth.VerificationUriComplete, - as.browser, as.urlExecCommand) + action := as.urlAction + if as.SSOConfig.AuthUrlAction != url.Undef { + // specific action for authentication? + action = as.SSOConfig.AuthUrlAction + } + + urlOpener := url.NewHandleUrl(action, auth.VerificationUriComplete, as.browser, as.urlExecCommand) urlOpener.ContainerSettings(as.StoreKey(), DEFAULT_AUTH_COLOR, DEFAULT_AUTH_ICON) if err = urlOpener.Open(); err != nil { @@ -262,7 +267,7 @@ func (as *AWSSSO) createToken() error { } else if errors.As(err, &ape) { time.Sleep(retryInterval) } else { - return err + return fmt.Errorf("createToken: %s", err.Error()) } } diff --git a/sso/awssso_auth_test.go b/sso/awssso_auth_test.go index e711090c..7bcdc36b 100644 --- a/sso/awssso_auth_test.go +++ b/sso/awssso_auth_test.go @@ -344,17 +344,96 @@ func TestAuthenticateFailure(t *testing.T) { CreateToken: &ssooidc.CreateTokenOutput{}, Error: fmt.Errorf("some error"), }, + // fourth test + { + RegisterClient: &ssooidc.RegisterClientOutput{ + AuthorizationEndpoint: nil, + ClientId: aws.String("this-is-my-client-id"), + ClientSecret: aws.String("this-is-my-client-secret"), + ClientIdIssuedAt: time.Now().Unix(), + ClientSecretExpiresAt: int64(expires), + TokenEndpoint: nil, + }, + Error: nil, + }, + { + StartDeviceAuthorization: &ssooidc.StartDeviceAuthorizationOutput{ + DeviceCode: aws.String("device-code"), + UserCode: aws.String("user-code"), + VerificationUri: aws.String(""), + VerificationUriComplete: aws.String("verification-uri-complete"), + ExpiresIn: int32(expires), + Interval: 5, + }, + Error: nil, + }, + // fifth test + { + RegisterClient: &ssooidc.RegisterClientOutput{ + AuthorizationEndpoint: nil, + ClientId: aws.String("this-is-my-client-id"), + ClientSecret: aws.String("this-is-my-client-secret"), + ClientIdIssuedAt: time.Now().Unix(), + ClientSecretExpiresAt: int64(expires), + TokenEndpoint: nil, + }, + Error: nil, + }, + { + StartDeviceAuthorization: &ssooidc.StartDeviceAuthorizationOutput{ + DeviceCode: aws.String("device-code"), + UserCode: aws.String("user-code"), + VerificationUri: aws.String("verification-uri"), + VerificationUriComplete: aws.String("verification-uri-complete"), + ExpiresIn: int32(expires), + Interval: 5, + }, + Error: nil, + }, + // sixth test + { + RegisterClient: &ssooidc.RegisterClientOutput{ + AuthorizationEndpoint: nil, + ClientId: aws.String("this-is-my-client-id"), + ClientSecret: aws.String("this-is-my-client-secret"), + ClientIdIssuedAt: time.Now().Unix(), + ClientSecretExpiresAt: int64(expires), + TokenEndpoint: nil, + }, + Error: nil, + }, + { + StartDeviceAuthorization: &ssooidc.StartDeviceAuthorizationOutput{ + DeviceCode: aws.String("device-code"), + UserCode: aws.String("user-code"), + VerificationUri: aws.String("verification-uri"), + VerificationUriComplete: aws.String("verification-uri-complete"), + ExpiresIn: int32(expires), + Interval: 5, + }, + Error: nil, + }, }, } err = as.Authenticate("print", "fake-browser") - assert.Contains(t, err.Error(), "some error") + assert.Contains(t, err.Error(), "Unable to register client with AWS SSO") + + err = as.Authenticate("print", "fake-browser") + assert.Contains(t, err.Error(), "Unable to start device authorization") + + err = as.Authenticate("print", "fake-browser") + assert.Contains(t, err.Error(), "createToken:") err = as.Authenticate("print", "fake-browser") - assert.Contains(t, err.Error(), "some error") + assert.Contains(t, err.Error(), "No valid verification url") + + err = as.Authenticate("invalid", "fake-browser") + assert.Contains(t, err.Error(), "Unsupported Open action") + as.SSOConfig.AuthUrlAction = "invalid" err = as.Authenticate("print", "fake-browser") - assert.Contains(t, err.Error(), "some error") + assert.Contains(t, err.Error(), "Unsupported Open action") } func TestReauthenticate(t *testing.T) { diff --git a/sso/config.go b/sso/config.go index 8ee3b383..cc07260a 100644 --- a/sso/config.go +++ b/sso/config.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/synfinatic/aws-sso-cli/internal/tags" + "github.com/synfinatic/aws-sso-cli/internal/url" "github.com/synfinatic/aws-sso-cli/internal/utils" ) @@ -33,6 +34,10 @@ type SSOConfig struct { StartUrl string `koanf:"StartUrl" yaml:"StartUrl"` Accounts map[string]*SSOAccount `koanf:"Accounts" yaml:"Accounts,omitempty"` // key must be a string to avoid parse errors! DefaultRegion string `koanf:"DefaultRegion" yaml:"DefaultRegion,omitempty"` + + // overrides for this SSO Instance + AuthUrlAction url.Action `koanf:"AuthUrlAction" yaml:"AuthUrlAction,omitempty"` + // passed to AWSSSO from our Settings MaxBackoff int `koanf:"-" yaml:"-"` MaxRetry int `koanf:"-" yaml:"-"` @@ -62,6 +67,11 @@ type SSORole struct { func (c *SSOConfig) Refresh(s *Settings) { c.MaxBackoff = s.MaxBackoff c.MaxRetry = s.MaxRetry + + if c.AuthUrlAction == url.Undef { + c.AuthUrlAction = s.UrlAction + } + for accountId, a := range c.Accounts { a.SetParentConfig(c) for roleName, r := range a.Roles {