security(agent-zero): remove cluster RBAC entirely + no token mount (operator directive — MCP only)

Operator: agent-zero must reach the cluster ONLY through gated MCP tools, not a
service account with cluster roles for raw kubectl. Removed the read-only
ClusterRole/ClusterRoleBinding entirely (SA now has zero cluster perms) and set
automountServiceAccountToken: false so no K8s API token is mounted at all.
Applied live (SA secrets/exec/pods/namespaces -> all Forbidden); this makes it
durable so ArgoCD selfHeal won't re-create any role.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andrew Stoltz
2026-06-17 11:36:31 -05:00
parent 191eb91642
commit a77fbd0381

View File

@@ -75,52 +75,12 @@ metadata:
name: agent-zero
namespace: agent-zero
---
# SEC-6 / audit RBAC-001: agent-zero is an LLM agent — cluster-admin let raw
# kubectl BYPASS the MCP layer to read every Secret / exec any pod. Scoped to
# read-only (no secrets/configmaps/exec/writes) so sensitive + mutating actions
# must go through the gated MCP tools (the operator's intended boundary).
# SEC-6 / audit RBAC-001 (operator directive 2026-06-17): agent-zero is an LLM
# agent and must reach the cluster ONLY through gated MCP tools — NOT raw kubectl.
# It therefore has NO ClusterRole/ClusterRoleBinding at all, and the pod sets
# automountServiceAccountToken: false (below) so no Kubernetes API token is even
# mounted. History: cluster-admin -> read-only -> (now) no cluster RBAC / no token.
# Detail: FlowerCore.Notes docs/security/sec-6-agent-zero-rbac-remediation.md.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: agent-zero-readonly
labels:
flowercore.io/sec-lane: SEC-6
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "endpoints", "events", "namespaces", "nodes", "persistentvolumeclaims", "replicationcontrollers", "serviceaccounts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["traefik.io", "traefik.containo.us"]
resources: ["ingressroutes", "middlewares"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: agent-zero-readonly
labels:
flowercore.io/sec-lane: SEC-6
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: agent-zero-readonly
subjects:
- kind: ServiceAccount
name: agent-zero
namespace: agent-zero
# =============================================================================
# Agent Zero — AI Agent Web UI (NUC Edition, Blue Jay Profile)
@@ -222,6 +182,9 @@ spec:
app: agent-zero
spec:
serviceAccountName: agent-zero
# SEC-6: no Kubernetes API token mounted — agent-zero reaches the cluster
# only via gated MCP tools, never raw kubectl. (RBAC is also removed above.)
automountServiceAccountToken: false
initContainers:
# Wait for fc-llm-bridge to be reachable before starting Agent Zero.
- name: wait-for-llm-bridge