feat(guests): give guests only the host-decrypted secrets they name #33
Reference in New Issue
Block a user
Delete Branch "task-0007-guest-secrets-placement"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a
secretsplacement option to the guest builder: a Host setsguests.<path>.secrets, a list of secret names.For each name the builder declares
sops.secrets.<name>on the Host — so the Host is the sole decryptor, from the sops files it already holds — and bind-mounts the decrypted file into the guest read-only at the same/run/secrets/<name>path it occupies on a Host.A service inside the guest reads its credentials at a predictable location, while the guest carries no age key and decrypts nothing itself.
Ownership needs no new code: the container already runs in the Host's uid and gid space one to one (
privateUsers = "no"), so the decrypted file's Host owner is its owner inside the guest.A build-time assertion rejects an in-guest path claimed by both a
mountsentry and a secret, since the two attribute sets merge and the collision would otherwise resolve silently in the secret's favour.No Host commits a
secretsplacement (the only Host,neogaia, is a laptop carrying no service that names one), matching the mounts/networking/storage foundations.Verified via
nixosConfigurations.neogaia.extendModules: the resolvedsops.secrets, the container's read-onlybindMountsentry, a null interiorsops.age.keyFile, and an empty interiorsops.secrets.The full Host toplevel was built with the sample guest naming a real key;
nix flake checkpasses on the committed tree.Deviation: none from the task plan. sops-nix validates at build time that each named key exists in the Host's sops files, so a name absent from those files fails the build clearly rather than at activation — a stronger guarantee than the task required, noted for reviewers.
Review
Risk
Overall: HIGH
bindMounts, no other callers touched.secretsand services depend on/run/secrets/<name>, removing it breaks consumers.map/listToAttrs, no tangled control flow.Standards — unaddressed
userMountscomment is a "what" comment (lib.nix): kept deliberately, since it documents the non-obvious keying (the attribute key is the in-guest path). A judgement call; the duplication, cross-file narration, fragment, and prose-semicolon findings were fixed in the diff.Spec — unaddressed
0400 root:rootsecret (spec "Secrets"): by design and recorded in the task's Implementation Notes. Thesecretsfield stays a list of names, which the spec treats as its interface; the operator setssops.secrets.<name>.owneron the Host when a non-root service needs the file, and it merges cleanly with the builder's stub declaration.nix eval(spec testing decisions): the mount source/run/secrets/<name>is a sops-managed symlink, so real reads inside the guest — symlink resolution at container start and secret-decryption-before-container ordering — are verified manually on the target Host, per the spec's decision to verify real reads there.— Claude