Discovered while implementing the group-permission grant ceiling in #1737 (fixes #1550). Deliberately left out of that PR to keep it proportionate.
What
PR #1737 added a grant ceiling to CreateGroupAPI / UpdateGroupAPI (internal/auth/service_api.go): a caller may only write permissions their own effective set holds, and the money verbs carved out of admin:* are not grantable at all.
The same request body also carries AllowedAccounts, and that field has no ceiling. applyUpdateGroupRequest writes it verbatim:
if req.AllowedAccounts != nil {
group.AllowedAccounts = req.AllowedAccounts
}
A caller whose own groups restrict them to accounts A and B can therefore write allowed_accounts: ["*"] (or any account UUID they cannot see) onto a group and gain visibility into every registered cloud account. IsUnrestrictedAccess treats both an empty list and a "*" entry as unrestricted, so [] works as well as ["*"].
Blast radius
Data visibility, not money: AllowedAccounts feeds CanAccessAccount / GetAllowedAccountsAPI, which scope which cloud accounts a user sees. It does not grant a verb, so it cannot by itself reach a purchase. That is why it was scoped out of #1737 rather than folded in.
Note it is only reachable by a caller who already holds create:groups or update:groups, i.e. an admin today.
Fix direction
Mirror the permission ceiling: a group's AllowedAccounts may not name an account outside the union of the acting principal's own groups' AllowedAccounts, with IsUnrestrictedAccess(actor) meaning "any value is in ceiling". Fail closed if the actor's allowed set cannot be resolved, and refuse loudly naming the offending account rather than silently intersecting the list (same rule #1629 establishes for permissions).
The hook already exists: checkGrantCeiling in internal/auth/group_ceiling.go is called from both write paths with the acting principal already threaded through, so this is an added check inside an existing seam, not new plumbing.
Related
Discovered while implementing the group-permission grant ceiling in #1737 (fixes #1550). Deliberately left out of that PR to keep it proportionate.
What
PR #1737 added a grant ceiling to
CreateGroupAPI/UpdateGroupAPI(internal/auth/service_api.go): a caller may only write permissions their own effective set holds, and the money verbs carved out ofadmin:*are not grantable at all.The same request body also carries
AllowedAccounts, and that field has no ceiling.applyUpdateGroupRequestwrites it verbatim:A caller whose own groups restrict them to accounts A and B can therefore write
allowed_accounts: ["*"](or any account UUID they cannot see) onto a group and gain visibility into every registered cloud account.IsUnrestrictedAccesstreats both an empty list and a"*"entry as unrestricted, so[]works as well as["*"].Blast radius
Data visibility, not money:
AllowedAccountsfeedsCanAccessAccount/GetAllowedAccountsAPI, which scope which cloud accounts a user sees. It does not grant a verb, so it cannot by itself reach a purchase. That is why it was scoped out of #1737 rather than folded in.Note it is only reachable by a caller who already holds
create:groupsorupdate:groups, i.e. an admin today.Fix direction
Mirror the permission ceiling: a group's
AllowedAccountsmay not name an account outside the union of the acting principal's own groups'AllowedAccounts, withIsUnrestrictedAccess(actor)meaning "any value is in ceiling". Fail closed if the actor's allowed set cannot be resolved, and refuse loudly naming the offending account rather than silently intersecting the list (same rule #1629 establishes for permissions).The hook already exists:
checkGrantCeilingininternal/auth/group_ceiling.gois called from both write paths with the acting principal already threaded through, so this is an added check inside an existing seam, not new plumbing.Related