Sops-nix
Atomic, declarative, and reproducible secret provisioning for NixOS based on sops.
Secrets are decrypted from sops files during activation time. The secrets are stored as one secret per file and access-controlled by full declarative configuration of their users, permissions, and groups. GPG keys or age keys can be used for decryption, and compatibility shims are supported to enable the use of SSH RSA or SSH Ed25519 keys.
Usage
Add sops-nix to your flake inputs and use it in a system configuration:
{
inputs.sops-nix.url = "github:Mic92/sops-nix";
inputs.sops-nix.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, nixpkgs, sops-nix }: {
# change `yourhostname` to your actual hostname
nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
# customize to your system
system = "x86_64-linux";
modules = [
./configuration.nix
sops-nix.nixosModules.sops
];
};
};
}Secret Management
You manage the secrets like you would in sops by declaring a .sops.yaml with creation rules and encrypted secret files.
To derive an age key from SSH host key:
nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'For easier key management, you can seperate the secret files in their respective directories, use ssh host keys for decryption and reference them like:
# .sops.yaml
keys:
- &root_key age1...
- hosts:
- &somehost age1...
creation_rules:
- path_regex: '\.secrets\/somehost\/.*(secrets?(\.yml)?)$'
encrypted_regex: '.*'
key_groups:
- age:
- *root_key
- *somehost
- path_regex: 'secrets?(\.yml)?'
encrypted_regex: '.*'
key_groups:
- age:
- *root_key# Encrypt a binary file
sops -e <file> > <encrypted.file>
# Edit a secret file
sops secrets.yml
# Update Keys
sops updatekeys <file>Configuration
You first configure sops-nix:
{
# This will add secrets.yml to the nix store
# You can avoid this by adding a string to the full path instead, i.e.
# sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
sops.defaultSopsFile = ./secrets/example.yaml;
# This will automatically import SSH keys as age keys
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
# This is using an age key that is expected to already be in the filesystem
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
# This will generate a new key if the key specified above does not exist
sops.age.generateKey = true;
}Using Secrets
To use secrets use the following configuration options:
# This is the actual specification of the secrets.
sops.secrets.example-key = {};
# The path here corrosponds with the fields path in the yml.
# ex. myservice/my_subdir/my_secret comes from myservice.my_subdir.my_secret and will be mounted at /run/secrets/myservice/my_subdir/my_secret
sops.secrets."myservice/my_subdir/my_secret" = {};
# More options
sops.secrets.example-secret {
# File Permissions (Mode)
mode = "0440";
# Owner + Group
owner = config.users.users.nobody.name;
group = config.users.users.nobody.group;
# Restart systemd units when secret changes
restartUnits = [ "home-assistant.service" ];
# Symlink to other directories
path = "/var/lib/hass/secrets.yaml";
# Overwrite secret source per secret
sopsFile = ./other-secrets.json;
# Format (can be "json", "yml" or "binary" for placing binary secrets)
format = "json";
# Select a different key from secret source
key = "";
}
# Setting a user password
# This needs to happen before user setup. Generate a password with "echo 'pass' | mkpasswd -s"
sops.secrets.my-password.neededForUsers = true;
users.users.mic92 = {
isNormalUser = true;
hashedPasswordFile = config.sops.secrets.my-password.path;
};