Files
nix-config/secrets
2023-12-28 21:45:45 +08:00
..
2023-12-20 15:37:33 +08:00

Secrets Management

All my secrets are safely encrypted via agenix, and stored in a separate private GitHub repository and referenced as a flake input in this flake.

In this way, all secrets is still encrypted when transmitted over the network and written to /nix/store, they are decrypted only when they are finally used.

In addition, we further improve the security of secrets files by storing them in a separate private repository.

This directory contains this README.md, and a nixos.nix/darwin.nix that used to decrypt all my secrets via agenix, and then I can use them in this flake.

Adding or Updating Secrets

All the operations in this section should be performed in my private repository: nix-secrets.

This task is accomplished using the agenix CLI tool with the ./secrets.nix file, so you need to have it installed first:

To use agenix temporarily, run:

nix shell nixpkgs#agenix

Suppose you want to add a new secret file xxx.age. Follow these steps:

  1. Navigate to your private nix-secrets repository.
  2. Edit secrets.nix and add a new entry for xxx.age, defining the encryption keys and the secret file path, for example:
# This file is not imported into your NixOS configuration. It is only used for the agenix CLI.
# agenix use the public keys defined in this file to encrypt the secrets.
# and users can decrypt the secrets by any of the corresponding private keys.

let
  # get user's ssh public key by command:
  #     cat ~/.ssh/id_ed25519.pub
  # if you do not have one, you can generate it by command:
  #     ssh-keygen -t ed25519
  ryan = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJx3Sk20pLL1b2PPKZey2oTyioODrErq83xG78YpFBoj";
  users = [ ryan ];

  # get system's ssh public key by command:
  #    cat /etc/ssh/ssh_host_ed25519_key.pub
  ai = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGeXNCazqiqxn8TmbCRjA+pLWrxwenn+CFhizBMP6en root@ai";
  systems = [ ai ];
in
{
  "./xxx.age".publicKeys = users ++ systems;
}
  1. Create and edit the secret file xxx.age interactively using the following command:
agenix -e ./xxx.age

Alternatively, you can encrypt an existing file to xxx.age using the following command:

cat /path/to/xxx | agenix -e ./xxx.age

agenix will encrypt the file with all the public keys we defined in secrets.nix, so all the users and systems defined in secrets.nix can decrypt it with their private keys.

Deploying Secrets

All the operations in this section should be performed in this repository.

First, add your own private nix-secrets repository and agenix as a flake input, and pass them to sub modules via specialArgs:

{
  inputs = {
    # ......

    # secrets management, lock with git commit at 2023/5/15
    agenix.url = "github:ryantm/agenix/db5637d10f797bb251b94ef9040b237f4702cde3";

    # my private secrets, it's a private repository, you need to replace it with your own.
    mysecrets = { url = "github:ryan4yin/nix-secrets"; flake = false; };
  };
  
  outputs = inputs@{ self, nixpkgs, ... }: {
    nixosConfigurations = {
      nixos-test = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";

        # Set all input parameters as specialArgs of all sub-modules
        # so that we can use the `agenix` & `mysecrets` in sub-modules
        specialArgs = inputs;
        modules = [
          # ......

          # import & decrypt secrets in `mysecrets` in this module
          ./secrets/default.nix
        ];
      };
    };
  };
}

Then, create ./secrets/default.nix with the following content:

# import & decrypt secrets in `mysecrets` in this module
{ config, pkgs, agenix, mysecrets, ... }:

{
  imports = [
     agenix.nixosModules.default
  ];

  # if you changed this key, you need to regenerate all encrypt files from the decrypt contents!
  age.identityPaths = [ 
    "/home/ryan/.ssh/juliet-age"
  ];

  age.secrets."xxx" = {
    # whether secrets are symlinked to age.secrets.<name>.path
    symlink = true;
    # target path for decrypted file
    path = "/etc/xxx/";
    # encrypted file path
    file =  "${mysecrets}/xxx.age";  # refer to ./xxx.age located in `mysecrets` repo
    mode = "0400";
    owner = "root";
    group = "root";
  };
}

From now on, every time you run nixos-rebuild switch, it will decrypt the secrets using the private keys defined in age.identityPaths. It will then symlink the secrets to the path defined by the age.secrets.<name>.path argument, which defaults to /etc/secrets.

NOTE: By default, age.identityPaths is set to ~/.ssh/id_ed25519 and ~/.ssh/id_rsa, so make sure to place your decryption keys there. If you're deploying to the same machine from which you encrypted the secrets, it should work out of the box.

Other Replacements

  • ragenix: A Rust reimplementation of agenix.
    • agenix is mainly written in bash, and it's error message is quite obscure, a little typo may cause some errors no one can understand.
    • with a type-safe language like Rust, we can get a better error message and less bugs.