Skip to content

Commit

Permalink
Add AuthUrlAction to override UrlAction for SSO auth
Browse files Browse the repository at this point in the history
This basically reverts #491 and goes back to unique Firefox
containers for each SSO provider/AWS SSO instance.  The
AuthUrlAction does allow you to pick a single SSO instance
to use your default browser via `open` to re-use the existing
session cookies you might already have.

Fixes: #524
  • Loading branch information
synfinatic committed Aug 20, 2023
1 parent b616c68 commit c4309d9
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
7 changes: 7 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ SSOConfig:
SSORegion: <AWS Region where AWS SSO is deployed>
StartUrl: <URL for AWS SSO Portal>
DefaultRegion: <AWS_DEFAULT_REGION>
AuthUrlAction: [clip|exec|print|printurl|open|granted-containers|open-url-in-container]
Accounts: # optional block for specifying tags & overrides
<AccountId>:
Name: <Friendly Name of Account>
Expand Down Expand Up @@ -129,6 +130,12 @@ selected (most specific to most generic):
1. At the AWS SSO Instance level: `SSOConfig -> <AWS SSO Instance>`
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
Expand Down
11 changes: 8 additions & 3 deletions sso/awssso_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
}
}

Expand Down
85 changes: 82 additions & 3 deletions sso/awssso_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions sso/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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:"-"`
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit c4309d9

Please sign in to comment.