paperclip: per-agent kubectl ServiceAccounts + apiserver egress CNP (REN-414) #1080

Merged
Ghost merged 1 commits from atlas/ren-414-agent-kubectl-sa into master 2026-09-30 23:19:33 +00:00
3 changed files with 252 additions and 0 deletions
@@ -0,0 +1,214 @@
---
# Per-agent kubectl identities for agent pods (REN-412, parent REN-284).
#
# Each agent gets its own ServiceAccount instead of the shared pod token of
# `paperclip-prometheus-read`. Atlas, Iris, Hestia and Argus get exactly what
# they hold today through that shared account: built-in `view` cluster-wide
# (no Secrets, no write verb) plus the `monitoring/paperclip-prometheus-read`
# Role (see prometheus-read-rbac.yaml). Hermes gets only `get` on the five
# token Secrets below, to issue and rotate the kubeconfigs.
#
# Token Secrets carry no data in git; kube-controller-manager fills
# `data.token`. Flux applies server-side and does not own `data`, so it does
# not wipe the token. Revoke one agent: delete its Secret (the token dies at
# once; Flux recreates it with a fresh token on the next reconcile).
#
# The shared account may read the tokens too (Hermes still runs in the shared
# pod). Each token is worth at most what the shared account already holds, so
# that is no escalation. Drop that subject once Hermes runs in its own pod.
#
# PRODUCTION
#
# ServiceAccounts, bindings and empty token Secrets only. No workload changes,
# nothing restarts. Revert: delete this file and its line in kustomization.yaml.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-atlas
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-iris
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-hestia
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-argus
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-hermes
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
automountServiceAccountToken: false
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: agent-atlas-token
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
annotations:
kubernetes.io/service-account.name: agent-atlas
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: agent-iris-token
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
annotations:
kubernetes.io/service-account.name: agent-iris
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: agent-hestia-token
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
annotations:
kubernetes.io/service-account.name: agent-hestia
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: agent-argus-token
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
annotations:
kubernetes.io/service-account.name: agent-argus
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: agent-hermes-token
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
annotations:
kubernetes.io/service-account.name: agent-hermes
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: paperclip-agents-view
labels:
app.kubernetes.io/name: paperclip
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: agent-atlas
namespace: paperclip
- kind: ServiceAccount
name: agent-iris
namespace: paperclip
- kind: ServiceAccount
name: agent-hestia
namespace: paperclip
- kind: ServiceAccount
name: agent-argus
namespace: paperclip
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: paperclip-agents-prometheus-read
namespace: monitoring
labels:
app.kubernetes.io/name: paperclip
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
# Existing Role in prometheus-read-rbac.yaml, unchanged.
name: paperclip-prometheus-read
# Subjects live in `paperclip`, the binding in `monitoring`: `namespace:` is
# load-bearing.
subjects:
- kind: ServiceAccount
name: agent-atlas
namespace: paperclip
- kind: ServiceAccount
name: agent-iris
namespace: paperclip
- kind: ServiceAccount
name: agent-hestia
namespace: paperclip
- kind: ServiceAccount
name: agent-argus
namespace: paperclip
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: agent-token-read
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames:
- agent-atlas-token
- agent-iris-token
- agent-hestia-token
- agent-argus-token
- agent-hermes-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-token-read
namespace: paperclip
labels:
app.kubernetes.io/name: paperclip
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: agent-token-read
subjects:
# Hermes still runs in the shared pod; drop this subject once Hermes runs in
# its own pod.
- kind: ServiceAccount
name: paperclip-prometheus-read
namespace: paperclip
- kind: ServiceAccount
name: agent-hermes
namespace: paperclip
@@ -10,6 +10,7 @@ resources:
- prometheus-read-rbac.yaml
- cluster-view-rbac.yaml
- sandbox-rbac.yaml
- agent-kubectl-rbac.yaml
- paperclip.yaml
- gate-service.yaml
- ingress.yaml
@@ -31,3 +31,40 @@ spec:
- ports:
- port: "8443"
protocol: TCP
---
# Agent pods reach the Kubernetes API server with their own kubeconfig
# (REN-412, parent REN-284; RBAC in apps/paperclip/agent-kubectl-rbac.yaml).
#
# A `toCIDRSet` rule cannot open this path: `policy-cidr-match-mode` is unset
# in cilium-config, so CIDR rules never match node IPs, and the API server
# (10.2.46.11-13:6443, Service 10.96.0.1:443 translated by KPR) runs on the
# nodes. The `kube-apiserver` entity matches it. Port is the post-DNAT 6443.
#
# Selects by agent id, so only the five agents with a kubectl identity get
# the path; other agent pods stay closed. Not labelled
# `paperclip.io/managed-by`, so the plugin does not own or delete it.
#
# Revert: delete this document.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: paperclip-agents-kube-apiserver
namespace: paperclip-renner
spec:
endpointSelector:
matchExpressions:
- key: paperclip.io/agent-id
operator: In
values:
- 27d813a4-ae75-4913-b25b-3516a893d614 # Atlas
- 6eb4d648-83b4-40cb-b16e-d05d3ef372cb # Iris
- 32b3c62a-a5dc-4499-b970-25f5c7f60e8c # Hestia
- b023e112-1466-46f1-9fc1-e87e5cd3f005 # Argus
- b1be203c-d260-4922-8b48-ab9e87e73fb0 # Hermes
egress:
- toEntities:
- kube-apiserver
toPorts:
- ports:
- port: "6443"
protocol: TCP