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-operator
There'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 to Gateway/
HTTPRoute objects in namespaces it doesn't control in advance.
- Narrow it, e.g. by reconciling a
Role/RoleBinding per namespace that
actually contains a managed Gateway (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
Summary
deploy/rbac.yamlgrants the operator'sClusterRolecluster-scopedcreate/update/patch/deleteonServices,ConfigMaps,Deployments,and
PodDisruptionBudgetsacross every namespace in the cluster (boundvia a matching
ClusterRoleBinding, not a per-namespaceRole). This is awide blast radius for a controller whose actual job is to manage resources it
owns in namespaces where a
Gateway/HTTPRoutehappens to live, and itisn't documented anywhere as an accepted trade-off.
To be clear up front: this isn't as bad as it could be.
SecretsandEndpointsare already correctly scoped to read-only (get/list/watch)with an explanatory comment ("Secrets (TLS material) and Endpoints are only
ever read."), and the
PodDisruptionBudgetwrite access has its ownrationale 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-80atupstream/main(5bda5841a64d5d6a6d46062f0c0169e20c7b7b59):There's no per-namespace
Role/RoleBindingvariant offered, and nothing indocs/configuration.mdordocs/architecture.mdexplains why cluster scope(vs. e.g. a
Roleapplied to each namespace containing a managedGateway)was chosen.
Risk
A bug in reconciliation logic, or a compromised operator pod, could
create/update/delete a
Service,ConfigMap,Deployment, orPodDisruptionBudgetin any namespace in the cluster, not just ones with aPraxis
Gateway. For a generic, cross-namespace Gateway API controller thismay 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 ajudgment call that should be recorded, not left implicit.
Suggested fix
Pick one and document the decision in
docs/configuration.md:necessary for a Gateway API controller that must react to
Gateway/HTTPRouteobjects in namespaces it doesn't control in advance.Role/RoleBindingper namespace thatactually contains a managed
Gateway(mirroring how the child resourcesthemselves 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