Yet Another Secrets Management question :)

Hi,

I’m curious about how you folks manage secrets with NixOS.

I want to make my NixOS flake public, but before doing so, I must find a way to encrypt a few fields in my Syncthing configuration.

I tried sops-nix (which works beautifully), but because the flake can’t access files outside its parent directory, I cannot reference the decoded secrets file from my Syncthing configuration.

Before exploring other options listed at Comparison of secret managing schemes - NixOS Wiki, I’d love to hear about your experiences and what ultimately led you to choose one of those options over the others.

:vulcan_salute:

I’m using agenix paired with double flake pattern. The flake with secrets exports them as modules and is hosted on my private gitea instance, but it could be sourced from a file system. The public flake is on github, and individual hosts/services pull in secrets when needed.

#ty @VTimofeenko
I have seen this some times and always wanted to see the(/a initial) private repo to understand this mechanism.
Are you maybe possible to share some info, plz?

KR

My flake setup is:

~/nixos ->
    nixos ->
        home-manager/
        hosts/
        flake.nix
    nixos-secrets ->
        host1/
        host2/
        ...

In flake.nix, one of the inputs is:

my-secrets.url = "/home/firecat53/nixos/nixos-secrets";
my-secrets.flake = false;

Then in a sops.nix file:

{
  config,
  inputs,
  ...
}:
let
  secretspath = builtins.toString inputs.my-secrets;
in
  {
  # Sops-nix
  sops = {
    age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
    defaultSopsFile = "${secretspath}/${config.networking.hostName}/secrets.yaml";
  };
}

This allows use of the sops secrets from a repository that is outside of the main flake repository (~/nixos/nixos-secrets). Note: this is not my work, but was figured out from various sources here and online!

Hope that helps!

1 Like

My private config is built on top of a bespoke nickel-based config solution that serves as the source of truth for my entire homelab.

Essentially it goes like this:

Data objects (hosts, services, secrets, networks, etc.) are described in nickel using raw-harmonized-public layers (a pattern common in OLAP, my $DAYJOB). The layers do various checks, enrich the config by cross-referencing objects, generate IP addresses, etc.

The compiled nickel project is turned into a nix module that adds proper paths to secrets (as only the nix flake is path-aware, nickel does not have those types). This is all wrapped in a custom Nix module called “data” that’s imported on all hosts through some wrappers around deploy-rs. The connection to host object is necessary since agenix is using the hosts’ SSH keys to encrypt the secret materials.

An example public module that consumes this data is gitea. Gitea-the-service uses some values from networks and the service and nginx uses the secrets for SSL stuff. The API looks like a mess right now since I am migrating from that custom module to a dynamically generated lib that is expanded with custom functions.

1 Like

I use sops-nix as well, and am not quite sure why not being able to reference paths outside of the flake dir is a problem. I understand that one should be using config.sops.secrets.<name>.path, no?

Anyway, I could not find a way to get a password from a file in Syncthing directly, so I use the snippet below

  systemd.services.syncthing.serviceConfig.ExecStartPre =
    let
      cfg = config.services.syncthing;
      syncthing-set-password = pkgs.writeShellScriptBin "syncthing-set-password" ''
        PASSWORD="$(cat ${config.sops.secrets.syncthingPassword.path})"
        ${cfg.package}/bin/syncthing generate \
          --config=${cfg.configDir} \
          --gui-user=${cfg.user} \
          --gui-password="$PASSWORD" \
          ${lib.escapeShellArgs cfg.extraFlags}
      '';
    in
    if (cfg.cert != null || cfg.key != null) then
      abort "Cert or key manually set for syncthing, so overriding execstart pre is not safe! Compare with ExecStartPre in nixpkgs to see what needs to be done."
    else
      lib.mkForce "${syncthing-set-password}/bin/syncthing-set-password";

Note that I am overwriting the ExecPreStart in nixpkgs

1 Like

Oh no, I didn’t know about Nickel! You’ve pointed us to a (new to me) rabbit hole that I now need to do some spelunking in!

Thanks for your reply!

and am not quite sure why not being able to reference paths outside of the flake dir is a problem.

config.sops.secrets.<name>.path

I was trying to hack something around builtins.readFile in my Syncthing configuration file to read the fields from the secrets file to populate the configuration at build time; that approach may work if treating the flake as impure, which Is a path I’m not taking.

Working with systemd services is an option I considered since it solves the login credentials issue (thanks for sharing yours). However, the sync hosts’ ID would remain exposed :pensive:

Fwiw, according to the developer, it’s not necessary to keep device IDs private.

Fair enough, the developer is taxative about this.

clan provides a nice interface for sops-nix: Secrets & Facts - Clan Documentation