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 SSO #1051

Merged
merged 8 commits into from
Jan 11, 2022
Merged

Add Support for SSO #1051

merged 8 commits into from
Jan 11, 2022

Conversation

rcoh
Copy link
Collaborator

@rcoh rcoh commented Jan 7, 2022

This commit adds support for the SSO credential provider, which enables the aws-config to support using SSO when specified in ~/.aws/config.

Motivation and Context

awslabs/aws-sdk-rust#4

Description

This adds support for SSO, both as a standalone credentials provider, and with AWS config.

Testing

  • http traffic test
  • assorted UTs
  • profile tests

Checklist

  • I have updated CHANGELOG.next.toml if I made changes to the smithy-rs codegen or runtime crates
  • I have updated CHANGELOG.next.toml if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

This commit adds support for the SSO credential provider, which enables the aws-config to support using SSO when specified in `~/.aws/config`.
@rcoh rcoh requested a review from a team as a code owner January 7, 2022 18:41
@github-actions
Copy link

github-actions bot commented Jan 7, 2022

A new doc preview is ready to view.

@github-actions
Copy link

github-actions bot commented Jan 7, 2022

A new generated diff is ready to view.

Copy link
Collaborator

@jdisanti jdisanti left a comment

Choose a reason for hiding this comment

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

Overall looks great! I just have some questions and some minor fixes.

aws/rust-runtime/aws-config/src/json_credentials.rs Outdated Show resolved Hide resolved
aws/rust-runtime/aws-config/src/sso.rs Outdated Show resolved Hide resolved
aws/rust-runtime/aws-config/src/sso.rs Outdated Show resolved Hide resolved
aws/rust-runtime/aws-config/src/sso.rs Outdated Show resolved Hide resolved
aws/rust-runtime/aws-config/src/sso.rs Outdated Show resolved Hide resolved
rcoh and others added 2 commits January 11, 2022 09:25
- Improve error messages
- zeroize token
- add track_caller to improve test failure error messages
Co-authored-by: John DiSanti <jdisanti@amazon.com>
@rcoh rcoh requested a review from jdisanti January 11, 2022 14:27
@github-actions
Copy link

A new doc preview is ready to view.

@github-actions
Copy link

A new doc preview is ready to view.

@github-actions
Copy link

A new generated diff is ready to view.

@github-actions
Copy link

A new doc preview is ready to view.

@github-actions
Copy link

A new generated diff is ready to view.

@github-actions
Copy link

A new generated diff is ready to view.

Copy link
Collaborator

@jdisanti jdisanti left a comment

Choose a reason for hiding this comment

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

Awesome!

@rcoh rcoh merged commit 00bc624 into main Jan 11, 2022
@rcoh rcoh deleted the sso branch January 11, 2022 18:42
@naftulikay
Copy link

Excited to see this passed! This will certainly help in building applications which leverage SSO.

I'm trying to find out how the AWS CLI does aws sso login to see if I can implement that logic in Rust, as the flow I'd like to emulate is:

  1. If cached credentials that are not expired exist for the given profile, use them, use STS to get role credentials, proceed.
  2. If cached credentials do not exist or are expired, optionally implement the browser launch flow to renew credentials, then proceed.

My goal is naturally that for a given AWS utility, I want it to work, whether credentials are coming from environment variables, from the credentials file(s), from an SSO configuration, or from the EC2 metadata server. Does anyone know enough to point me to where aws sso login is implemented in the Python AWS CLI? I have dug around and the functionality has not been easy to find.

@rcoh
Copy link
Collaborator Author

rcoh commented Jan 12, 2022

I ran aws sso login --debug, which lead to this callstack:

Traceback (most recent call last):
  File "awscli/clidriver.py", line 457, in main
  File "awscli/clidriver.py", line 586, in __call__
  File "awscli/customizations/commands.py", line 191, in __call__
  File "awscli/customizations/sso/login.py", line 40, in _run_main
  File "awscli/customizations/sso/login.py", line 64, in _get_sso_config

this code was hard to track down! I eventually found it here: https://fossies.org/linux/www/aws-cli-2.4.10.tar.gz/

@naftulikay
Copy link

After loads of reverse engineering and dropping debug points, it's all handled in the aws_sdk_ssooidc crate APIs. The flow looks like this:

Register your Client

First, call RegisterClient with a client name of your choice and a client type of public. This will return a RegisterClientOutput, which contains a couple things you'll need to care about, the client_id and the client_secret. The client_secret_expires_at is useful if you're going to cache these credentials. botocore caches these intermediate credentials in JSON at ~/.aws/sso/cache/botocore-client-id-${AWS_REGION}.json if you'd like to take a look.

Start Device Authorization

Next, call StartDeviceAuthorization with the client_id, client_secret, and the SSO start URL from your SSO configuration (usually something like https://d-${SOMETHING}.awsapps.com/start. You'll get a device_code, user_code, verification_uri, and verification_uri_complete. An expires_in will also be returned demonstrating when these credentials expire. Finally, save the interval value, as you'll use this interval to respectfully poll for token creation.

You're now ready to launch the browser.

Launch the Browser

Launch your browser to verification_uri_complete, and it's time to start polling to await completion of the user login flow.

Create and Obtain the Token

In a loop, and respectfully, attempt to CreateToken by passing in urn:ietf:params:oauth:grant-type:device-code as the grant_type, pass in your client_id and client_secret from before, and finally pass the device_code.

You'll need to match on error responses, because CreateTokenErrorKind has a lot of variants that you care about, namely AuthorizationPendingException, which means that you should keep polling, SlowDownException which means you should poll less often, and the rest of them largely mean that the request failed.

Caching the Token

You'll end up with a CreateTokenOutput, which you can then use for all the rest of the magic you'll need to do, see my repository for the additional steps needed: naftulikay/aws-sso-env.

The CreateTokenOutput will contain an access_token and an expires_in field to denote how long the token is valid for. To cache this value and play nicely with other tools, render it to ~/.aws/sso/cache/$(shasum "$SSO_START_URL").json in the following JSON format:

[
 {
    "startUrl": "$START_URL",
    "region": "$REGION",
    "accessToken": "$ACCESS_TOKEN",
    "expiresAt": "$RFC3339_EXPIRY_TIMESTAMP"
 }
]

Obtaining Actual IAM Credentials

Now that you've gone through all of this, you can use the access token with aws_sdk_sso to get role credentials:

use aws_sdk_sso::{Client as SsoClient};
use aws_sdk_sso::model::RoleCredentials;

let client = SsoClient::from_conf(config);

let role_credentials: RoleCredentials = client
    .get_role_credentials()
    .account_id(sso_account_id)
    .role_name(sso_role_name)
    .access_token(access_token)
    .send()
    .await?
    .role_credentials
    .unwrap();

This was a fun little deep-dive for me and hopefully it helps someone else as well. I'm not too familiar with how OIDC works, but I think I'm starting to understand a little bit.

@raunakdoesdev
Copy link

@naftulikay You are the hero we need but do not deserve... thank you so much. This is actually saving my company right now!

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.

4 participants