Skip to main content

brolly

British informal for umbrella — one login covers every profile under an AWS SSO session.

PyPI version Python versions CI License

A small, pure-Python CLI for AWS IAM Identity Center (SSO). Authenticate once against an [sso-session] and every profile under it is usable — brolly verifies and refreshes credentials cheaply, repoints a profile's account/role in place, adds new profiles, and ships a freshness-aware prompt pill. It never touches $AWS_PROFILE.

brolly                                  verify/refresh the current profile (same as `brolly refresh`)
brolly login [-s <session>]             force a fresh device-code login for a session
brolly switch                           repoint the current profile's account/role
brolly refresh [<profile>] [-s <session>]
brolly add <profile> [-s <session>]
brolly ls [--no-check]                  list every sso-session and its profiles, with token status
brolly secure enable|disable [-s <session>]   opt-in: keep a session's token in your OS keychain

Install

$ uv tool install brolly      # recommended
$ pipx install brolly
$ pip install brolly

Needs Python 3.14+ and the AWS CLI v2 on your $PATH — brolly shells out to aws sso login and aws configure set; everything else goes through boto3. The AWS CLI is not a pip dependency (install it separately). The arrow-key picker needs a POSIX TTY (Linux, macOS, WSL) and falls back to numeric selection without one. Secure mode is built in — nothing extra to install.

Mental model

~/.aws/config has two layers:

  • [sso-session <name>] — holds sso_start_url / sso_region. Logging in caches one token per session, keyed by SHA1 of the session name (~/.aws/sso/cache/<sha1>.json).
  • [profile <name>] — references a session via sso_session and adds its own sso_account_id / sso_role_name. Any number of profiles can point at the same session.

Because the token is cached per-session, every profile under one session shares a single login — which is where the name comes from.

$AWS_PROFILE is a fixed handle you set yourself. brolly never touches it: switch changes what a profile resolves to, add creates new profiles, and refresh targets the aws CLI with --profile rather than mutating your environment. No shell wrapper, nothing rewriting your env behind your back.

Commands

brolly

Shorthand for brolly refresh on the current $AWS_PROFILE — cheap, no browser unless the session is dead.

$ brolly
✔  corp-dev live → arn:aws:sts::111111111111:assumed-role/AdministratorAccess/alex

brolly login [-s <session>]

Forces a fresh device-code login for a session, unconditionally — session-scoped, not profile-scoped. Rarely needed: refresh already logs in whenever a session is actually dead.

brolly switch

