Summary
leverage reconstructs the AWS profile name it writes credentials into, instead of using the profile reference it already resolved. The reconstruction is f"{project}-{account_name}-{sso_role.lower()}", which silently produces the wrong name whenever an SSO permission set is not named after the profile's suffix — and the same helper mis-parses stale <project>-sso-* sections into junk profiles.
Found on 3.1.0 while adopting the new aws sso refresh (#315). The refresh command itself works well; these are pre-existing derivation bugs that the new command makes visible (and, for bug 2, amplifies).
Bug 1 — the profile name is coupled to the permission set name
get_layer_profile (leverage/modules/auth.py) resolves a .tf profile reference into raw, then throws raw away and rebuilds the name from account + role:
layer_profile = tf_profile if raw_profile in ("var.profile", "each.value.profile") else None
...
layer_profile = layer_profile or f"{project}-{account_name}-{sso_role.lower()}"
raw is the profile name the .tf will use. Rebuilding it assumes the permission set is named after the profile suffix.
We renamed one account's permission set from DevOps to DevOpsPrd (production gating). Nothing else changed. Immediately:
$ leverage tofu init # from an apps-prd layer
INFO Attempting to get temporary credentials for apps-prd account.
INFO Using already configured temporary credentials. # it checked bb-apps-prd-devopsprd
Error: validating provider credentials: ... ExpiredToken # tofu used bb-apps-prd-devops
backend.tfvars says profile = "bb-apps-prd-devops", so that is the profile tofu authenticates with — but leverage can only ever mint bb-apps-prd-devopsprd. The account becomes unusable and there is no way to get the right profile out of the CLI. Cross-account references written as literals (profile = "${var.project}-apps-prd-devops") hit the same wall.
Suggested fix: use the resolved reference. layer_profile should just be raw in _get_sso_profile; the account+role derivation is only needed by refresh_all_accounts_credentials, where there is no .tf reference to resolve.
Related: the tf_profile short-circuit is dead code
key_finder returns the raw HCL string, which for profile = var.profile is "${var.profile}" — never equal to the bare "var.profile" the ternary compares against:
>>> from leverage._utils import key_finder, parse_tf_file
>>> key_finder(parse_tf_file(Path("apps-prd/us-east-1/security-base/config.tf")), "profile", "lookup")
['${var.profile}']
So the tf_profile branch never fires and the derivation always wins, even for the layer's own profile. Same for each.value.profile.
Bug 2 — stale <project>-sso-* sections become junk profiles
_get_sso_profile_from_section treats everything after the prefix as the account name:
account_name = profile_section.replace(f"profile {project}-sso-", "")
layer_profile = f"{project}-{account_name}-{sso_role.lower()}"
Older leverage versions wrote sections named <project>-sso-<account>-<Role>, and configure_sso_profiles never removes them. Our config still had six. aws sso refresh read bb-sso-network-DevOps as account "network-DevOps" and wrote a profile literally named bb-network-DevOps-devops:
INFO Found 16 account(s) to refresh. # 10 real accounts + 6 stale sections
INFO Writing bb-network-DevOps-devops profile
INFO Writing bb-shared-DevOps-devops profile
INFO Writing bb-root-Administrator-administrator profile
...
INFO Credential refresh complete: 11 refreshed, 3 skipped, 2 failed.
Five junk profiles written into ~/.aws/<project>/{config,credentials}, five wasted get-role-credentials calls, and inflated counts in the summary. After deleting the stale sections by hand: Found 10 account(s) → 10 refreshed, 0 skipped, 0 failed.
Validating the suffix against the known account list (or having configure_sso_profiles prune sections it did not just write) would avoid this.
Bug 3 — refresh help is not accepted
The PR description for #315 documents both syntaxes. Only --help works:
$ leverage aws sso refresh help
Error: Got unexpected extra argument (help)
Also, leverage aws sso --help and bare leverage aws sso fall through to the real aws CLI, so there is no way to discover refresh from the SSO subcommand's own help.
Credit where due
The error handling in #315 is a genuine improvement — raise_on_permission_error=False plus the success/failure summary is what surfaced bug 1 at all. The layer-scoped refresh_layer_credentials still hard-exits on the first account whose permission set you lack, writing nothing; that path would benefit from the same treatment.
Environment
Summary
leveragereconstructs the AWS profile name it writes credentials into, instead of using the profile reference it already resolved. The reconstruction isf"{project}-{account_name}-{sso_role.lower()}", which silently produces the wrong name whenever an SSO permission set is not named after the profile's suffix — and the same helper mis-parses stale<project>-sso-*sections into junk profiles.Found on 3.1.0 while adopting the new
aws sso refresh(#315). The refresh command itself works well; these are pre-existing derivation bugs that the new command makes visible (and, for bug 2, amplifies).Bug 1 — the profile name is coupled to the permission set name
get_layer_profile(leverage/modules/auth.py) resolves a.tfprofile reference intoraw, then throwsrawaway and rebuilds the name from account + role:rawis the profile name the.tfwill use. Rebuilding it assumes the permission set is named after the profile suffix.We renamed one account's permission set from
DevOpstoDevOpsPrd(production gating). Nothing else changed. Immediately:backend.tfvarssaysprofile = "bb-apps-prd-devops", so that is the profile tofu authenticates with — but leverage can only ever mintbb-apps-prd-devopsprd. The account becomes unusable and there is no way to get the right profile out of the CLI. Cross-account references written as literals (profile = "${var.project}-apps-prd-devops") hit the same wall.Suggested fix: use the resolved reference.
layer_profileshould just berawin_get_sso_profile; the account+role derivation is only needed byrefresh_all_accounts_credentials, where there is no.tfreference to resolve.Related: the
tf_profileshort-circuit is dead codekey_finderreturns the raw HCL string, which forprofile = var.profileis"${var.profile}"— never equal to the bare"var.profile"the ternary compares against:So the
tf_profilebranch never fires and the derivation always wins, even for the layer's own profile. Same foreach.value.profile.Bug 2 — stale
<project>-sso-*sections become junk profiles_get_sso_profile_from_sectiontreats everything after the prefix as the account name:Older leverage versions wrote sections named
<project>-sso-<account>-<Role>, andconfigure_sso_profilesnever removes them. Our config still had six.aws sso refreshreadbb-sso-network-DevOpsas account"network-DevOps"and wrote a profile literally namedbb-network-DevOps-devops:Five junk profiles written into
~/.aws/<project>/{config,credentials}, five wastedget-role-credentialscalls, and inflated counts in the summary. After deleting the stale sections by hand:Found 10 account(s)→10 refreshed, 0 skipped, 0 failed.Validating the suffix against the known account list (or having
configure_sso_profilesprune sections it did not just write) would avoid this.Bug 3 —
refresh helpis not acceptedThe PR description for #315 documents both syntaxes. Only
--helpworks:Also,
leverage aws sso --helpand bareleverage aws ssofall through to the realawsCLI, so there is no way to discoverrefreshfrom the SSO subcommand's own help.Credit where due
The error handling in #315 is a genuine improvement —
raise_on_permission_error=Falseplus the success/failure summary is what surfaced bug 1 at all. The layer-scopedrefresh_layer_credentialsstill hard-exits on the first account whose permission set you lack, writing nothing; that path would benefit from the same treatment.Environment