Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Secrets Management (sops-nix)

All secrets in this flake are encrypted with sops using age as the backing encryption layer. sops-nix handles decryption at boot and materializes secrets into /run/secrets/ (NixOS) or ~/.config/sops/ (Home Manager).

How it works

secrets/secrets.yaml ──encrypted to──► 15 age public keys (.sops.yaml)
       │
       ▼ sops-nix (at boot)
  /run/secrets/<name>          (NixOS hosts)
  ~/.config/sops/<name>        (Home Manager / macOS)

Each host derives its age identity from its SSH host ed25519 key. The age public key is listed in .sops.yaml. At boot, sops-nix converts the host’s SSH private key to an age identity and decrypts the secrets.

NixOS hosts

Key generation at first boot

SSH host keys are generated by services.openssh.hostKeys (configured in modules/nixos/services/ssh.nix). The ed25519 key at /etc/ssh/ssh_host_ed25519_key is the one used by sops-nix.

sops-nix auto-converts this key to an age identity via generateKey = true and stores it at /var/lib/sops-nix/keys.txt. No manual key provisioning needed on the host — it “just works” after first boot.

Configuration (per host, implicit)

# modules/nixos/security/secrets.nix
sops = {
  defaultSopsFile = "${secretspath}/secrets.yaml";
  age = {
    sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
    keyFile = "/var/lib/sops-nix/keys.txt";
    generateKey = true;
  };
};

Using a secret in a host config

sops.secrets."tokens/beszel-marvin" = {
  mode = "0444";
};

Then reference it:

environment.KEY_FILE = config.sops.secrets."tokens/beszel-marvin".path;

Adding a new NixOS host to secrets

  1. Generate the age public key from the host’s SSH key:
ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub
# Output: age1...

If ssh-to-age isn’t available:

nix shell nixpkgs#ssh-to-age --command ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub
  1. Add the age public key to secrets/.sops.yaml under the host’s entry:
creation_rules:
  - path_regex: secrets.yaml$
    key_groups:
      - age:
        - age1...  # shinji
        - age1...  # kenpachi
        - age1...  # new-host  ← add here
  1. Re-encrypt the secrets file so the new host can decrypt it:
sops updatekeys secrets/secrets.yaml
  1. Commit the updated .sops.yaml and secrets.yaml. The new host can now decrypt secrets on its next boot.

Home Manager / macOS

User age key

Home Manager users decrypt secrets using their own age identity at ~/.config/sops/age/keys.txt (not an SSH host key). This file must be placed manually:

mkdir -p ~/.config/sops/age
# Copy your age identity (private key) here:
echo "AGE-SECRET-KEY-..." > ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt

Bootstrap: user SSH key from secrets

The user’s SSH private key (~/.ssh/id_ed25519) is itself stored in secrets.yaml and decrypted at Home Manager activation. This works because the user’s age key (e.g. mirza) is separately listed in .sops.yaml and can decrypt the SSH key secret.

# modules/home-manager/background/secrets.nix
sops.secrets."ssh_keys/${config.user}" = {
  path = "${config.home.homeDirectory}/.ssh/id_ed25519";
};

The user’s age key (for sops CLI use) is separate from the SSH key (for git/SSH access).

Editing secrets

# Decrypt, edit in $EDITOR, re-encrypt
sops secrets/secrets.yaml

To add a new secret entry:

# secrets/secrets.yaml
my-new-token: "sk-abc123..."

Then reference it in your Nix config:

sops.secrets."my-new-token".path = "/run/secrets/my-token";

Key hierarchy

KeySourceUsed by
Host age keys (shinji, kenpachi, …)/etc/ssh/ssh_host_ed25519_key → agesops-nix decryption on that host
User age keys (mirza, mar)Manually generated with age-keygensops CLI for editing secrets
User SSH keysDecrypted from secrets.yaml by Home ManagerGit push, SSH access, etc.
SSH host keysGenerated at first boot by services.openssh.hostKeysSSH server identity + age identity source

Common commands

# Edit secrets
sops secrets/secrets.yaml

# Re-encrypt after adding/removing recipients
sops updatekeys secrets/secrets.yaml

# Check which keys can decrypt
sops --decrypt secrets/secrets.yaml > /dev/null && echo "OK"

# List recipients
grep age secrets/.sops.yaml

# Convert SSH key to age
ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub

# Generate a new age key (for users)
age-keygen -o ~/.config/sops/age/keys.txt