Skip to content

SOPS Secret Management

This configuration uses SOPS (Secrets OPerationS) with Age encryption for managing secrets securely.

Overview

SOPS is used for:

  • User password hashes
  • Other sensitive configuration data

Files

  • secrets/users.yaml - Encrypted user data (passwords, etc.)
  • .sops.yaml - SOPS configuration with age recipients

Architecture

System-Level Secrets

  • Managed via NixOS SOPS module
  • SSH host keys used for age encryption/decryption
  • Secrets available at /run/secrets/ during boot

User-Space Environment Variables

For secrets that need to be available in shell sessions (SSH, console):

  1. SOPS Template: Create template in NixOS module
  2. Systemd User Service: Copy template to user-accessible location
  3. Home Manager Integration: Source environment variables in shell configurations

Example pattern:

# NixOS module
sops.templates."app-env-user" = {
  content = ''
    APP_SECRET=${config.sops.placeholder."app/secret"}
  '';
  owner = "user";
  group = "users";
  mode = "0400";
};

systemd.user.services.app-env = {
  description = "Copy app environment to user space";
  serviceConfig = {
    Type = "oneshot";
    RemainAfterExit = true;
    ExecStart = pkgs.writeShellScript "app-env-setup" ''
      mkdir -p "$HOME/.config/environment.d"
      cp "${config.sops.templates."app-env-user".path}" "$HOME/.config/environment.d/50-app.conf"
      chmod 600 "$HOME/.config/environment.d/50-app.conf"
    '';
  };
};
# Home Manager module
home.file.".local/bin/load-app-env" = {
  text = ''
    #!/bin/sh
    if [ -f "$HOME/.config/environment.d/50-app.conf" ]; then
      set -a
      source "$HOME/.config/environment.d/50-app.conf"
      set +a
    fi
  '';
  executable = true;
};

# Shell integration
programs.bash.bashrcExtra = ''
  if [ -x "$HOME/.local/bin/load-app-env" ]; then
    source "$HOME/.local/bin/load-app-env"
  fi
'';

Usage

To edit secrets:

sops secrets/users.yaml

To view decrypted secrets:

sops --decrypt secrets/users.yaml

YubiKey-backed Age Identity (End-to-End)

This repository supports adding a YubiKey-backed recipient in addition to existing recipients.

Current behavior:

  • Existing host and admin recipients continue to work
  • New YubiKey recipient is added for operator decryption/editing
  • Files remain decryptable during migration

1. Install tools on each workstation

Required tools:

  • sops
  • age
  • age-plugin-yubikey

This repo now includes these packages in:

  • macOS/Home Manager development package sets
  • WSL host package set (darth-vader)
  • development shell (nix develop)

Home Manager now also manages:

  • ~/.config/sops/age/
  • sops-yubikey-init helper command

Home Manager does not manage ~/.config/sops/age/yubikey.txt contents, which prevents clobbering your generated identity on rebuilds.

2. Initialize a fresh YubiKey identity

Plug in the YubiKey and run:

sops-yubikey-init

By default this uses slot 1. It first tries to load identity metadata from that slot and only generates a new identity in the same slot if none exists.

To regenerate and overwrite:

sops-yubikey-init --force

If you accidentally generated multiple slots previously, keep using the slot that matches .sops.yaml recipient (or update .sops.yaml to the new recipient).

List recipients and copy the age... recipient value:

age-plugin-yubikey --list

3. Add YubiKey recipient to repository policy

In .sops.yaml:

  1. Add a new key anchor under keys: (for example &admin_karl_yubikey)
  2. Add that key to each relevant creation_rules[*].key_groups[*].age list
  3. Keep existing recipients during migration

Example pattern:

keys:
  - &admin_karl age1...
  - &admin_karl_yubikey age1...
  - &r2d2_host age1...

creation_rules:
  - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
    key_groups:
      - age:
          - *admin_karl
          - *admin_karl_yubikey
          - *r2d2_host

4. Rewrap existing encrypted files with updated recipients

Run updatekeys for existing secrets so metadata includes the new recipient:

sops updatekeys secrets/users.yaml

For non-interactive usage:

sops updatekeys -y secrets/users.yaml

5. Verify decryption with YubiKey

sops --decrypt secrets/users.yaml >/dev/null

Expected behavior:

  • Touch prompt appears on YubiKey (with touch-policy always)
  • PIN prompt appears according to PIN policy (once caches during session)

6. Multi-workstation usage model

You can use the same YubiKey on macOS and WSL, but each workstation still needs:

  • installed tooling (sops, age, age-plugin-yubikey)
  • a local YubiKey identity file (for example ~/.config/sops/age/yubikey.txt)

The identity file is metadata (not the private key). The private key material stays on the YubiKey.

7. Migration and hardening

Recommended rollout:

  1. Add YubiKey recipient alongside existing keys
  2. Validate decryption/edit workflow from each workstation
  3. Remove old software admin recipient later (optional hardening)
  4. Run sops updatekeys again after recipient removal

Troubleshooting

  • If decryption fails, confirm age-plugin-yubikey is in PATH
  • On Linux/WSL environments, ensure smartcard/PCSC plumbing is available
  • If you switch YubiKey applets frequently (SSH/FIDO/PIV), PIN cache may reset
  • Use age-plugin-yubikey --list to confirm the expected recipient is visible