Last active
November 4, 2025 22:48
-
-
Save atheiman/b99f8f98308ad1b3df9d9cf3a6e12a98 to your computer and use it in GitHub Desktop.
AWS SSO Identity Center accounts and roles config generator - ~/.aws/config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Write content for an AWS config file (~/.aws/config) to stdout based on available SSO accounts and roles | |
| # from the previously executed `aws sso login`. | |
| # | |
| # Example usage: | |
| # | |
| # aws sso login --profile my-existing-sso-profile | |
| # python ./aws_config_generator.py | |
| # | |
| # SSO session name will default to the region of the connected Identity Center / SSO instance. It can be | |
| # customized with environment variable SSO_SESSION_NAME. This is useful if you have multiple Identity Center | |
| # instances to interact with, ie. multiple AWS organizations or Commercial + GovCloud. | |
| # | |
| # aws sso login --profile org1-sso-profile | |
| # SSO_SESSION_NAME=org1 python ~/aws_config_generator.py | |
| # aws sso login --profile org2-sso-profile | |
| # SSO_SESSION_NAME=org2 python ~/aws_config_generator.py | |
| # | |
| # The closest equivalent to this with AWS CLI is: | |
| # | |
| # CACHE_FILE="$(ls -t ~/.aws/sso/cache/*.json | head -n 1)" | |
| # ACCESS_TOKEN="$(jq -r .accessToken "$CACHE_FILE")" | |
| # aws sso list-accounts --access-token $ACCESS_TOKEN --region us-east-1 --output yaml | |
| # | |
| # for ACCOUNT_ID in $(aws sso list-accounts --access-token $ACCESS_TOKEN --region us-east-1 --output text --query 'accountList[].accountId'); do | |
| # aws sso list-account-roles --access-token "$ACCESS_TOKEN" --account-id "$ACCOUNT_ID" --region us-gov-west-1 --output text --query 'roleList[]' | |
| # done | |
| # | |
| import boto3 | |
| import botocore | |
| import configparser | |
| from datetime import datetime, timezone | |
| import glob | |
| import json | |
| import os | |
| import sys | |
| def main(): | |
| sso_cache_files_glob_path = os.path.join( | |
| os.environ["HOME"], ".aws", "sso", "cache", "*.json" | |
| ) | |
| sso_cache_files = glob.glob(sso_cache_files_glob_path) | |
| if not sso_cache_files: | |
| raise Exception( | |
| f"No AWS SSO access tokens found at path '{sso_cache_files_glob_path}'. Login using `aws sso login` before running this script. A valid SSO access token needs to be read from this path." | |
| ) | |
| latest_cache_file_path = max(sso_cache_files, key=os.path.getmtime) | |
| # print("SSO cache JSON file:", latest_cache_file_path) | |
| with open(latest_cache_file_path) as fp: | |
| sso_cache_data = json.load(fp) | |
| # print(json.dumps(sso_cache_data, default=str, indent=2)) | |
| expires_dt = datetime.strptime(sso_cache_data["expiresAt"], "%Y-%m-%dT%H:%M:%SZ") | |
| expires_dt = expires_dt.replace(tzinfo=timezone.utc) | |
| config = configparser.ConfigParser() | |
| sso_session_name = os.environ.get("SSO_SESSION_NAME", sso_cache_data["region"]) | |
| config[f"sso-session {sso_session_name}"] = { | |
| "sso_start_url": sso_cache_data["startUrl"], | |
| "sso_region": sso_cache_data["region"], | |
| "sso_registration_scopes": "sso:account:access", | |
| } | |
| sso = boto3.client("sso", region_name=sso_cache_data["region"]) | |
| accts = [] | |
| try: | |
| for pg in sso.get_paginator("list_accounts").paginate( | |
| accessToken=sso_cache_data["accessToken"] | |
| ): | |
| accts += pg["accountList"] | |
| except sso.exceptions.UnauthorizedException as err: | |
| if datetime.now(timezone.utc) > expires_dt: | |
| raise Exception( | |
| f"accessToken loaded from '{latest_cache_file_path}' is expired (expiresAt: '{expires_dt}'). Login again with `aws sso login` to renew the access token." | |
| ) from err | |
| else: | |
| raise err | |
| for acct in accts: | |
| # print(json.dumps(acct, default=str, indent=2)) | |
| for pg in sso.get_paginator("list_account_roles").paginate( | |
| accessToken=sso_cache_data["accessToken"], | |
| accountId=acct["accountId"], | |
| ): | |
| for role in pg["roleList"]: | |
| # print(json.dumps(role, default=str, indent=2)) | |
| config[f"profile {acct['accountName']}-{role['roleName']}"] = { | |
| "sso_session": sso_session_name, | |
| "sso_account_id": acct["accountId"], | |
| "sso_role_name": role["roleName"], | |
| "region": sso_cache_data["region"], | |
| } | |
| print() | |
| config.write(sys.stdout) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment