diff --git a/.gitignore b/.gitignore index 4bd922a..5598d80 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -secrets/ +/secrets/ diff --git a/modules/modules.nix b/modules/modules.nix index 9509e5b..27c67c6 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -23,6 +23,7 @@ in { ./recurring-donations.nix ./hardware-wallets.nix ./lnd.nix + ./secrets/setup-secrets.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/secrets/setup-secrets.nix b/modules/secrets/setup-secrets.nix new file mode 100644 index 0000000..13393df --- /dev/null +++ b/modules/secrets/setup-secrets.nix @@ -0,0 +1,94 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + secretsDir = "/secrets/"; # TODO: make this an option + + secrets = (import ./make-secrets.nix { inherit config; }).activeSecrets; + + setupSecrets = concatStrings (mapAttrsToList (n: v: '' + setupSecret ${n} ${v.user} ${v.group} ${v.permissions} ${optionalString (v.keyFile != null) (baseNameOf v.keyFile)} + '') secrets); +in +{ + options.nix-bitcoin.setup-secrets = mkEnableOption "Set permissions for secrets generated by 'generate-secrets.sh'"; + + config = mkIf config.nix-bitcoin.setup-secrets { + systemd.targets.nix-bitcoin-secrets = { + requires = [ "setup-secrets.service" ]; + after = [ "setup-secrets.service" ]; + }; + + # Operation of this service: + # - Create missing secrets that are composed of attrs from secrets.nix + # - Set owner and permissions for all used secrets + # - Make all other secrets accessible to root only + # For all steps make sure that no secrets are copied to the nix-store. + # + systemd.services.setup-secrets = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + } // config.nix-bitcoin-services.defaultHardening; + script = '' + setupSecret() { + file="$1" + user="$2" + group="$3" + permissions="$4" + srcFile="$5" + if [[ ! -e $file ]]; then + if [[ $srcFile ]]; then + if [[ ! -e $srcFile ]]; then + echo "Error: Secret source file '$srcFile' is missing" + exit 1 + fi + mv "$srcFile" "$file" + else + createFile "$file" + fi + fi + chown "$user:$group" "$file" + chmod "$permissions" "$file" + processedFiles+=("$file") + } + + createFile() { + file="$1" + # 'nix eval' requires filesystem or daemon access to a store even if nothing is built. + # Use a private store so that 'nix eval' always succeeds regardless of the + # execution environment, like a container. + + # This tmp dir is automatically removed by systemd via PrivateTmp + [[ $store ]] || store="$(mktemp -d)" + secretsFile="$(realpath secrets.nix)" \ + ${pkgs.nix}/bin/nix eval --raw --store "$store" "( + (import ${./make-secrets.nix} { + secretsFile = builtins.getEnv \"secretsFile\"; + }).allSecrets.$file.text + )" > "$file" + } + + dir="${secretsDir}" + if [[ ! -e $dir ]]; then + echo "Error: Secrets dir '$dir' is missing" + exit 1 + fi + chown root: "$dir" + cd "$dir" + + processedFiles=() + ${setupSecrets} + + # Make all other files accessible to root only + unprocessedFiles=$(comm -23 <(printf '%s\n' *) <(printf '%s\n' "''${processedFiles[@]}" | sort)) + IFS=$'\n' + chown root: $unprocessedFiles + chmod 0440 $unprocessedFiles + + # Now make the secrets dir accessible to other users + chmod 0751 "$dir" + ''; + }; + }; +}