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

Provide way to get current credentials (AWS SDKs do not support SSO) #5261

Closed
chrsmith opened this issue Jun 4, 2020 · 22 comments
Closed

Provide way to get current credentials (AWS SDKs do not support SSO) #5261

chrsmith opened this issue Jun 4, 2020 · 22 comments
Labels
configuration feature-request A feature should be added or improved. needs-review This issue or pull request needs review from a core team member. p2 This is a standard priority issue sso v2

Comments

@chrsmith
Copy link

chrsmith commented Jun 4, 2020

My team has set up AWS SSO and is starting to use aws sso login for most of their needs. Everything is working smoothly. However, it appears that AWS SDKs (e.g. Golang apps that call AWS APIs) do not support reading the temporary SSO credentials stored in ~/.aws/cli/cache/~/.aws/sso/cache. (See aws/aws-sdk-go#3186)

And as a result, my team is blocked from adopting AWS SSO because it only works with the AWS CLI but none one our existing tools.

Ideally, the various language-specific AWS SDKs would be able to pick up on the current AWS SSO credentials seamlessly. Honoring AWS_PROFILE or AWS_DEFAULT_PROFILE environment variables, and using the same credential lookup algorithm as the CLI. (I assume that's the on the roadmap eventually.)

However, getting that change fixed across all of the AWS SDKs, as well as them upstreamed into tools that rely on them, will take a long time. And it would be nice to unblock my team until then so they can just rely on aws sso login.

I can see the credential files on my local disk, e.g. ~/.aws/sso/cache/61368d38a2497e42a24a243072108001849d0b07.json. But it isn't clear how to map the current set of environment variables to which JSON file to load.

Could the CLI support some way of returning whatever the credentials it is using? e.g.

# Return the SSO credentials file for the provided profile, or just use AWS_PROFILE, etc.
aws --profile staging-environment \
    sts get-caller-credentials

# Or even better: write the SSO credentials to ~/.aws/credentials. So other tools
# could just read from it.
aws sso write-credentials-to-disk --profile staging-environment

I don't know if there is a better approach here, as I'm do not know the specific differences between credential resolution in the AWS CLI vs. AWS SDKs. But hopefully there is some sort of workaround to make this scenario work?

@chrsmith chrsmith added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Jun 4, 2020
@maestretti
Copy link

maestretti commented Jun 10, 2020

This is an important feature, please prioritize it. Many of the AWS SDKs do not work with SSO forcing a workaround. Most SDKs do support external credential_process handlers via configuration profile. It would be great if aws sso could output credentials in the supported format as a one liner. This would eliminate the need for a number of third-party tools that work around this, and the many AWS customers that are rewriting those tools so as not to expose their credentials.

aws sso get-profile-credentials --profile profileName could get the token from cache and return the STS creds for the given profile in the JSON format for credential_process.

Format for credential helpers to export: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html

Example of a 3rd party tool, also has a list of other tools on the README: https://github.com/victorskl/yawsso

@aleksandrzhiliaev
Copy link

Are there any updates on this feature request?

@ldormoy
Copy link

ldormoy commented Sep 4, 2020

In the meantime, aws-vault v6+ is a nice solution to circumvent this issue in your local environment.

See 99designs/aws-vault#549

@ryansonshine
Copy link
Contributor

I've created an npm package for updating the credentials from the command line for any users out there running node https://github.com/ryansonshine/aws-sso-creds-helper

@kdaily kdaily added configuration sso v2 and removed needs-triage This issue or PR still needs to be triaged. labels Oct 19, 2020
@kdaily
Copy link
Member

kdaily commented Oct 19, 2020

Hi @chrsmith,

Thanks for the request and sorry for the delay. This is also related to #4982. Marking as an SSO feature request.

@benkehoe
Copy link

I've also written a utility in Python that supports AWS SSO credentials. https://github.com/benkehoe/aws-export-credentials

@m17kea
Copy link

m17kea commented May 18, 2021

On the CDK repo they recommend the following wrapper: https://pypi.org/project/aws2-wrap/

@m4dc4p
Copy link

m4dc4p commented Oct 5, 2021

Please, please fix this. aws sso needs to write ~/.aws/credentials.

@tim-finnigan tim-finnigan added the needs-review This issue or pull request needs review from a core team member. label Oct 5, 2021
@rfestag
Copy link

rfestag commented Nov 18, 2021

The solution from @m4dc4p has the added benefit of working well with tools like cdk, which currently do not support SSO named profiles. Workarounds exist, but this seems like a simple solution to a problem affecting anyone trying to work with SSO
aws/aws-cdk#5455

@es50678
Copy link

es50678 commented Nov 20, 2021

unfortunately, this is a blocker for moving to SSO for my organization. sad to see it's been 2 years since the issue was brought up. Is there any significant movement or news on this?

@kdaily
Copy link
Member

kdaily commented Feb 11, 2022

@es50678,

SSO support is nearly universal across AWS SDKs today. As of now, all AWS SDKs except C++ support the credentials from SSO login. You can read more about the feature and support across AWS SDKs here:

I'm made a couple of posts in a related issue. Specifically these two comments:

I also note here that exporting credentials of various types remains a desirable feature for users that we should explore further:

One thing I hear from this is the need to still get out the current set of credentials that a profile would be using, regardless if they come from SSO, an assume role, or even are configured in the credential file itself. This open issue (#5261) proposes that specifically for SSO in response to the lack of support for SSO, but it seems to have further value to those who need the credentials in a predictable format for other uses.

I don't have a timeline on when we would have something implemented at this time.

@TechIsCool
Copy link

Since my team and I needed this sooner than the merged PR, which I am glad to see in motion.

Here is my shell function

get_me_creds() {
  aws sso get-role-credentials \
    --account-id $(aws configure get sso_account_id --profile ${AWS_PROFILE}) \
    --role-name $(aws configure get sso_role_name --profile ${AWS_PROFILE}) \
    --access-token $(find ~/.aws/sso/cache -type f ! -name "botocore*.json" | xargs jq -r .accessToken) \
    --region $(aws configure get region --profile ${AWS_PROFILE}) |\
  jq -r '.roleCredentials |
      {
        "AWS_ACCESS_KEY_ID": .accessKeyId,
        "AWS_SECRET_ACCESS_KEY": .secretAccessKey,
        "AWS_SESSION_TOKEN": .sessionToken,
        "AWS_CREDENTIALS_EXPIRATION": (.expiration / 1000 | todate)
      } | keys[] as $k | "export \($k)=\(.[$k])"'
}

# AWS_PROFILE=example get_me_creds

@datfinesoul
Copy link

datfinesoul commented Jun 24, 2022

Thanks @TechIsCool, that function was extremely helpful. I had to make just a few changes to make it work for me, since I swap between SSO orgs, and some profiles didn't have regions, so I defaulted us-east-1 in my case.

aws-sso-creds() {
  local account_id role_name access_token region
  account_id="$(aws configure get sso_account_id --profile ${AWS_PROFILE})"
  role_name="$(aws configure get sso_role_name --profile ${AWS_PROFILE})"
  region="$(aws configure get region --profile ${AWS_PROFILE})"
  access_token="$( \
    \ls -c "${HOME}/.aws/sso/cache/" | grep -v botocore \
    | sort -nr | cut -d' ' -f2 | head -n1 \
    | xargs -I{} jq -r .accessToken ${HOME}/.aws/sso/cache/{}
  )"
  aws sso get-role-credentials \
    --account-id "${account_id}" \
    --role-name "${role_name}" \
    --region "${region:-us-east-1}" \
    --access-token "${access_token}" \
    --no-sign-request \
    --output json \
    | jq -r '.roleCredentials |
      {
        "AWS_ACCESS_KEY_ID": .accessKeyId,
        "AWS_SECRET_ACCESS_KEY": .secretAccessKey,
        "AWS_SESSION_TOKEN": .sessionToken,
        "AWS_CREDENTIALS_EXPIRATION": (.expiration / 1000 | todate)
      } | keys[] as $k | "export \($k)=\(.[$k])"'
}

Edited: Made my first version OSX compatible

@ebolingerjc
Copy link

Based on the examples above, my version initializes AWS env vars using .aws/sso & .aws/cli caches. It requires good profiles defined in .aws/config file. Compatible with both Zsh and Bash.

Source this file from your login profile (.zprofile or .bash_profile) or directly from the shell:
source ~/setup-aws

Switch between profiles by exporting AWS_PROFILE with a new value & source this script again.
export AWS_PROFILE=staging-env
source ~/setup-aws

export AWS_PROFILE=${AWS_PROFILE:-development}
eval $(aws configure list --profile $AWS_PROFILE > /tmp/aws$$ || \
{ aws sso login > /dev/null; aws configure list --profile $AWS_PROFILE > /tmp/aws$$; } && \
jq -r '.Credentials | {
"AWS_ACCESS_KEY_ID": .AccessKeyId,
"AWS_SECRET_ACCESS_KEY": .SecretAccessKey,
"AWS_SESSION_TOKEN": .SessionToken,
"AWS_EXPIRATION":.Expiration } |
keys[] as $k | "export \($k)=\(.[$k])"' $(grep -l $(grep access_key /tmp/aws$$ | cut -c 32-35) ~/.aws/cli/cache/*))

Save that as ~/setup-aws or wherever you like.

@jsifuentes
Copy link

Love all the script sharing here. I used @datfinesoul's as a base, but I wanted to have more confidence finding the SSO cache file instead of using the first found token. The SSO cache file name is a sha1 based on the sso_start_url, so we can just do the same thing in our script. https://github.com/boto/botocore/blob/b006ff741d12608a9187b873e276abd1fd8eb707/botocore/utils.py#L2364-L2365

This does not automatically set the environment variables. It just outputs the export statements.

aws_creds() {
  local profile="${1:-${AWS_PROFILE}}"
  local account_id="$(aws configure get sso_account_id --profile "${profile}")" \
  role_name="$(aws configure get sso_role_name --profile "${profile}")" \
  region="$(aws configure get region --profile "${profile}")" \
  start_url="$(aws configure get sso_start_url --profile "${profile}")"

  if [ -z "$start_url" ] ; then
    echo "did not find sso_start_url in profile ${profile}"
    exit 1
  fi

  local cache_file="${HOME}/.aws/sso/cache/$(echo -n "$start_url" | sha1sum | awk '{print $1}').json"

  if [ ! -f "$cache_file" ] ; then
    echo "sso creds not found. are you logged into AWS SSO?"
    echo ;
    echo "aws sso login --profile \"${profile}\""
    exit 1
  fi

  local access_token=$(jq -r .accessToken "${cache_file}")

  aws sso get-role-credentials \
    --account-id "${account_id}" \
    --role-name "${role_name}" \
    --region "${region:-us-east-1}" \
    --access-token "${access_token}" \
    --no-sign-request \
    --output json \
    | jq -r '.roleCredentials |
      {
      "AWS_ACCESS_KEY_ID": .accessKeyId,
      "AWS_SECRET_ACCESS_KEY": .secretAccessKey,
      "AWS_SESSION_TOKEN": .sessionToken,
      "AWS_CREDENTIALS_EXPIRATION": (.expiration / 1000 | todate)
      } | keys[] as $k | "export \($k)=\(.[$k])"'
}

@vnagendra
Copy link

@jsifuentes - I am not sure if this is right..

  local cache_file="${HOME}/.aws/sso/cache/$(echo -n "$start_url" | sha1sum | awk '{print $1}').json"

The code you link to is a great find, I don't want to take away from that. But shouldn't you be doing utf-8 encoding before looking up the file based on SHA1? I have absolutely no idea how to do this in bash... 😂

Anyway, I tried the following...

  1. I tried to echo whatever I had in the profile (ex: echo https://mydomain.awsapps.com/start)
  2. Then piped it to sha1sum Ex: echo https://mydomain.awsapps.com/start | sha1sum
  3. That is NOT the same as what I had in the ~/.aws/sso/cache directory

Once I find a solution for this, I'll post here. But you can verify the results fairly easily using this recipe

https://gchq.github.io/CyberChef/#recipe=Encode_text('UTF-8%20(65001)')SHA1(80)&input=aHR0cHM6Ly9teWRvbWFpbi5hd3NhcHBzLmNvbS9zdGFydA

@jsifuentes
Copy link

jsifuentes commented Aug 22, 2022

Hi @vnagendra , I'm not sure I understand your question about the UTF-8 encoding. I don't think it's applicable in this case. Make sure you don't forget to include -n in your echo command. The -n is important because it removes the \n that echo automatically includes at the end, so if you're excluding -n, it will lead to a different sha1 sum.

Hope that helps.

@vnagendra
Copy link

@jsifuentes, Thanks for your help! Using your script as a base, this is what I did.

My specific use case was there were a couple of Terraform modules. I wanted to upgrade them one by one to the latest version of Terraform. The latest version supports AWS SSO credentials, the older versions don't. I've always used direnv for managing a bunch of environment variables. So I used the following script inside the .envrc of the module that was not upgraded. Meaning if you have the newer Terraform (and provider) version, you do not need all this circus.

source_up

function aws_sso() {
  local start_url=$(aws configure get sso_start_url)
  if [ -z "$start_url" ]; then
    echo "No start URL. Will not continue."
    return 1
  fi
  local fname=$(echo -n $start_url | sha1sum | awk '{print $1}')
  local cache_file=${HOME}/.aws/sso/cache/${fname}.json
  if [ ! -f "$cache_file" ]; then
    echo "No SSO cache found. Logged in?"
    return 1
  fi
  local access_token=$(jq -r .accessToken "${cache_file}")
  aws sso get-role-credentials \
    --account-id "$(aws configure get sso_account_id)" \
    --role-name "$(aws configure get sso_role_name)" \
    --region "$(aws configure get region)" \
    --access-token "${access_token}" \
    --no-sign-request \
    --output json | jq -r '.roleCredentials'
}

local f=$(aws_sso)
if [ "$f" ]; then
	AWS_ACCESS_KEY_ID=$(echo -n $f | jq -r '.accessKeyId')
	AWS_SECRET_ACCESS_KEY=$(echo -n $f | jq -r '.secretAccessKey')
	AWS_SESSION_TOKEN=$(echo -n $f | jq -r '.sessionToken')
	echo "Token Expires $(echo -n $f | jq -r '(.expiration / 1000 | todate)')"
fi

Please note, if you use this trick -- you must have the "function aws_sso()" declared inside that specific .envrc (inside the module that is not upgraded). In my case the source_up was pointing to another .envrc (in the parent directory of course) which was just exporting the following

set -a
AWS_PROFILE=sso
AWS_DEFAULT_REGION=us-west-2

With this setup, it was fairly simple..

> cd my-terraform/
direnv: loading ...../.envrc
direnv: export +AWS_DEFAULT_REGION +AWS_PROFILE +ret

> aws sso login
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.us-west-2.amazonaws.com/

Then enter the code:

DHGN-WKFW
Successfully logged into Start URL: https://something.awsapps.com/start

> cd my-not-upgraded-module/
direnv: loading ..../.envrc
direnv: loading ......./.envrc
Token Expires 2022-08-23T22:25:29Z
direnv: export +AWS_ACCESS_KEY_ID +AWS_DEFAULT_REGION +AWS_PROFILE +AWS_SECRET_ACCESS_KEY +AWS_SESSION_TOKEN +ret

jamesls added a commit to jamesls/aws-cli that referenced this issue Nov 2, 2022
This PR builds on the interface proposed in aws#6808 and implements
the additional features proposed in aws#7388.

From the original PRs, the additional features are:

* Added support for an explicit `--format` args to control the output
  format.
* Add support for env vars, powershell/windows vars, and a JSON format
  that's enables this command to be used as a `credential_process`.
* Detect, and prevent infinite recursion when the credential process
  resolution results in the CLI calling itself with the same command.

Closes aws#7388
Closes aws#5261
jamesls added a commit to jamesls/aws-cli that referenced this issue Nov 2, 2022
This PR builds on the interface proposed in aws#6808 and implements
the additional features proposed in aws#7388.

From the original PRs, the additional features are:

* Added support for an explicit `--format` args to control the output
  format.
* Add support for env vars, powershell/windows vars, and a JSON format
  that's enables this command to be used as a `credential_process`.
* Detect, and prevent infinite recursion when the credential process
  resolution results in the CLI calling itself with the same command.

Closes aws#7388
Closes aws#5261
@drewwells
Copy link

drewwells commented Nov 8, 2022

The code examples above work by mutating the AWS environment variables. This can be problematic if you're using multiple environments. I altered a previous script to instead dump credentials into the credentials file. Now I can sso into multiple profiles

I use this to run it
aws_creds | xargs -d\\n -n1 sh -c

aws_creds() {
  local profile="${1:-${AWS_PROFILE}}"
  local account_id="$(aws configure get sso_account_id --profile "${profile}")" \
  role_name="$(aws configure get sso_role_name --profile "${profile}")" \
  region="$(aws configure get region --profile "${profile}")" \
  start_url="$(aws configure get sso_start_url --profile "${profile}")"

  if [ -z "$start_url" ] ; then
    echo "did not find sso_start_url in profile ${profile}"
    exit 1
  fi

  local cache_file="${HOME}/.aws/sso/cache/$(echo -n "$start_url" | sha1sum | awk '{print $1}').json"

  if [ ! -f "$cache_file" ] ; then
    echo "sso creds not found. are you logged into AWS SSO?"
    echo ;
    echo "aws sso login --profile \"${profile}\""
    exit 1
  fi

  local access_token=$(jq -r .accessToken "${cache_file}")

  aws sso get-role-credentials \
    --account-id "${account_id}" \
    --role-name "${role_name}" \
    --region "${region:-us-east-1}" \
    --access-token "${access_token}" \
    --no-sign-request \
    --output json \
    | jq --arg p "${AWS_PROFILE}" -r '.roleCredentials |
      {
      "aws_access_key_id": .accessKeyId,
      "aws_secret_access_key": .secretAccessKey,
      "aws_session_token": .sessionToken,
      "aws_credentials_expiration": (.expiration / 1000 | todate)
      } | keys[] as $k | "aws configure set --profile \($p) \($k) \"\(.[$k])\";\n"'}
}

@tim-finnigan
Copy link
Contributor

I think this can now be closed as #7398 was merged. Please refer to this documentation for aws configure export-credentials: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configure/export-credentials.html

@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

@apogrebnyak
Copy link

For AWS cli > 2.13.5 which uses sso-session sections in configuration file, you need to change the SHA1 source to sso_session value in your profile

...
local sso_session="$(aws configure get sso_session --profile "${profile}")"
...
local cache_file="${HOME}/.aws/sso/cache/$(echo -n "$sso_session" | sha1sum | awk '{print $1}').json"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
configuration feature-request A feature should be added or improved. needs-review This issue or pull request needs review from a core team member. p2 This is a standard priority issue sso v2
Projects
None yet
Development

No branches or pull requests