Files
devops-infra-argo-config-gcp/extra-manifests/toolshed-deployer-rbac.yaml
T
Mukul Sharma f23fc5f03c deployer: permission to manage app configuration Secrets
Environment variables are delivered through a Secret so values never appear
in the pod spec, but the ClusterRole was never given the resource — so every
deploy with configuration failed on a forbidden error, minutes after the
change looked fine.

Granted without list or watch. Kubernetes RBAC cannot scope a ClusterRole to
a namespace pattern, so this necessarily covers every namespace; withholding
list at least stops deployer enumerating the cluster's secrets, leaving only
access by a name it already knows. That narrows the blast radius rather than
removing it, and is called out in the manifest.

The proper fix, once there are tenants who are not the operator, is a
RoleBinding created per app namespace. That requires deployer to be able to
create RoleBindings, which is its own escalation path and wants deciding
deliberately rather than being slipped in here.
2026-09-05 01:49:49 +05:30

77 lines
3.2 KiB
YAML

# 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"]
# An app's configuration, delivered as a Secret so values never appear in
# the pod spec.
#
# Deliberately without list or watch. Kubernetes RBAC cannot scope a
# ClusterRole to a namespace pattern, so this necessarily covers every
# namespace — but without list, deployer cannot enumerate the cluster's
# secrets, only address ones by a name it already knows. That narrows the
# blast radius without removing it: get on a known name still reaches any
# secret in the cluster.
#
# The proper fix, when this has tenants who are not the operator, is a
# RoleBinding created per app namespace instead of one ClusterRole. That
# needs deployer to hold permission to create RoleBindings, which is its
# own escalation path and wants thinking about rather than adding here.
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "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