A target whose tests enrol a service identity could not be built at all. CultLib's Linux protector binds the seed to /etc/machine-id, the build image carries none, and the runner is --read-only, so Odin's suite failed every step with "Linux machine-id is unavailable". Mounting the host's machine-id would let a build container protect a seed that unwraps on the host, which is exactly the property the binding exists to deny. So each frozen workspace now gets its own identity, derived from the workspace path and mounted read-only: stable for the life of a build, and useless anywhere else including this host. Also moves Idunn's topology store to /var/lib/gamecult/idunn-projection. It is Idunn's published surface -- every managed target must read it to verify its own Expected incarnation -- and it was sitting inside Idunn's private 0750 root where no workload could traverse to it. Odin's own daemon already named that directory in its tests. Odin's three roots become writable for the same reason Heimdall's are: a target Idunn cannot write is a target it cannot deploy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
4.7 KiB
Desktop File
94 lines
4.7 KiB
Desktop File
[Unit]
|
|
Description=Idunn deployment admission and daemon-survival authority
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
# Deployment runners use Docker, but continuity actuation does not. Warm,
|
|
# fence, promote, drain and restart all run on systemd; only the build step
|
|
# is containerized, and worktree-tree recipes skip even that. A hard
|
|
# Requires= would let a Docker outage stop Idunn itself, killing daemon
|
|
# survival for every target. Want it, order after it, and let individual
|
|
# container-backed deploys fail on their own if it is absent.
|
|
Wants=docker.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
|
|
# Idunn runs privileged, and this is deliberate rather than laziness. It is the
|
|
# actuation authority: it starts transient units with systemd-run, writes unit
|
|
# files and route configuration, reloads nginx, and drives Docker. It invokes
|
|
# those programs directly -- there is no sudo anywhere in the source -- so a
|
|
# confined User= cannot actuate anything.
|
|
#
|
|
# The privilege separation is *internal*, not at the process boundary. Source
|
|
# freezing runs as the unprivileged idunn user via --source-uid/--source-gid
|
|
# (both validated as non-zero); the privileged driver never opens Git. What
|
|
# stops a compromised target repository from using that privilege is that Idunn
|
|
# accepts no imperative input: a recipe declares capability, the operator
|
|
# binding grants affordances, and cli_exposes_only_declarative_commands asserts
|
|
# there is no way to hand the daemon a command string.
|
|
#
|
|
# The previous generation ran as User=idunn and reached root through a narrow
|
|
# sudoers grant onto a shell actuator. That inverted the safety story: the
|
|
# process was confined but the thing it invoked was an unaudited root shell.
|
|
User=root
|
|
Group=root
|
|
|
|
# Idunn discovers no authority implicitly. Every store is named here, and every
|
|
# one of them is Idunn's own -- no path below belongs to a managed target.
|
|
ExecStart=/usr/local/bin/idunn serve \
|
|
--state-store /var/lib/gamecult/idunn/control.cc \
|
|
--bindings-dir /etc/gamecult/idunn/bindings \
|
|
--source-root /var/lib/gamecult/idunn/sources \
|
|
--staging-root /var/lib/gamecult/idunn/staging \
|
|
--topology-store /var/lib/gamecult/idunn-projection/topology.cc \
|
|
--idunn-identity-store /var/lib/gamecult/idunn/idunn-service-identity.cc \
|
|
--odin-correlation-store /var/lib/gamecult/odin/topology.cc \
|
|
--odin-trust-anchor /etc/gamecult/idunn/odin-topology-anchor.cc \
|
|
--deployment-brake-operator-anchor /etc/gamecult/idunn/deployment-brake-operator-anchor.cc \
|
|
--source-uid 986 \
|
|
--source-gid 978
|
|
Restart=always
|
|
RestartSec=5
|
|
TimeoutStopSec=20
|
|
KillSignal=SIGINT
|
|
UMask=027
|
|
|
|
# /run/idunn, for the route driver's preflight staging. systemd creates and
|
|
# removes it with the unit, so it never survives as stale state across a
|
|
# restart.
|
|
RuntimeDirectory=idunn
|
|
RuntimeDirectoryMode=0750
|
|
|
|
PrivateTmp=yes
|
|
ProtectHome=yes
|
|
# ProtectSystem=full keeps /usr and /etc read-only, then the paths Idunn
|
|
# actually actuates are re-opened one at a time. This list is the blast radius,
|
|
# and it is the reason running as root is tolerable: root plus an enumerated
|
|
# write set is narrower than root everywhere.
|
|
ProtectSystem=full
|
|
ReadWritePaths=/etc/gamecult/idunn
|
|
ReadWritePaths=/etc/nginx/idunn-stream-routes
|
|
ReadWritePaths=/etc/systemd/system
|
|
# idunn-projection is deliberately outside Idunn's own 0750 directory: the
|
|
# topology store is Idunn's *published* surface, and every managed target must
|
|
# read it to verify its own Expected incarnation against the Idunn anchor. It
|
|
# was inside the private root, where no workload could traverse to it.
|
|
ReadWritePaths=/var/lib/gamecult/idunn /var/lib/gamecult/idunn-authority /var/lib/gamecult/idunn-projection
|
|
ReadWritePaths=/srv/build
|
|
# One entry per managed target: its runtime root, state root and release root.
|
|
# A target Idunn cannot write is a target it cannot deploy, so this list grows
|
|
# by exactly three paths per migration and is the honest blast radius.
|
|
ReadWritePaths=/etc/gamecult/heimdall /var/lib/gamecult/heimdall /srv/heimdall/app
|
|
# Odin is a managed target like any other now, so its three roots are writable
|
|
# on the same terms. This used to be ReadOnlyPaths, on the reasoning that Idunn
|
|
# only *reads* Odin's correlation store to gate promotion. That remains true of
|
|
# the correlation itself -- continuity never consults it, see
|
|
# stale_odin_provider_receipt_cannot_gate_continuity_restart, and an absent
|
|
# store yields None rather than an error, so Odin being down blocks promotions
|
|
# and leaves daemon survival untouched. But a target Idunn cannot write is a
|
|
# target it cannot deploy, and Odin has to be deployable: it is the bootstrap
|
|
# target every other target's warming presence is observed through.
|
|
ReadWritePaths=/etc/gamecult/odin /var/lib/gamecult/odin /srv/odin
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|