Add toolshed deployer's cluster RBAC

Scoped to the resource kinds toolshed creates for an app — not
cluster-admin, not a wildcard. This is the one credential in the system
whose compromise means the cluster, so what it can do should fit on one
screen.

Here rather than in toolshed's chart because these are cluster-scoped and
that Application runs in the webapp project, which permits only Namespace.
Widening webapp would have given every demo app in it the ability to create
cluster-wide RBAC.
This commit is contained in:
Mukul Sharma
2026-09-04 22:42:27 +05:30
parent 0eeef50781
commit 6f37fa0fd3
@@ -0,0 +1,59 @@
# Cluster write access for toolshed's deployer, and only deployer.
#
# Lives here rather than in toolshed's own Helm chart because these are
# cluster-scoped, and toolshed's Application runs in the `webapp` project,
# whose clusterResourceWhitelist deliberately allows only Namespace. Widening
# that project to permit ClusterRole and ClusterRoleBinding would let *any*
# Application in it — every demo app — create cluster-wide RBAC, which is a
# privilege-escalation surface in the project that holds ordinary apps. The
# narrow fix is to put the two cluster-scoped objects where platform-level
# cluster resources already live.
#
# The ServiceAccount they bind to is namespaced, so it stays in toolshed's
# chart alongside the Deployment that uses it. If that ever moves, this
# binding's subject has to move with it.
#
# Scoped to the resource kinds toolshed creates for an app. Not cluster-admin
# and not a wildcard: this is the one credential in the system whose
# compromise means the cluster, so what it can do should fit on one screen.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: toolshed-deployer
labels:
app.kubernetes.io/part-of: toolshed
rules:
# Namespaces are cluster-scoped, and one is created per app.
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch", "create", "delete"]
# Everything an app needs inside its own namespace.
- apiGroups: [""]
resources: ["services", "resourcequotas"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Read-only. Pods are listed to report why a rollout failed, never changed.
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# The policy that stops one app reaching another.
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: toolshed-deployer
labels:
app.kubernetes.io/part-of: toolshed
subjects:
- kind: ServiceAccount
name: toolshed-deployer
namespace: toolshed
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: toolshed-deployer