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.
77 lines
3.2 KiB
YAML
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
|