Skip to content

sec(auth): group AllowedAccounts has no grant ceiling; a caller can widen a group past their own account scope #1738

Description

@cristim

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

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions