ADR 0086: a secret reaches a process as a file, and an exception is declared
Closes issue 041 by decision and by code on the same branch: the catalogue engine refuses a secret in a container's env, and a secret-carrying env-file unless the container declares its reason; the controller reads all six of its credentials from files; design 13 states the rule and how it is checked.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
---
|
||||
topic: building it
|
||||
status: accepted
|
||||
date: 2026-09-21
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0085-a-secret-is-a-provision.md
|
||||
---
|
||||
|
||||
# 86. A secret reaches a process as a file, and an exception is declared
|
||||
|
||||
## Context
|
||||
|
||||
The mesh seals a secret to the machine that uses it and discards the plaintext; the host unseals it
|
||||
into a file at 0600 ([ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md)). Then
|
||||
the module hands it to its container through an env-file, and the runtime puts it where anything
|
||||
on the machine that can talk to the runtime can read it: `docker inspect` prints it, and
|
||||
`/proc/<pid>/environ` holds it for the life of the process
|
||||
([issue 041](../04-ISSUES/041-a-sealed-credential-ends-up-in-the-process-environment/00-report.md)).
|
||||
|
||||
Two facts made this a decision rather than a fix. **It is a supported shape**: placing
|
||||
`${secret:…}` in a file a container reads as its environment is what playbook 06 shows, and 36
|
||||
containers in 25 catalogue modules do it, the controller's own among them. And **nothing says which
|
||||
secrets are exposed**: a reader cannot tell from a manifest whether a credential is protected from
|
||||
`inspect` or not, because the manifest looks the same either way. The vault
|
||||
([ADR 0085](0085-a-secret-is-a-provision.md)) rests on the seal this undoes.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **Leave it: the environment is where configuration goes.** Rejected — it gives the sealed value
|
||||
back to a routine operation, and the care taken to seal it is then theatre.
|
||||
2. **Refuse every secret in an environment.** Rejected — some software reads its configuration
|
||||
from the environment and nothing else, and a rule the catalogue cannot obey is a rule that gets
|
||||
switched off.
|
||||
3. **A secret reaches a process as a file; an environment exception is declared, with a reason,
|
||||
and refused otherwise.** Adopted.
|
||||
|
||||
## Decision
|
||||
|
||||
**A secret reaches a process as a file.** A module mounts the file the host wrote and points the
|
||||
program at it; the mesh's own programs accept a `_FILE` twin for every variable that carries a
|
||||
credential, the way the controller's store connections already did.
|
||||
|
||||
**A container that reads a secret from its environment says so.** The manifest key
|
||||
`secrets-in-environment` on the container carries the reason. It is catalogue-level — the host
|
||||
never sees it — and it exists so a reader can tell from the manifest which secrets are exposed
|
||||
that way and why.
|
||||
|
||||
**Everything else is refused.** A `${secret:…}` placeholder inside a container's `env` is never
|
||||
filled and is refused outright. A file carrying a secret that a container names in `env-file` is
|
||||
refused unless the container declares the exception.
|
||||
|
||||
## Consequences
|
||||
|
||||
The property *a sealed credential is readable only where it is used* becomes a manifest-level
|
||||
fact: true where no exception is declared, stated where one is. The controller reads all six of
|
||||
its credentials from files; the 35 other containers are marked with their reason and convert one
|
||||
by one where their software accepts a path, which is per-module work under
|
||||
[issue 041](../04-ISSUES/041-a-sealed-credential-ends-up-in-the-process-environment/00-report.md).
|
||||
|
||||
What got harder: a module author meets one more refusal, and the reason they write is only as
|
||||
honest as they are. What is not decided: the reach of the exposure on a node — which identities can
|
||||
talk to the runtime — which decides whether a declared exception is a hardening item or something
|
||||
sharper.
|
||||
|
||||
## How it is checked
|
||||
|
||||
The catalogue engine refuses at composition, before anything reaches a machine, and its tests
|
||||
refuse both shapes and prove the reason is stripped from what is sent.
|
||||
|
||||
## References
|
||||
|
||||
- [issue 041](../04-ISSUES/041-a-sealed-credential-ends-up-in-the-process-environment/00-report.md)
|
||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md), [ADR 0085](0085-a-secret-is-a-provision.md)
|
||||
- [`03-DESIGN/01-to-be/13-credentials-and-their-rotation.md`](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md)
|
||||
Reference in New Issue
Block a user