nix/hive-matrix: read register token via systemd LoadCredential (#644 / iris)
Per iris's recommendation on #644 [comment 8043](http://localhost:3000/hyperhive/hyperhive/issues/644#issuecomment-8043): swap the `chown root:tuwunel + chmod 0640 + pinned GID 10042` shape (shipped via #649) for systemd's `LoadCredential=` mechanism. How it works: systemd reads the host-side file at service start, copies it into a per-service credentials dir (`/run/credentials/tuwunel.service/registration_token`) owned by the dynamic user with mode 0400. Service reads from there. All the namespace mapping happens transparently inside systemd — keeps `DynamicUser=true` + `PrivateUsers=true` intact. Net diff from current shape: - DROP `users.groups.tuwunel.gid = 10042;` from BOTH host AND container - DROP `chown root:tuwunel "$tokenFile"; chmod 0640 "$tokenFile"` from activation script; replace with `chmod 0600` (root:root) - DROP `[ "var" "users" ]` activation dep on `users` (no longer needs the group to exist before chown) - ADD `systemd.services.tuwunel.serviceConfig.LoadCredential = [...]` inside the container config - CHANGE `registration_token_file` from the bind-mount path to `/run/credentials/tuwunel.service/registration_token` - KEEP the bind mount + activation-script token generation (load credential reads the bind-mounted host file at service start) Verified via `nix eval`: - host: no `users.groups.tuwunel` (was: gid = 10042) - container: tuwunel group exists with `gid = null` (auto-allocated; no longer pinned to match host since it doesn't need to) - container: tuwunel.service.serviceConfig.LoadCredential = `["registration_token:/var/lib/hyperhive/matrix-register-token"]` - container: services.matrix-tuwunel.settings.global.registration_token_file = `/run/credentials/tuwunel.service/registration_token` `/run/credentials/<service>/<id>` is a systemd-stable path (documented in `man systemd.exec` → LoadCredential); safe to hardcode.
This commit is contained in:
parent
c5d466c5c5
commit
2e40e1782a
1 changed files with 30 additions and 35 deletions
|
|
@ -250,18 +250,6 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
# Pin the `tuwunel` group at a fixed GID on BOTH the host and the
|
||||
# hive-matrix container. The registration token file lives on the
|
||||
# host bind-mounted into the container; for tuwunel's non-root
|
||||
# user inside the container to read it, the file gets `chown
|
||||
# root:tuwunel` + mode `0640` in the activation script below. That
|
||||
# ownership only works if the numeric GID resolves to the same
|
||||
# name on both sides of the bind — without an explicit pin, the
|
||||
# host's auto-allocated GID for `tuwunel` (if any) almost
|
||||
# certainly wouldn't match the container's. 10042 sits well
|
||||
# outside nixos's auto-allocated system-user range (200..399).
|
||||
users.groups.tuwunel.gid = 10042;
|
||||
|
||||
# Generate the registration token at system activation time, BEFORE
|
||||
# the hive-matrix container would otherwise start with an empty
|
||||
# bind-mount target (argus nit on #565: nspawn creates an empty
|
||||
|
|
@ -271,15 +259,14 @@ in
|
|||
# doesn't exist. 32-byte hex = 64 chars, same shape hive-c0re's
|
||||
# `matrix::ensure_register_token` would produce.
|
||||
#
|
||||
# Ownership: tuwunel inside the hive-matrix container runs as its
|
||||
# own non-root user (nixpkgs's `services.matrix-tuwunel`), so a
|
||||
# 0600 root-owned file denies it open(2) and tuwunel boots loop-
|
||||
# fails with `Permission denied (os error 13)` (#644). Fix:
|
||||
# `chown root:tuwunel` + `chmod 0640` so only the tuwunel group
|
||||
# gains read access (no world-readable footgun, per mara). The
|
||||
# `tuwunel` group GID is pinned to 10042 above so the host's name
|
||||
# → number lookup matches what the container sees.
|
||||
system.activationScripts.hive-matrix-register-token = lib.stringAfter [ "var" "users" ] ''
|
||||
# Ownership: plain `root:root 0600` — tuwunel inside the container
|
||||
# runs as a hardened dynamic user (#644) and reads the token via
|
||||
# systemd's `LoadCredential=` mechanism (see container config
|
||||
# below), so it never needs direct read access on the host-side
|
||||
# file. No `chown :tuwunel` / `chmod 0640` / GID-pin gymnastics
|
||||
# required (per iris on #644 8043, dropping the shape #649
|
||||
# shipped with).
|
||||
system.activationScripts.hive-matrix-register-token = lib.stringAfter [ "var" ] ''
|
||||
tokenFile=${lib.escapeShellArg (toString cfg.registrationTokenFile)}
|
||||
if [ ! -s "$tokenFile" ]; then
|
||||
mkdir -p "$(dirname "$tokenFile")"
|
||||
|
|
@ -287,11 +274,10 @@ in
|
|||
echo >> "$tokenFile"
|
||||
echo "hive-matrix: generated registration token at $tokenFile"
|
||||
fi
|
||||
# Always re-apply ownership + mode (covers existing 0600 root-
|
||||
# owned files from pre-#644 deployments; `chown` + `chmod` are
|
||||
# both idempotent).
|
||||
chown root:tuwunel "$tokenFile"
|
||||
chmod 0640 "$tokenFile"
|
||||
# Always re-apply 0600 (idempotent on already-correct files;
|
||||
# also normalises any 0640 / world-readable carry-over from
|
||||
# pre-LoadCredential deployments).
|
||||
chmod 0600 "$tokenFile"
|
||||
'';
|
||||
|
||||
containers.hive-matrix = {
|
||||
|
|
@ -315,14 +301,6 @@ in
|
|||
{ ... }:
|
||||
{
|
||||
system.stateVersion = "26.05";
|
||||
# Mirror the host's pinned `tuwunel` GID so the bind-mounted
|
||||
# registration token (chowned `root:tuwunel` on the host)
|
||||
# resolves to the same group inside the container. Without
|
||||
# this pin nixos auto-allocates whatever's free, the two
|
||||
# sides diverge, and tuwunel's user falls back to the
|
||||
# "other" mode bits (= no read) on the file. See the host-
|
||||
# side `users.groups.tuwunel.gid` above.
|
||||
users.groups.tuwunel.gid = 10042;
|
||||
services.matrix-tuwunel = {
|
||||
enable = true;
|
||||
package = cfg.package;
|
||||
|
|
@ -346,12 +324,29 @@ in
|
|||
# `yes_i_am_very_very_sure_…_open_registration_…` flag
|
||||
# keeps the server closed to anyone without the token.
|
||||
allow_registration = true;
|
||||
registration_token_file = toString cfg.registrationTokenFile;
|
||||
# Read the registration token via systemd's
|
||||
# `LoadCredential=` mechanism (wired below) instead of
|
||||
# the bind-mount path directly. systemd copies the host-
|
||||
# owned 0600 root:root file into a per-service
|
||||
# credentials dir owned by tuwunel's dynamic user with
|
||||
# mode 0400 — keeps `DynamicUser=true` + `PrivateUsers=true`
|
||||
# intact, no host-side `chown :tuwunel` / GID-pin
|
||||
# gymnastics required (#644 / iris on 8043).
|
||||
registration_token_file = "/run/credentials/tuwunel.service/registration_token";
|
||||
# E2EE disabled in initial rollout per operator call
|
||||
# (#548) — re-enabling tracked at #551.
|
||||
allow_encryption = false;
|
||||
};
|
||||
};
|
||||
# `LoadCredential=<id>:<host-path>` makes systemd copy the
|
||||
# bind-mounted host file into `/run/credentials/tuwunel.service/<id>`
|
||||
# owned by the service's (dynamic) user with mode 0400 at
|
||||
# service start. The hardcoded path in `registration_token_file`
|
||||
# above is the systemd-stable credentials dir; see
|
||||
# `man systemd.exec` → LoadCredential.
|
||||
systemd.services.tuwunel.serviceConfig.LoadCredential = [
|
||||
"registration_token:${toString cfg.registrationTokenFile}"
|
||||
];
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue