Skip to content

fix(auth): stop treating any-team admins as platform admins - #97

Open
atbagan wants to merge 1 commit into
mainfrom
fix/any-team-admin-bypass
Open

atbagan wants to merge 1 commit into
mainfrom
fix/any-team-admin-bypass

Conversation

@atbagan

@atbagan atbagan commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Problem

With no X-Butler-Team header, ClusterHandler.checkClusterAccess / checkOperatePermission, AddonsHandler.checkOperatePermission, GitOpsHandler.checkOperatePermission and the certificate checks returned early on UserSession.IsAdmin(), which means "admin of any team". An admin of team alpha could read, scale, delete, enable GitOps on, and rotate certificates for every other team's clusters by omitting the header. The portal proxy sends the header only when a team is selected, so this is its default path. POST /teams/{name}/providers/test reused the platform TestConnection handler with no team check. Found in the 2026-08 parity audit (D17, D8).

Change

  • Visibility (cluster read, list filter, certificate read): platform admin or platform viewer sees all; otherwise team membership.
  • Operate (cluster mutations, addons, GitOps, certificate rotation): platform admin; otherwise CanOperateTeam(teamRef) (or IsAdminOfTeam for CA rotation, unchanged).
  • ProvidersHandler.TestTeamConnection wraps TestConnection with CanOperateTeam(name); the router uses it for the team route.
  • Selected-team paths are unchanged.

Verification

  • CGO_ENABLED=0 go build ./..., go vet ./internal/api/... clean
  • CGO_ENABLED=0 go test ./... passes; clusters_operate_test.go asserts an alpha team admin is confined to alpha across all five check sites and that platform viewer reads but cannot operate.
  • Console: the admin pages run as platform admin; team pages send X-Butler-Team, which takes the unchanged selected-team path.

Depends on

None. Complements #93 (middleware tier) and #96 (management reads).

Out of scope

IsAdmin() call sites in images.go and workspaces.go (image syncs and workspace admin visibility), which need their own scoping decision.

When a request carries no X-Butler-Team header, the cluster, addon,
GitOps and certificate checks short-circuited on UserSession.IsAdmin,
which is true for an admin of any team. An admin of team alpha could
therefore read, scale, delete, enable GitOps on and rotate
certificates for every other team's clusters by omitting the header.
This is the path the portal proxy uses when no team is selected.

Platform roles are now checked explicitly: platform admins and
viewers see every cluster, platform admins operate on any cluster,
and everyone else is limited to teams where they hold the needed
role. POST /teams/{name}/providers/test gets its own handler that
requires an operate role on the named team; it previously reused the
platform handler with no team check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant