ADR-039: Per-Team Developer RBAC
Date: 2026-05-29
Status: Accepted
Amendment (2026-06-03, #174 / ADR-046 + ADR-048): The RBAC model is unchanged (group-mapped:
DeveloperAccess-<team>→ EKS access entry → groupteam-<team>:developers→ namespace-scoped RoleBinding). What changed is who provisions it: for migrated teams theDeveloperAccess-<team>IAM role, the EKS access entry →team-<team>:developers, and the namespace RoleBinding are now rendered per team by the Crossplane Tenant Composition, not by the Terragruntiam-roles/eksper-team loops or thetenantmodule (all removed/retired). Read theiam-roles/eks/tenantreferences below as the historical mechanism.
Amendment (2026-06-27, accuracy review) — corrects the 2026-06-03 note above: (1) Only the in-cluster RoleBinding is emitted today. The v3 (
XEnvironment) Composition renders the namespace RoleBinding but not theDeveloperAccess-<team>IAM role or the EKS access entry — that half is a known regression, #647. So the end-to-end AWS→kubectl chain is not wired; operators useplatctl kubeconfig/ PlatformAdmin until it is. (2) Names changed under ADR-067. The live binding is group<environment-namespace>:developers(e.g.alpha-demo-dev:developers) → ClusterRoleenvironment-developer, notteam-<team>:developers→tenant-developer. (3) Vocabulary/source: “Crossplane Tenant Composition” is now theXEnvironmentComposition;teams.hcland thetenantmodule are retired in favour of the git-native registries (ADR-063/067). Read the body’stenant-developer/team-<team>:developers/teams.hclreferences through these corrections.
Context
Section titled “Context”ADR-007 introduced a DeveloperAccess IAM role for “namespace-scoped kubectl.” In practice the
preprod implementation had a single shared role that:
- Was granted
AmazonEKSEditPolicyscoped to the union of every team’s namespace (so a developer authorized into one team’s namespace was authorized into all of them), and - Was assumable by any SSO PowerUser/Admin, with no developer→team partitioning.
Separately, the Developers SSO group was assigned PowerUserAccess on the preprod account,
granting full AWS data-plane access (read and write to every team’s Secrets Manager, EKS, EC2,
etc.) — which bypasses Kubernetes namespace isolation entirely via the AWS API.
Net effect: there was no real isolation of one team from another, at either the Kubernetes RBAC layer or the AWS layer. As the platform onboards multiple teams (ADR-027, ADR-031), we need developer access that is genuinely scoped to a team’s own namespace and AWS footprint.
Alternatives Considered
Section titled “Alternatives Considered”1. Per-team access entries with an AWS-managed policy (namespace scope). Keep using
AmazonEKSEditPolicy but create one access entry per team scoped to that team’s namespace. Minimal
effort (no RBAC to author), but the authorization lives in the EKS control plane (not inspectable as
in-cluster RBAC), and the managed policy is all-or-nothing — it cannot be tightened (e.g. to exclude
Secrets) and does not generalize to non-EKS clusters or vClusters.
2. Group-mapped access entries + cluster-managed RBAC (chosen — “Bridge B”). Map each team’s IAM
principal to a Kubernetes group via an EKS access entry, and bind that group to a namespace-scoped
RoleBinding we own. The RBAC is real, inspectable, GitOps-versioned, customizable per compliance
tier, and the group→RoleBinding model is portable to any Kubernetes (including future vClusters).
Costs a small eks module change and a per-namespace RoleBinding.
Decision
Section titled “Decision”Adopt per-team developer isolation built from teams.hcl as the single source of truth:
- One IAM role per team —
DeveloperAccess-<team>(preprod), generated in theiam-rolesunit. Its trust condition restricts assumption to that team’s own SSO permission set (Dev-<team>), so a developer can assume only their own team’s role. - One SSO permission set + group per team —
Dev-<team>/Developers-<team>(mgmt identity-center unit), assigned on the preprod account. The broadDevelopers → PowerUserAccessassignment is removed. - One EKS access entry per team — maps
DeveloperAccess-<team>to the Kubernetes groupteam-<team>:developers(no AWS-managed access policy). Requires theeksmodule to support group-mapped entries (optionalpolicy_arn, newkubernetes_groups). - One RoleBinding per namespace — the
tenantmodule binds groupteam-<team>:developersto atenant-developerClusterRole in the team’s namespace.
Identity flow
Section titled “Identity flow”SSO group Developers-<team> -> Dev-<team> permission set on Preprod (identity-center, mgmt) -> AWSReservedSSO_Dev-<team>_* session role -> assumes DeveloperAccess-<team> (iam-roles, preprod; trust restricts to that set) -> EKS access entry maps it to group team-<team>:developers (eks unit, preprod) -> RoleBinding binds that group -> ClusterRole tenant-developer in ns team-<team> (tenant module) -> authorized to edit only in team-<team>tenant-developer ClusterRole
Section titled “tenant-developer ClusterRole”Initialized as an aggregate of the built-in edit role (via the aggregate-to-edit label) under a
name we control. Developers therefore can read/write Secrets in their own namespace today; the
named ClusterRole is the single place to tighten this (e.g. exclude Secrets) for a future compliance
tier without touching every RoleBinding. (Note: RBAC alone cannot fully hide Secrets — a developer
who can exec/run pods can read any Secret a pod mounts.)
AWS access posture
Section titled “AWS access posture”- Preprod: the
Dev-<team>permission set grants account-wideReadOnlyAccess(broad metadata read;ReadOnlyAccessexcludes value retrieval likesecretsmanager:GetSecretValue) plussts:AssumeRoleinto the team’s role, plus a per-team ABACDeny(#62) that blocks acting on any resource tagged with another team (aws:ResourceTag/Team∉ {own team,platform}) — so a dev can’t pull another team’s ECR images, etc. Backed by theTeamtag (#61) and a tag-integrity SCP that prevents forging the tag. AWS ABAC only covers actions that supportaws:ResourceTag, so broadDescribe/Listmetadata read remains account-wide. No data-plane write. - Prod (not yet deployed): same deny-other-teams model; whether to drop account-wide read entirely is bounded by the same ABAC limits — tracked as a prod-posture follow-up.
Consequences
Section titled “Consequences”Positive:
- A developer can edit only their own team’s namespace, and can assume only their own team’s role.
- Developers have no AWS data-plane write and no cross-team write; preprod read is read-only.
- RBAC is inspectable (
kubectl get rolebinding -n team-<team>) and versioned in Git. - The model is generated from
teams.hcl, so onboarding a team is largely a one-line change there (plus the manual SSO permission-set/group entries in the mgmt identity-center unit).
Negative:
- More IAM roles, SSO permission sets, and access entries to manage (one set per team).
- The mgmt identity-center entries are hand-maintained and must track
teams.hcl. - The Kubernetes group-name convention (
team-<team>:developers) is duplicated between theeksunit and thetenantmodule and must stay in sync.
Related hardening (so the RBAC cannot be bypassed): Pod Security Admission on tenant namespaces (prevents privileged/hostPath pods escaping to the node) and IMDS hardening (prevents pods from stealing node-role credentials). See ADR-027 and ADR-014.