- Add .gitignore for nix build result symlinks - Fix all headscale CLI commands: --user now requires numeric ID, not username (changed in newer headscale versions) - Add 'headscale users list' step to docs where preauth keys are created
3.3 KiB
3.3 KiB
SOPS Secret Management
Atomic secret provisioning for NixOS using sops-nix.
Overview
Secrets are encrypted with age using SSH host keys, ensuring:
- No plaintext secrets in the repository
- Secrets are decrypted at activation time
- Each host can only decrypt its own secrets
Setup
1. Get Host's Age Public Key
After a host is installed, extract its age key from the SSH host key:
nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 <HOST_IP> | ssh-to-age'
Or locally on the host:
nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
2. Configure .sops.yaml
Add the host key to .sops.yaml:
keys:
- &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t
- &main_key age1... # cryodev-main
- &pi_key age1... # cryodev-pi
creation_rules:
- path_regex: hosts/cryodev-main/secrets.yaml$
key_groups:
- age:
- *admin_key
- *main_key
- path_regex: hosts/cryodev-pi/secrets.yaml$
key_groups:
- age:
- *admin_key
- *pi_key
3. Create Secrets File
sops hosts/<hostname>/secrets.yaml
This opens your editor. Add secrets in YAML format:
tailscale:
auth-key: "tskey-..."
some-service:
password: "secret123"
Usage in Modules
Declaring Secrets
{ config, ... }:
{
sops.secrets.my-secret = {
# Optional: set owner/group
owner = "myservice";
group = "myservice";
};
}
Using Secrets
Reference the secret path in service configuration:
{
services.myservice = {
passwordFile = config.sops.secrets.my-secret.path;
};
}
Using Templates
For secrets that need to be embedded in config files:
{
sops.secrets."netdata/stream-api-key" = { };
sops.templates."netdata-stream.conf" = {
content = ''
[stream]
enabled = yes
api key = ${config.sops.placeholder."netdata/stream-api-key"}
'';
owner = "netdata";
};
services.netdata.configDir."stream.conf" =
config.sops.templates."netdata-stream.conf".path;
}
Common Secrets
cryodev-main
mailserver:
accounts:
forgejo: "$2y$05$..." # bcrypt hash
admin: "$2y$05$..."
forgejo-runner:
token: "..."
headplane:
cookie_secret: "..." # openssl rand -hex 16
agent_pre_authkey: "..." # headscale preauthkey
tailscale:
auth-key: "tskey-..."
cryodev-pi
tailscale:
auth-key: "tskey-..."
netdata:
stream:
child-uuid: "..." # uuidgen
Generating Secret Values
| Secret | Command |
|---|---|
| Mailserver password | nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt' |
| Random hex token | nix-shell -p openssl --run 'openssl rand -hex 16' |
| UUID | uuidgen |
| Tailscale preauth | sudo headscale preauthkeys create --expiration 99y --reusable --user <ID> |
Updating Keys
After modifying .sops.yaml, update existing secrets files:
sops --config .sops.yaml updatekeys hosts/<hostname>/secrets.yaml
Troubleshooting
"No matching keys found"
Ensure the host's age key is in .sops.yaml and you've run updatekeys.
Secret not decrypting on host
Check that /etc/ssh/ssh_host_ed25519_key exists and matches the public key in .sops.yaml.