Interactively repoints the current $AWS_PROFILE to a different account/role under its own session. Arrow-key picker (/ or j/k, enter to select, q to quit); accounts first, then roles (skipped when there's only one). Rewrites sso_account_id, sso_role_name, and sso_account_name in place — recording the account name is what lets the prompt show something friendlier than a raw ID.

brolly switch: an arrow-key picker choosing an account, then a role, then the confirmation line

The circle marks where the profile points now, the highlighted row is the cursor. $AWS_PROFILE is untouched — only what it resolves to changed, which the next prompt shows.

brolly refresh [<profile>] [-s <session>]

The cheap, no-browser daily driver. -s asserts which session you're operating in; the target profile must actually belong to it, so crossing sessions is always deliberate:

$ AWS_PROFILE=corp-dev brolly refresh corp-prod
✔  corp-prod live → arn:aws:sts::222222222222:assumed-role/AdministratorAccess/alex

$ AWS_PROFILE=customer-admin brolly refresh corp-prod
profile 'corp-prod' is under session 'corp', not 'customer' — use -s corp to target it

Under the hood it runs aws sts get-caller-identity --profile <target>, which forces credential resolution and lets botocore refresh the hourly token — but only when that token is lapsed or near expiry, so a healthy one isn't reset. If the 7-day session is dead it falls through to a device-code login and retries. It also backfills a missing sso_account_name (one list_accounts call, made only when absent).

brolly add <profile> [-s <session>]

Creates a new profile under an existing session, walks the same picker, and leaves it authenticated. Refuses if the profile already exists (use switch) or the session is unknown, and copies region/output from a sibling profile when there is one.

$ brolly add corp-qa                     # session inferred from $AWS_PROFILE
$ brolly add customer-admin -s customer  # explicit session

It does not change $AWS_PROFILE. If you Ctrl-C out of the picker the profile skeleton is already written, so finish it with export AWS_PROFILE=<name> then brolly switch rather than re-running add.

brolly ls [--no-check]

Lists every sso-session and its profiles as one aligned table, with live/idle/gone token status, expiry, and a footer naming what $AWS_PROFILE currently resolves to — ls -l for brolly, where ps1 is the glance. By default it silently probes each session over the network (an SSO refresh-token grant, never an interactive login) to tell a truly-dead session apart from a merely-lapsed token; --no-check skips that and reads local expiry files only.

brolly ls output: two sso-sessions with their profiles, token status, accounts, roles and regions

The current profile is the orange one. secure marks which profiles keep their token in the OS keychain, and the whole table needs a Nerd Font for its glyphs, same as the prompt pill.

Common tasks

Situation Command
Verify/refresh current creds brolly
Force a fresh login brolly login
Wrong account or role for the current profile brolly switch
New profile under the current session brolly add <name> then export AWS_PROFILE=<name>
New profile under a different session brolly add <name> -s customer
Refresh a profile in another session brolly refresh <profile> -s <session>
Interrupted a brolly add mid-picker export AWS_PROFILE=<name> then brolly switch
See every session/profile & token status brolly ls (add --no-check to skip the network probe)
Keep a session's token out of plaintext brolly secure enable -s <session>

Shell prompt integration

brolly ps1 renders a colored session/profile · account pill reflecting the local, filesystem-only state of the session token — no network call, no keychain access, no boto3 import:

  • live (amber) — token still valid.
  • idle (grey, clock glyph) — cached but lapsed; refreshes automatically on next use.
  • gone (red, cross glyph) — no cached token; run brolly.
  • plain (neutral grey) — not an SSO profile.

brolly ps1 prompt pill shown in its live, idle, gone, and plain states

Add it to your PS1. It needs a Nerd Font for the separators and glyphs:

export PS1='$(brolly ps1)\u@\h:\w\$ '

It reads whichever store the profile actually uses — the expiry sidecar for secure-mode profiles, the stock cache otherwise — so it stays accurate with no configuration. A dead 7-day session can't be detected locally, so it reads as idle rather than gone. Cost is ~10ms per prompt.

Secure mode (OS keychain)

By default brolly is a thin layer over the stock plaintext ~/.aws/sso/cache — the same cache the aws CLI uses. Secure mode is an opt-in that moves the SSO token into your OS keychain and registers brolly as each profile's credential_process, so every SDK and the aws CLI keep working with nothing but $AWS_PROFILE — no shell wrapper, no plaintext token. It's built in; keyring ships as a dependency.

brolly secure enable [-s <session>]

Logs the session in (a device-code login brolly runs itself), rewrites every profile under it to use credential_process, and deletes the now-redundant plaintext token. Idempotent — re-run it after brolly add to pull new profiles into secure mode.

$ brolly secure enable -s corp
→ keychain backend: pass (gpg-agent)
✓ authorized — SSO token stored in your OS keychain
✓ removed plaintext token cache for session 'corp'
✓ secure mode on for session 'corp' — 3 profile(s) now use the OS keychain

Nothing else about your workflow changes: export AWS_PROFILE=corp-prod and every SDK resolves credentials through brolly. refresh and switch keep working and stay in secure mode.

brolly secure login / brolly secure disable

login re-authorizes a session whose 7-day window has fully lapsed — rarely needed, since refresh is silent. disable reverts every secured profile to a stock plaintext-cache profile and deletes the keychain token: a clean, complete undo.

How it works

  • The token (with its refresh token) lives in the keychain under service brolly-sso, keyed the way botocore keys its own cache. brolly plugs a keychain-backed cache into botocore's token provider, so silent hourly refresh still happens — no reimplementation, just a different vault.
  • A secured profile keeps sso_session but moves sso_account_id / sso_role_name under brolly_sso_* and adds credential_process. That combination deactivates botocore's built-in SSO credential provider so resolution flows through brolly — otherwise botocore would find the now-absent plaintext token and fail.
  • The prompt pill reads a small non-secret expiry sidecar (~/.aws/brolly/<sha1>.json) rather than the keychain, so it stays a cheap filesystem check.
  • No environment variable to keep exported. The chosen backend is saved to ~/.aws/brolly/config.json and re-selected on every call, so resolution works from any venv, cron job, or IDE. (It does run the brolly command, so keep brolly on your PATH.)

Choosing a backend

credential-process is spawned fresh and non-interactively on every cold credential resolution, so the backend has to be one that stays unlocked for your session — macOS Keychain and gnome-keyring / KWallet already work that way. If keyring can't find a usable backend, brolly says so and stops rather than failing obscurely.

secure enable auto-detects: a live OS keychain if there is one, otherwise pass when its store is initialized. Whatever it picks is saved and reused, so you normally never name a backend; --backend <dotted.path> overrides. Backends must live in brolly's own environment, since credential_process runs the brolly executable — keyring_pass is bundled, and others install alongside brolly with uv tool install brolly --with <pkg> (or pipx inject brolly <pkg>).

Linux without a desktop: use pass + gpg-agent. pass stores each secret gpg-encrypted and gpg-agent is the session daemon that keeps your key unlocked, so reads are silent once it's warm — and encrypting a write needs no passphrase, so token refreshes never prompt.

$ sudo apt install pass            # the pass CLI itself
$ pass init <your-gpg-key-id>      # initialize the store
$ brolly secure enable -s corp     # auto-detects pass

gpg-agent must already be warm when credential-process runs, since it has no TTY to prompt on. Either raise max-cache-ttl in ~/.gnupg/gpg-agent.conf and unlock once per login, or preset the passphrase with gpg-preset-passphrase for zero-touch warming — the pattern used by borg-backup.

Other vaults. Anything with a keyring backend works via --backend — for example onepassword-keyring with a 1Password service-account token. keyrings.alt's EncryptedKeyring is a last resort: with no daemon to keep it unlocked it prompts in every new process, meaning a prompt on roughly every aws call and a hard failure anywhere non-interactive.

How it compares

No single existing tool combines what brolly does. The niche is the combination: native to the sso-session block, in-place switch/add, never mutating $AWS_PROFILE, a shipped prompt pill, and pure-Python/pip.

  • aws-sso-util is the closest sibling — pure-Python, config-native, non-invasive — but has no prompt integration, no in-place single-profile repoint, and predates the sso-session block.
  • granted and aws-sso-cli are excellent, but they're Go binaries that install a shell wrapper and generate bulk static profiles.
  • awsume targets classic IAM role assumption, not Identity Center.

Positioning, not disparagement: brolly's plaintext default is deliberately thin, and keychain storage is an opt-in rather than the always-on model of aws-vault, granted, or aws-sso-cli.

Design notes & caveats

  • switch rewrites ~/.aws/config globally. A profile lives in one shared file, so repointing a profile name silently retargets any other shell pinned to that same name on its next command. Safe use = one distinct profile name per concurrent context. This is the one real footgun.
  • Tokens sit in the stock plaintext cache by default — thin by design. When you want them off disk, opt into secure mode.

Roadmap

  • Windows support. The picker uses termios/tty (POSIX only); an msvcrt-based key reader would let the menus run natively on Windows.
  • Passphrase-backend ergonomics — smoother first-run setup for the encrypted-file keyring backend.

License

Apache-2.0 © 2026 Full Duplex Media

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brolly-0.3.0.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brolly-0.3.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

File details

Details for the file brolly-0.3.0.tar.gz.

File metadata

  • Download URL: brolly-0.3.0.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for brolly-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ca91b3eb3f55fc179d1e62465ffdf2527952f90366fac0d01d95713dc99293ae
MD5 fe627a367dba8e4071bb32137ed8d205
BLAKE2b-256 17a4d52f43e9d3fe3454764f34b648cb6d2761c789daa4f3499087cf01e3b438

See more details on using hashes here.

Provenance

The following attestation bundles were made for brolly-0.3.0.tar.gz:

Publisher: release.yml on fduplex/brolly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file brolly-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: brolly-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 28.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for brolly-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2a3f0b70018b7e5acf010376db6c34aa43bbf777c914c556b54a8849b8a6ff8e
MD5 a277ad3c691f3639bc2125045d5e8c1b
BLAKE2b-256 69b2ef2c2d2e15fc0411058cfdddc4d788073220675170dcbdcb1955a3d90a22

See more details on using hashes here.

Provenance

The following attestation bundles were made for brolly-0.3.0-py3-none-any.whl:

Publisher: release.yml on fduplex/brolly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page