From 3f0201ed9746747cb8a1ef3ad6bca0a813dfb928 Mon Sep 17 00:00:00 2001 From: Konstantins Feofantovs Date: Sun, 30 Aug 2026 12:58:15 +0300 Subject: [PATCH] docs: the installation is cluster-wide, and there is no narrower mode (ADR 0055) `security.md` has offered a namespace-scoped installation since the first commit that created the file. No ADR decided it, nothing was built behind it, and ADR 0032 then twice asserted that the mode "remains available", giving speculative prose the standing of a decision. The description was also false in the direction that matters. It promised "requests-vs-usage findings for the permitted namespaces only"; the agent reads every kubelet through `get nodes/proxy`, which is cluster-scoped, so without a ClusterRole there is no usage collection at all. What the document called a mode of this product is a different and much weaker one. The section is replaced by a statement of the single installation shape and its reason. ADR 0032's body is immutable, so its two sentences stand as written and ADR 0055 is their forwarding address. No code, no chart, no RBAC change. go test ./... && make lint # green Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp --- docs/adr/0032-cluster-policy.md | 2 +- .../0055-the-installation-is-cluster-wide.md | 77 +++++++++++++++++++ docs/adr/README.md | 1 + docs/security.md | 30 ++++---- 4 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 docs/adr/0055-the-installation-is-cluster-wide.md diff --git a/docs/adr/0032-cluster-policy.md b/docs/adr/0032-cluster-policy.md index 89c57aa..64bc2bd 100644 --- a/docs/adr/0032-cluster-policy.md +++ b/docs/adr/0032-cluster-policy.md @@ -4,7 +4,7 @@ Date: 2026-08-24 Status: Accepted Amends: 0031 §7 -Amended by: 0033, 0048, 0051 +Amended by: 0033, 0048, 0051, 0055 Adds the payload kinds `workload_policy` and `cluster_policy`. Adds six resources to the ClusterRole — the first time the agent's read access has widened since it diff --git a/docs/adr/0055-the-installation-is-cluster-wide.md b/docs/adr/0055-the-installation-is-cluster-wide.md new file mode 100644 index 0000000..3b42542 --- /dev/null +++ b/docs/adr/0055-the-installation-is-cluster-wide.md @@ -0,0 +1,77 @@ +# 0055. The installation is cluster-wide, and there is no narrower mode + +Date: 2026-08-30 + +Status: Accepted + +Amends: 0032 + +Removes the namespace-scoped installation mode from `docs/security.md`. It was +never built, never decided, and the document's description of what it would +deliver was wrong. No code changes. + +## Context + +`docs/security.md` has offered a "namespace-scoped **[planned]**" installation — +Role and RoleBinding in listed namespaces, no ClusterRole — since the first +commit that created the document. No ADR ever decided it. Nothing was ever built +behind it. [ADR 0032](0032-cluster-policy.md) then twice asserted that the mode +"remains available", which took a sentence of speculative prose and gave it the +standing of a decision. + +The description was also false, in the direction that matters. It said the mode +"reports requests-vs-usage findings for the permitted namespaces only". The agent +reads every kubelet through `get nodes/proxy`, which is cluster-scoped. Without a +ClusterRole there is no `nodes/proxy`, so there is no usage collection at +all — not reduced, absent. `nodes`, `namespaces`, `priorityclasses` and +`storageclasses` are cluster-scoped for the same reason. + +So what the document described as a mode of this product is a different and much +weaker product, offered to a reader who would discover the difference only after +installing it. + +## Decision + +**1. There is one installation shape: cluster-wide.** The section is replaced by +a statement of that, with the reason — half of what the agent reads is +cluster-scoped — rather than by a narrower promise. + +**2. ADR 0032's two references to the mode are void, and this is their +forwarding address.** An accepted ADR's body is immutable +([`README.md`](README.md)), so the sentences there stand as written and are +answered here: the mode they call "available" does not exist and will not. + +**3. The scoping mechanism is the four controls, and that is a real answer, not +a fallback.** Namespace filters and the three opt-out annotations apply at the +collection stage, and since [ADR 0054](0054-coverage-says-how-much-was-hidden-never-what.md) +their effect is checkable from what the backend receives rather than only from +what was configured. + +The argument for API-server enforcement is that a customer need not trust the +agent's own filtering. It does not survive contact with the rest of the product: +that customer is already trusting agent-side filtering for the symbol allow-list +that governs what leaves a profile, for the drop of `env`, `args` and `command` +before the informer cache, and for the redaction in every node report. And under +the `inventory` and `ebpf` profiles the agent runs with `hostPID` and +`CAP_SYS_PTRACE`, which no namespace-scoped RBAC bounds — the boundary would look +like a guarantee exactly where it is not one. + +## Consequences + +**Easier.** One fewer unbuilt promise, and one fewer shape for every future +decision to be checked against — ADR 0032 had to weigh what a mode nobody had +built would lose. `security.md` states one installation and describes it truly. + +**Harder / given up.** A buyer whose requirement is a namespace boundary enforced +by the API server is not served by this product. That was already true; what +changes is that they learn it from the document instead of from an installation +that collects nothing. If such a buyer ever becomes the reason to build +something, it is a new product decision with its own ADR, not the resumption of +this one. + +**Not changed.** No code, no chart, no RBAC. The ClusterRole the chart renders +today is what it rendered yesterday, and `internal/chartrender/guardrail_test.go` +still holds its resource list closed. + +This ADR records a documentation change made in the same pull request, per the +process in [`README.md`](README.md). diff --git a/docs/adr/README.md b/docs/adr/README.md index 77dc0b4..79cbea2 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -112,3 +112,4 @@ line to paste. - [0052. The peak the kernel remembers, and the second file the scanner opens](0052-the-peak-the-kernel-remembers.md) - [0053. The network counters are the pod's, and they say nothing about where the bytes went](0053-network-counters-are-the-pods.md) - [0054. Coverage says how much was hidden, never what](0054-coverage-says-how-much-was-hidden-never-what.md) +- [0055. The installation is cluster-wide, and there is no narrower mode](0055-the-installation-is-cluster-wide.md) diff --git a/docs/security.md b/docs/security.md index d9b1992..640df1f 100644 --- a/docs/security.md +++ b/docs/security.md @@ -188,23 +188,19 @@ the node from the token instead of believing the request (ADR 0040). A token tha was not projected into a running pod carries no node and is refused. Resolving the OIDC discovery and JWKS endpoints is a read-only non-resource URL `GET`. -### Cluster-wide vs. namespace-scoped installation - -- **Cluster-wide (default):** a single ClusterRole with the rules above. -- **Namespace-scoped [planned]:** Role + RoleBinding only in namespaces you - list; no ClusterRole is created. This would be a hard boundary enforced by - the API server, not by agent configuration. Not built: the agent's informers - are cluster-wide today, so this mode has no implementation behind it, and the - trade-offs below describe what it would cost rather than what it costs. - - Honest trade-off: `nodes` and `namespaces` are cluster-scoped resources, so - in this mode the agent cannot compute node idle capacity, cannot read - instance types for pricing, and cannot reconcile totals against your cloud - invoice. It reports requests-vs-usage findings for the permitted namespaces - only. `priorityclasses` and `storageclasses` are cluster-scoped for the same - reason, so in this mode `cluster_policy` carries namespace policy without the - two catalogs: a workload's priority class and a claim's storage class are - reported by name, and what those names mean is not. +### The installation is cluster-wide + +One ClusterRole with the rules above, in every installation. This is not a +default with a narrower alternative on offer: `nodes`, `nodes/proxy`, +`namespaces`, `priorityclasses` and `storageclasses` are cluster-scoped, and +`nodes/proxy` is how the agent reaches every kubelet — so an installation +without a ClusterRole would collect no usage at all +([ADR 0055](adr/0055-the-installation-is-cluster-wide.md)). + +What scopes collection is the four controls in +[§11](#11-your-controls-and-what-the-agent-says-about-itself), applied at the +collection stage rather than at the API server, and the counts in +`collection_coverage` are how you check that they were. ---