Skip to content

sops-nix silently installs no secrets in three configurations #533

Description

@aldoborrero

Summary

system-manager's sops-nix support works, but only for the default
configuration. Three configurations produce no secrets at all, with no error
and no warning.

nix/modules/upstream/sops-nix.nix declares system.activationScripts.{setupSecrets,setupSecretsForUsers,generate-age-key} as stubs. That is enough for the common case, because sops-nix's sops.useSystemdActivation defaults to config.services.userborn.enable, which system-manager sets to true — so secrets get installed from sops-install-secrets.service and the stubbed activation scripts are never populated.

The three cases below fall outside that.

1. sops.age.generateKey = true

sops-nix only implements key generation as an activation script (system.activationScripts.generate-age-key). The stub absorbs it, so the key is never generated and sops-install-secrets.service fails on any machine that does not already have the key at sops.age.keyFile.

2. sops.secrets.<name>.neededForUsers = true

modules/sops/secrets-for-users/default.nix orders the unit around systemd-sysusers.service:

systemd.services.sops-install-secrets-for-users = {
  wantedBy = [ "systemd-sysusers.service" ];
  before   = [ "systemd-sysusers.service" ];
  ...
};

system-manager creates users with userborn and sets systemd.sysusers.enable = false. The only symlink emitted for this unit is therefore systemd-sysusers.service.wants/sops-install-secrets-for-users.service; it is not in sysinit-reactivation.target.requires, so it never runs on activation, and it is not ordered before userborn.service, so the secret is not there when the user that consumes it is created.

Reproducer — a user whose password comes from sops ends up without one:

sops.secrets.alice-password.neededForUsers = true;
users.users.alice = {
  isNormalUser = true;
  hashedPasswordFile = config.sops.secrets.alice-password.path;
};

3. services.userborn.enable = false

This flips sops.useSystemdActivation to false, which switches sops-nix to the activation-script path that system-manager cannot run. Secrets management is disabled entirely, silently.

Worth noting for whoever fixes this: forcing sops.useSystemdActivation = true is enough for regular secrets, but not for neededForUsers ones. secrets-for-users/default.nix recomputes the condition locally and ignores the option:

useSystemdActivation =
  sysusersEnabled || (options.services ? userborn && config.services.userborn.enable);

So case 2 remains tied to userborn being enabled, whatever useSystemdActivation says.

4. The documented behaviour does not match any of this

docs/site/how-to/import-nixos-module.md presents the activation-script options as pure stubs and states that "the actual secret decryption is handled differently in system-manager through a systemd service" without saying which service, when it runs, or what enables it. Combined with the CHANGELOG line for 1.1.0 being the only other mention, there is no way for a user to find out that sops-nix works at all, let alone which parts of it do.

Environment

  • system-manager main at 64748b6
  • sops-nix a8627b21b9107c5711c96b84f32a9a4b3d45295f
  • Reproduced on Debian 13 and by inspecting the generated units for each case

Expected

The first two should work. The third should either work or fail evaluation with an explanatory message, rather than producing a configuration that looks like it manages secrets and does not.

Fix

I have a fix ready and will open a PR against main shortly. It turns nix/modules/upstream/sops-nix.nix into a compatibility module: it defaults sops.useSystemdActivation to true and asserts it stays enabled, re-points the for-users unit at userborn.service and sysinit-reactivation.target, and runs the generate-age-key script from a oneshot unit. Two container tests cover cases 1 and 2, and the documentation is corrected.

Branch, if useful before the PR is up:
https://github.com/aldoborrero/system-manager/tree/aldoborrero/sops-nix-systemd-activation

One caveat I cannot resolve myself: the two new container tests have not been executed. makeContainerTest requires the uid-range system feature, which the Nix daemon on my machine does not offer and which cannot be toggled from the command line. The same assertions were run and passed as VM tests on Debian 13 before being converted.


Investigated and drafted with the help of Claude Code. The findings above were checked against the repository at the revisions listed in Environment: the quoted sops-nix code, the generated unit files and symlinks, and the test results are all from actual builds and runs, not from model recall. The caveat about the unexecuted container tests is accurate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions