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. ---