Skip to content

fix(auth): require platform admin for platform-level mutations - #93

Open
atbagan wants to merge 2 commits into
mainfrom
fix/platform-admin-write-gates
Open

atbagan wants to merge 2 commits into
mainfrom
fix/platform-admin-write-gates

Conversation

@atbagan

@atbagan atbagan commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Problem

auth.AdminMiddleware (internal/auth/middleware.go:431) is RequireAdmin, which passes any user who is an admin of any team (UserSession.IsAdmin, session.go:299). It gated the /api/admin write group, platform provider CRUD, management addon and GitOps mutations, and Git provider config (router.go:320,332,396,485,542). A team admin could create users, edit identity providers, network pools, policies, the platform config, and add or remove members of teams they do not administer. Found in the 2026-08 parity audit (H15, D5).

Change

  • Platform-level mutation groups now use auth.RequirePlatformAdmin() (previously defined, never wired).
  • Team member and group-sync routes stay behind the any-team-admin middleware, and the six handlers check IsAdminOfTeam(name) through authorizeTeamMembershipChange, so an admin of team A cannot edit team B.
  • GET /admin/config and GET /admin/audit move to the platform-viewer tier of the /admin group; AuditHandler.ListAll accepts platform viewer or above instead of platform admin only. GET /admin/observability/status stays admin-only because the handler writes ButlerConfig.
  • Comments on the affected groups say which tier applies.

Verification

  • CGO_ENABLED=0 go build ./..., go vet ./internal/api/... clean
  • CGO_ENABLED=0 go test ./... passes; teams_membership_authz_test.go covers nil session, platform admin, platform viewer, own-team admin, other-team admin, and mixed memberships.
  • Callers: console admin pages run as platform admin; the portal proxy session is the legacy platform admin, so nothing changes for it. The /admin route already required platform viewer or above before this change, so no currently working caller loses access.

Depends on

#92 (team create/update/delete gates). Independent files except the /admin/teams lines in router.go; merge either order.

Out of scope

Observed while here: the whole /api/admin route requires platform viewer or above, so a team admin without a platform role cannot reach /api/admin/teams/{t}/members at all, which is what the console TeamMembersPage calls. That is pre-existing and worth a product decision (team-scoped member management outside /admin). The UpdateMemberRole handler still allows promoting to admin without restriction; unchanged here.

AdminMiddleware passes any user who is an admin of any team. It gated
the /api/admin write group, platform provider CRUD, management addon
and GitOps mutations, and Git provider config. A team admin could
therefore create users, edit identity providers, network pools,
policies, the platform config and any other team's members.

Platform-level mutations now use RequirePlatformAdmin. Team member and
group-sync routes keep the any-team-admin middleware and the handlers
narrow it to the named team (authorizeTeamMembershipChange). GET
/api/admin/config and GET /api/admin/audit move to the platform-viewer
tier of the /admin group so read-only platform viewers can use them.
Review follow-up. GET /api/admin/config returns the audit and
notification webhook URLs, which are bearer-like secrets, so it stays
in the platform-admin group; only the audit log moves to the viewer
tier. AuditHandler.ListTeam now accepts platform viewers alongside
team admins, matching ListAll.
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