Encrypting secrets #
Secrets such as API keys and passwords should never be stored in plain text. Instead, we can use sops-nix to import this sensitive data from encrypted files. This negates the need to omit certain files from public repositories and reduces the risk of leaks due to human error.
Prerequisites #
Generating age key #
In order to encrypt and decrypt secret files, we must first tell sops the location of our age file. This can be generated from an SSH key using the ssh-to-age package. In this case, we use separate keys for root and user secrets:
# Generate user age
mkdir -p ~/.config/sops/age
ssh-to-age -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txt
# Generate root age
mkdir -p /root/.config/sops/age
ssh-to-age -private-key -i /root/.ssh/id_ed25519 > /root/.config/sops/age/keys.txtCreating secret files #
Once age keys are generated for both root and user secrets, we can use sops to generate secret files. For simple importing, we can reuse our username and hostname config options for these filenames:
# Create user secrets
sops edit ./secrets/<username>.yaml
# Create root secrets
sops edit ./secrets/<hostname>.yamlImporting secrets in our nix config #
Before using secrets, we must add a configuration module to setup sops-nix for both nixos (root secrets) and home-manager (user-secrets):
{
inputs,
...
}:
{
flake.modules.nixos.secrets =
{
config,
lib,
pkgs,
...
}:
{
options = {
secrets.enable = lib.mkEnableOption "importing secrets using sops-nix";
secrets.passwords.enable = lib.mkEnableOption "user password secrets";
};
config = lib.mkIf config.secrets.enable {
environment.systemPackages = [
pkgs.sops
pkgs.ssh-to-age
];
sops = {
defaultSopsFile = lib.mkDefault ../../secrets/${config.networking.hostName}.yaml;
age.sshKeyPaths =
if config.impermanence.enable then
lib.mkDefault [ "/persist/root/.ssh/id_ed25519" ]
else
lib.mkDefault [ "/root/.ssh/id_ed25519" ];
};
};
};
flake.modules.homeManager.secrets =
{
config,
lib,
pkgs,
...
}:
{
options = {
secrets.enable = lib.mkEnableOption "importing secrets using sops-nix";
};
config = lib.mkIf config.secrets.enable {
sops = {
defaultSopsFile = ../../secrets/${config.home.username}.yaml;
age.sshKeyPaths = [ "/home/${config.home.username}/.ssh/id_ed25519" ];
};
};
};
}Then we can import root secrets such as user passwords like:
(lib.mkIf (config.secrets.enable && config.secrets.passwords.enable) {
sops.secrets."passwords/${username}".neededForUsers = true;
users.users.${username} = {
initialPassword = null;
hashedPasswordFile = config.sops.secrets."passwords/${username}".path;
};
})We can import user secrets such as license keys and create template files like:
(lib.mkIf (config.vuescan.enable && config.secrets.enable && config.secrets.vuescan.enable) {
sops = {
secrets = {
"vuescan/user_id" = { };
"vuescan/email_address" = { };
"vuescan/customer_number" = { };
"vuescan/serial_number" = { };
};
templates.".vuescanrc".content = ''
UserID=${config.sops.placeholder."vuescan/user_id"}
SerialNumber=${config.sops.placeholder."vuescan/serial_number"}
CustomerNumber=${config.sops.placeholder."vuescan/customer_number"}
EmailAddress=${config.sops.placeholder."vuescan/email_address"}
'';
};
home.activation."vuescanrc" = ''
ln -sf ${config.sops.templates.".vuescanrc".path} ~/.vuescanrc
'';
})