Record what the Odin migration exposed
Five things that had never been exercised failed on the way through Odin, and each is now a test: the unprojectable RUDP route, step environment not being ambient, build containers having no machine identity, the published projection sitting inside Idunn's private root, and a signing identity living in the state root the workload can write. Also documents that `idunn validate` proves nothing about deployability without a binding -- admit is where every cross-document rule lives -- and repairs apostrophes an earlier pass mangled into ".s". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
da7dcf4b2d
commit
589478c6ff
1 changed files with 54 additions and 5 deletions
|
|
@ -180,6 +180,20 @@ The binding must name every affordance the recipe's runners derive. Omitting one
|
|||
fails `admit()`. This is deliberate — a compromised target repository can change
|
||||
what gets built, but not what it may touch.
|
||||
|
||||
### 3b. Validate the recipe *with* the binding
|
||||
|
||||
```bash
|
||||
idunn validate --recipe <recipe> --binding <binding>
|
||||
```
|
||||
|
||||
Validating a recipe alone parses it and stops. Every rule that relates the two
|
||||
documents lives in `OperatorBinding::admit`, and admit does not run without a
|
||||
binding. Odin's recipe passed the recipe-only check for months while being
|
||||
structurally unadmittable: it declared `[state]`, which forces the binding to
|
||||
carry a state root, which in turn requires the launch contract to carry a
|
||||
`state_root` argument -- and its arguments named an absolute `store_path`
|
||||
instead. A recipe-only "ok" is not evidence that a target can be deployed.
|
||||
|
||||
### 4. Dry-run, then admit
|
||||
|
||||
```bash
|
||||
|
|
@ -417,15 +431,15 @@ Build fixtures with the CultCache client so a test can disagree with you.
|
|||
|
||||
## Per-target host preprovisioning
|
||||
|
||||
Idunn does not create a target.s roots; it refuses to deploy into paths that are
|
||||
Idunn does not create a target's roots; it refuses to deploy into paths that are
|
||||
not already shaped correctly. Each check exists so a compromised or careless
|
||||
binding cannot widen access, and each one cost a failed deployment to discover:
|
||||
|
||||
| Path | Required shape | Check |
|
||||
|---|---|---|
|
||||
| `state_root` | `root:<state_group>`, mode `2770` | must be root-owned, group-owned by the state group, not world-writable, and setgid so the workload.s dynamic user inherits group access |
|
||||
| `cache_root` | owned by the runner.s container uid, mode `0700` | "dedicated exact-identity 0700 directory" -- the uid is the runner.s `user`, not the source identity |
|
||||
| `cache_root` parent | root-owned, not group- or world-writable | `/srv/build` is `idunn:idunn` and therefore fails; put caches under Idunn.s own root |
|
||||
| `state_root` | `root:<state_group>`, mode `2770` | must be root-owned, group-owned by the state group, not world-writable, and setgid so the workload's dynamic user inherits group access |
|
||||
| `cache_root` | owned by the runner's container uid, mode `0700` | "dedicated exact-identity 0700 directory" -- the uid is the runner's `user`, not the source identity |
|
||||
| `cache_root` parent | root-owned, not group- or world-writable | `/srv/build` is `idunn:idunn` and therefore fails; put caches under Idunn's own root |
|
||||
| frozen source stage | root-owned, non-writable | source is frozen *as* `idunn`, then copied into a root-owned actuation stage |
|
||||
| runner network | must exist | the binding names a docker network; the operator provisions it |
|
||||
| runtime presence identity | `root:root`, mode `0400`, `nlink` 1 | passed to the workload as a parent-only descriptor, so it must be unwritable and unaliased -- a second hard link would be a second path to the signing key |
|
||||
|
|
@ -441,12 +455,47 @@ path. Naming the entry after the variable the service already reads --
|
|||
change at all. Setting the same name in `[workload.environment]` as well is
|
||||
refused as a collision, which is the mechanism telling you there is one owner.
|
||||
|
||||
**The Idunn unit also needs the target.s roots.** `ProtectSystem=full` makes
|
||||
**The Idunn unit also needs the target's roots.** `ProtectSystem=full` makes
|
||||
`/etc` read-only, so a target whose `runtime_root` is not in `ReadWritePaths`
|
||||
fails at "creating runtime bundle". Every migration adds exactly three paths --
|
||||
runtime root, state root, release root -- and that list is the honest blast
|
||||
radius of running Idunn as root.
|
||||
|
||||
## What the Odin migration cost, and what it fixed
|
||||
|
||||
Odin is the bootstrap target, so five things that had never been exercised all
|
||||
failed on the way through. Each one is now a test.
|
||||
|
||||
**A routed RUDP target could be admitted but not projected.** `admit` accepts
|
||||
`(route_required, Rudp, nginx-stream-udp)` against an `rudp://` endpoint, and
|
||||
the nginx driver already wrote `listen ... udp reuseport` -- but
|
||||
`endpoint_scheme` lumped RUDP in with `Private` and refused to name a scheme,
|
||||
so `expected_projection` bailed. Odin is the only routed RUDP target, and
|
||||
nothing else can warm until Odin is admitted.
|
||||
|
||||
**Only a step's declared `required_environment` reaches its container.** A
|
||||
runner's `[runners.<id>.environment]` is not ambient: names not listed on the
|
||||
step are dropped. Odin's steps declared none, so `CARGO_HOME` never arrived and
|
||||
cargo fell back to the image's read-only `/usr/local/cargo`.
|
||||
|
||||
**A build container has no machine identity.** CultLib's Linux protector binds
|
||||
a service identity seed to `/etc/machine-id`; the build image carries none and
|
||||
the runner is `--read-only`, so any target whose tests enrol an identity failed
|
||||
every step. Idunn now derives a machine-id per frozen workspace and mounts it
|
||||
read-only. Do not "fix" this by mounting the host.s: that would let a build
|
||||
container protect a seed that unwraps on the host, which is the property the
|
||||
binding exists to deny.
|
||||
|
||||
**The published projection was inside Idunn's private root.** Every managed
|
||||
target must read the topology store to verify its own Expected incarnation
|
||||
against the Idunn anchor, and it sat in a `0750 idunn:idunn` directory no
|
||||
workload could traverse. It now lives in `/var/lib/gamecult/idunn-projection`,
|
||||
world-readable, integrity protected by signature rather than by mode.
|
||||
|
||||
**A target's signing identity does not belong in its state root.** `state_root`
|
||||
is writable by the workload, and a daemon that can rewrite its own signing
|
||||
identity does not have one. Odin's topology identity moved to the runtime root.
|
||||
|
||||
## Cutting over the authority
|
||||
|
||||
Idunn last, and not by deploying Idunn with Idunn.
|
||||
|
|
|
|||
Loading…
Reference in a new issue