Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AWS SSO profiles #549

Merged
merged 3 commits into from
Apr 21, 2020
Merged

Conversation

itsdalmo
Copy link
Contributor

This PR hopefully closes #449 by adding support for AWS SSO.

Given a AWS SSO Profile configuration in ~/.aws/confg:

[profile Administrator-123456789012]
sso_start_url=https://aws-sso-portal.awsapps.com/start
sso_region=eu-west-1
sso_account_id=123456789012
sso_role_name=Administrator

This PR allows aws-vault to obtain short lived credentials for an SSO profile, which can be used by e.g. exec (and login), and produces output as seen below:

$ aws-vault exec Administrator-123456789012 -- aws sts get-caller-identity

Attempting to automatically open the SSO authorization page in your default
browser. If the browser does not open or you wish to use a different device to
authorize this request, open the following URL:

https://device.sso.eu-west-1.amazonaws.com/?user_code=HZZB-FPRL
(Use Ctrl-C to abort)

{
    "UserId": "AROAWH...",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ReadOnly_f4296a2ab1f54217/..."
}

In the above example, aws-vault would take care of:

  1. Creating and registering a new AWS SSO client (one per unique sso_start_url in your config).
  2. Initiating the client authorization flow in AWS SSO (and opening the default browser for the user).
  3. Creating a new access token after the client has been authorised by the user.
  4. Creating a new temporary session for the exec command.

The client credentials (ID and secret), access token and session credentials will all be cached in the Keyring, and automatically refreshed (by repeating the relevant steps above) when they expire. The AWS SSO Client is valid for 3 months by default, while the access token and session expiration depends on the settings for your AWS SSO Organization.

Please take a look and let us know what you think 😅

Co-authored-by: Rickard Löfström <[email protected]>

for {
// Sleep to allow the user to complete the login flow
time.Sleep(3 * time.Second)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder about this delay, should it be configurable? I am not very fast on the keyboard, it could definitely take me longer than 3 seconds to enter my credentials in AWS SSO via my 3rd party IdP, including finding my MFA device and completing the authentication. Or is it that the user should already be logged into the browser ahead of time and this time is for clicking a single approve button?

Copy link
Contributor

@rickardl rickardl Apr 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, retries every 3 seconds. If you have a SSO session against your IDP, it might not require you to re-authenticate with the MFA, so waiting longer might be annoying as it’s almost instant after the first initial authentication.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some digging around to see how the AWS CLI does this, and ended up here: https:/boto/botocore/blob/v2/botocore/utils.py#L1663

And from their comments around the default retry interval and slow down delay:
https://tools.ietf.org/html/draft-ietf-oauth-device-flow-15#section-3.5

I.e., we (like the AWS CLI) should just be using the interval that is specified in the response from the API when calling StartDeviceAuthorization, and fall back to the 5s defaults defined by the RFC. d7ce500 implements this, and also adds handling for the SlowDownException error code.

From my testing, this has effectively reduced the retry interval to 1s, which makes AWS Vault more snappy to respond after I had completed the browser login.

@statik
Copy link

statik commented Apr 1, 2020

I've tested this branch with my AWS SSO configured to use a 3rd Party IdP (Google Apps SAML), and it works well so far 😍

@itsdalmo
Copy link
Contributor Author

itsdalmo commented Apr 6, 2020

Hi @mtibben! Sorry to ping you like this; I was wondering when you might have time to look at this PR (since you seem to be the one maintaining aws-vault these days) - do you need anything else from us before reviewing and/or merging the PR?

return nil, err
}

fmt.Printf(authorizationTemplate, aws.StringValue(auth.VerificationUriComplete))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we dump this to stdout with fmt.Printf it may interfere with the output of whatever command you're executing... can we get this output to stderr, preferably through kingpin somehow? Alternatively should we be using the prompt driver to achieve this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Since prompt is only used for requesting user input (which we are not doing here), and kingpin is not currently used anywhere outside the cli/ page right now, I think it makes most sense to use Fprintf(os.Stderr, ...) here.


expiration := aws.MillisecondsTimeValue(resp.RoleCredentials.Expiration)

// This is needed because sessions.Store expects a sts.Credentials object.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I'm hoping to refactor sessions.Store completely and turn it into an ordinary Provider

}
return &SSOAccessToken{
Token: aws.StringValue(t.AccessToken),
Expiration: time.Now().Add(time.Duration(aws.Int64Value(t.ExpiresIn)) * time.Second),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we break this up to make it more readable?

@@ -222,6 +222,25 @@ role_arn = arn:aws:iam::123456789012:role/target

You can also set the `mfa_serial` with the environment variable `AWS_MFA_SERIAL`.

## AWS Single Sign-On (AWS SSO)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this to the TOC at the top

)

// CachedSSORoleCredentialsProvider uses the keyring to cache SSO Role sessions.
type CachedSSORoleCredentialsProvider struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I was planning a sessions refactor, but it may need to encompass this. Mind if I ping you if I get to this?

retryInterval = time.Duration(i) * time.Second
}

for {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to capture the retry logic in its own separate function? There's an example of this in

func retry(maxTime time.Duration, sleep time.Duration, f func() error) (err error) {

@mtibben
Copy link
Member

mtibben commented Apr 16, 2020

This is fantastic, thank you @itsdalmo!

I know from experience that testing this stuff is really really hard as you basically have to mock out everything. But if there are any simple tests you can write to prevent me breaking this going forward that would be ideal

Co-authored-by: Kristian Dalmo Olsen <[email protected]>
@glenjamin
Copy link

glenjamin commented Dec 14, 2020

Does anyone have this working with credentials_process?

edit: I've been able to get this to work with credentials process and terraform by having two profiles:

[profile xyz]
region=us-east-1
credential_process=aws-vault exec --json xyz-sso

[profile xyz-sso]
sso_start_url = https://someone.awsapps.com/start
sso_region = us-east-1
sso_account_id = 045464924556
sso_role_name = TheRole
region = us-east-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support AWS SSO
5 participants