Files
homelab/kubernetes/apps/paperclip/cluster-view-rbac.yaml
T
atlas-botandClaude Opus 5.5 21a7583523
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
ci: cite YAML by anchor instead of line number, and guard it (REN-183)
Comments that pointed at other YAML as `file.yaml:N` went stale on any
edit above the cited line; three drift fixes had been merged by hand
(REN-181). The cited block now carries `# anchor: <name>` and the
comment cites `path.yaml#<name>`.

- Sweep: all 32 line-number citations in YAML comments converted.
  Several were already stale at head and now point at the right block
  (see PR). Vendored files (Flux gotk-components, IMC CRD) get no
  anchor; those two citations name the object/schema path instead.
- Guard: scripts/ci/yaml-citations.sh in ci-manifests-test.yml; fails
  on a line-number citation or a path#name whose file/anchor is gone.
  Self-test in ci-scripts-test.yml.
- CONTRIBUTING.md: the convention in three lines.

Comment-only under kubernetes/: every file parses to identical objects
before and after, so Flux applies nothing and no ConfigMap data changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 08:44:54 +00:00

99 lines
4.8 KiB
YAML

---
# Cluster-wide READ for the agents in the paperclip pod (REN-111, decided on
# REN-52 2026-09-25 by the owner, option B).
#
# WHAT THIS IS
#
# One ClusterRoleBinding. It binds the Kubernetes built-in `view` ClusterRole to
# the ServiceAccount that REN-36 created in `../paperclip/prometheus-read-rbac.yaml`.
# No new ServiceAccount, no new ClusterRole, no write verb anywhere. `view` is
# maintained upstream and holds get/list/watch only; revocation is deleting this
# one object.
#
# WHY IT IS A SEPARATE FILE
#
# `prometheus-read-rbac.yaml` is deliberately narrow and its header argues for
# staying that way. This grant is the opposite in character — cluster-wide,
# every namespace — so it is kept in its own file and its own kustomization
# entry. Reverting it is deleting this file and one line from
# `kustomization.yaml`; nothing in the REN-36 path is touched, and the token
# falls back to `get services/proxy` on two named Services.
#
# WHY THE OWNER WANTED IT
#
# REN-108 part B had to validate a CiliumNetworkPolicy allowlist whose selector
# was `app.kubernetes.io/name: prometheus-blackbox-exporter`, and could not:
# kube-state-metrics here does not export `kube_pod_labels`, and
# blackbox-exporter is not a scrape target so Prometheus has no
# `__meta_kubernetes_pod_label_*` for it either. A CNP selector that matches no
# pod allows nothing and reports no error, so the proposal shipped unverified.
# `list pods` closes that, and the same gap recurs on every netpol or
# scrape-selector change.
#
# WHAT IT COSTS — ACCEPTED ON REN-52, NOT DISCOVERED HERE
#
# * EVERY process in the paperclip pod can read the mounted token, so this is
# a grant to every agent, not to one. REN-36 was approved on the premise
# that "the token is only worth what this Role grants"; this file changes
# that arithmetic, and the owner priced it knowingly.
# * `view` reads ConfigMaps in EVERY namespace. This repo keeps application
# configuration in ConfigMaps. Chosen over Hermes' narrower alternative.
# * `view` grants `pods/log`, which surfaces anything an application prints,
# including credentials it prints by mistake.
#
# WHAT IT DOES NOT GRANT
#
# * Secrets. `view` excludes them. "Secrets, metadata only" was the original
# ask and is NOT expressible in Kubernetes RBAC — the authorizer has no
# field-level verb — so that part of the ask is dead. Do not re-add Secrets
# here to approximate it.
# * Any write verb, on anything.
# * ClusterRoles and RoleBindings: `view` excludes the whole
# `rbac.authorization.k8s.io` group, on purpose, so a holder cannot read the
# shape of its own cage or find a Role to escalate through.
#
# FLUX CRDs — COVERED BY AGGREGATION, NO EXTRA ClusterRole NEEDED
#
# REN-52 pre-approved adding a read-only ClusterRole for the Flux CRDs *if*
# aggregation did not already cover them. It does, so none is added:
# `flux-view-flux-system` carries `rbac.authorization.k8s.io/aggregate-to-view:
# "true"` and lists get/list/watch on `*` across all six API groups —
# the `flux-view-flux-system` ClusterRole in
# `../../clusters/homelab/flux-system/gotk-components.yaml`. The
# aggregation controller merges those rules into `view` itself, so this one
# binding picks up kustomizations, helmreleases, gitrepositories, imagepolicies,
# imagerepositories and imageupdateautomations. Read from git, not from a live
# ClusterRole — `view` cannot read ClusterRoles, so it is unverifiable from
# inside the pod. The post-merge SelfSubjectAccessReview on REN-111 proves
# whether aggregation actually fired in the cluster; if `list kustomizations`
# comes back `allowed: false` there, the pre-approved ClusterRole is the fix.
#
# PRODUCTION
#
# Adding a ClusterRoleBinding mutates no workload, restarts no Pod and moves no
# traffic. RBAC is evaluated per request, so the grant takes effect on the
# already-mounted token with no remount and no rollout. That sequencing was the
# point of REN-52 putting this after REN-36: the paperclip Deployment restart
# was paid once, by REN-36, and is already paid.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: paperclip-cluster-view
labels:
app.kubernetes.io/name: paperclip
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
# Built-in, shipped by kube-controller-manager's default RBAC bootstrap. Not
# defined in this repo and must not be: it is auto-reconciled, so a local copy
# would be overwritten on every apiserver start.
name: view
subjects:
- kind: ServiceAccount
name: paperclip-prometheus-read
# A ClusterRoleBinding has no namespace of its own, so this is the only
# place the subject's namespace can be stated. Omit it and the binding
# resolves to no subject and fails closed, looking identical to a missing
# grant.
namespace: paperclip