Sops Nix
Updated: September 28, 2024
Secrets management for NixOS
Better than Agenix because it works with Home Manager.
Table of Contents
All keys in
.sops.yaml
are the pub age keys.
Thekeys.txt
file holds the corresponding private age keys.
Each system needs thekeys.txt
placed at~/.config/sops/age/keys.txt
First we need to setup .sops.yaml and then secrets.yaml.
After these are set we can work on sops.nix and installing into the flake.
SSH KEYS
ssh keys need to be converted to age keys to add to .sops.yaml
This is the file that allows the use of sops to encrypt and decrypt secrets.yaml
# create a folder that sops expects to exist when creating keys
mkdir -p ~/.config/sops/age
# create a new key from scratch
nix shell nixpkgs#age -c age-keygen -o ~/.config/sops/age/keys.txt
# create an age key from a private ssh key
nix run nixpkgs#ssh-to-age -- -private-key -i ~/.ssh/private > ~/.config/sops/age/keys.txt
nix-shell -p ssh-to-age --run 'cat ~/.ssh/id_ed25519.pub | ssh-to-age'
# print the public key of ~/.config/sops/age/keys.txt
nix shell nixpkgs#age -c age-keygen -y ~/.config/sops/age/keys.txt
Config
# create a config file at root of the flake and secrets folder
vim .sops.yaml
├── flake.nix
├── .sops.yaml
├── secrets/
│ ├── secrets.yaml
# .sops.yaml
# nix-shell -p ssh-to-age --run 'cat ~/.ssh/id_ed25519.pub | ssh-to-age'
# sops updatekeys secrets/secrets.yaml if you add/remove any keys or if no sops installed:
# nix-shell -p sops --run "sops updatekeys secrets/secrets.yaml"
keys:
- &users:
- &megacron agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # public key user
- &hosts:
- &blackout agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &energon agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &galvatron agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &ratchet agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
creation_rules:
- path_regex: secrets/secrets.yaml$
key_groups:
- age:
- *megacron
- *hosts
Secrets
Prior to using sops command there must exist a secrets directory in the root of the flake.
# create/edit a secrets file at root of the flake
sops secrets.yaml
# if you only have nix installed use nix shell
nix-shell -p sops --run "sops secrets/secrets.yaml"
# This is a comment
example_key: example_value
example_array:
- example_value1
- example_value2
example_number: 1234.5678
example_boolean:
- true
- false
User Passwords
Sops must run after NixOS creates users so it is not possible to set with
hashedPasswordFile.
# hash a password
echo "password" | mkpasswd -s
# configuration.nix
{ config, ... }:
{
sops.secrets.megacron-password.neededForUsers = true;
users.users.megacron = {
isNormalUser = true;
hashedPasswordFile = config.sops.secrets.megacron-password.path;
};
}
# secrets.yaml
private_keys:
megacron: | # key must be same level of indentation as the pipe
-----BEGIN PRIVATE KEY-----
asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
-----END PRIVATE KEY-----
megacronPW: passwordwehashed # mkpasswd -s
smtp-password: asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
megacron-password: asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
Nix Install
We will go into making a sops.nix module at the end.
This is just to understand the elements invovled on how sops works.
# add as an input to flake (follows line optional)
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
# then also add to nixosConfigurations
specialArgs = { inherit inputs; }; <-This
modules = [ ./configuration.nix ];
# open configuration.nix and add to imports
imports = [
inputs.sops-nix.nixosModules.sops
];
sops = {
defaultSopsFile = ./secrets/secrets.yaml;
defaultSopsFormat = "yaml";
age.keyFile = "home/user/.config/sops/age/keys.txt";
# how to place secret values
secrets.some-key = { };
secrets."dir/subdir/secret" = { };
# add another owner to your keys
owner = "bob"
owner = config.users.users.bob.name;
};
Now lets say we want to access and reveal a value
# this would cat out the value in plain text
$(cat ${config.sops.secrets."dir/subdir/secret".path})
sops.nix
{ inputs, config, ... }:
{
imports = [
inputs.sops-nix.nixosModules.sops
];
sops = {
defaultSopsFile = "secrets.yaml";
validateSopsFiles = false;
age = {
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # auto import host ssh keys as age keys
keyFile = "/var/lib/sops/age/key.txt"; # expected age key to already be on filesystem
generateKey = true; # generate a new age key if one does not exist
};
# secrets will be output to /run/secrets
# e.g. /run/secrets/msmtp-password
# secrets required for user creation are handled in respective ./users/<username>.nix files
# because they will be output to /run/secrets-for-users and only when the user is assigned to a host.
secrets = {
megacronPW = { };
megacronPW.neededForUsers = true;
msmtp-host = { };
msmtp-address = { };
msmtp-password = { };
# extract to default pam-u2f authfile location for passwordless sudo. see ../optional/yubikey
"yubico/u2f_keys" = {
path = "/home/ta/.config/Yubico/u2f_keys";
};
};
};
}
# configuration.nix
users.mutableUsers = false; # allow pw to be set during build process
users.users.username = {
hashedPasswordFile = config.sops.secrets.username.path;
openssh.authorizedKeys = [
(builtins.readFile ./secrets/id_blackout.pub)
];
};
Troubleshooting
sometimes rebuild may fail to start sops service.
Try runnning command with a--refresh
flag
If that fails, then try
# afterward run without --refresh
systemctl --user reset-failed