Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/adr/0032-cluster-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions docs/adr/0055-the-installation-is-cluster-wide.md
Original file line number Diff line number Diff line change
@@ -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).
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
30 changes: 13 additions & 17 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down