Repository navigation
RBAC: ClusterRole grants cluster-wide write access to Services/ConfigMaps/Deployments/PDBs across every namespace #7
Description
Summary
deploy/rbac.yaml grants the operator's ClusterRole cluster-scoped
create/update/patch/delete on Services, ConfigMaps, Deployments,
and PodDisruptionBudgets across every namespace in the cluster (bound
via a matching ClusterRoleBinding, not a per-namespace Role). This is a
wide blast radius for a controller whose actual job is to manage resources it
owns in namespaces where a Gateway/HTTPRoute happens to live, and it
isn't documented anywhere as an accepted trade-off.
To be clear up front: this isn't as bad as it could be. Secrets and
Endpoints are already correctly scoped to read-only (get/list/watch)
with an explanatory comment ("Secrets (TLS material) and Endpoints are only
ever read."), and the PodDisruptionBudget write access has its own
rationale comment too. The gap is narrower than a first glance suggests: it's
the cluster scope of the remaining write verbs on Services/ConfigMaps/
Deployments/PDBs, not raw Secret access.
Evidence
deploy/rbac.yaml:12-80 at upstream/main (5bda5841a64d5d6a6d46062f0c0169e20c7b7b59):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: praxis-operator
rules:
...
# The operator creates and owns child Services and ConfigMaps.
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Secrets (TLS material) and Endpoints are only ever read.
- apiGroups: [""]
resources: ["secrets", "endpoints"]
verbs: ["get", "list", "watch"]
...
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# PodDisruptionBudgets keep a data-plane pod serving through a drain.
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
...
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: praxis-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: praxis-operatorThere's no per-namespace Role/RoleBinding variant offered, and nothing in
docs/configuration.md or docs/architecture.md explains why cluster scope
(vs. e.g. a Role applied to each namespace containing a managed Gateway)
was chosen.
Risk
A bug in reconciliation logic, or a compromised operator pod, could
create/update/delete a Service, ConfigMap, Deployment, or
PodDisruptionBudget in any namespace in the cluster, not just ones with a
Praxis Gateway. For a generic, cross-namespace Gateway API controller this
may well be a reasonable and even necessary trade-off (the operator can't
know in advance which namespaces will host a Gateway), but that's a
judgment call that should be recorded, not left implicit.
Suggested fix
Pick one and document the decision in docs/configuration.md:
- Accept as-is, with a short paragraph explaining why cluster scope is
necessary for a Gateway API controller that must react toGateway/
HTTPRouteobjects in namespaces it doesn't control in advance. - Narrow it, e.g. by reconciling a
Role/RoleBindingper namespace that
actually contains a managedGateway(mirroring how the child resources
themselves are already namespace-scoped).
Either way, a one-line rationale comment (matching the style already used for
the Secrets/Endpoints and PDB rules) on the Services/ConfigMaps/Deployments
block would close the documentation gap cheaply.
Severity: Medium
Activity