diff --git a/helper/makeShell.nix b/helper/makeShell.nix index efe3c81..0fbd24b 100644 --- a/helper/makeShell.nix +++ b/helper/makeShell.nix @@ -21,12 +21,22 @@ stdenv.mkDerivation rec { ${toString ./fetch-release} } - krops-deploy() { + generate-secrets() {( + set -euo pipefail + genSecrets=$(nix-build --no-out-link -I nixos-config="${cfgDir}/configuration.nix" \ + '' -A config.nix-bitcoin.generateSecretsScript) + mkdir -p "${cfgDir}/secrets" + (cd "${cfgDir}/secrets"; $genSecrets) + )} + + krops-deploy() {( + set -euo pipefail + generate-secrets # Ensure strict permissions on secrets/ directory before rsyncing it to # the target machine chmod 700 "${cfgDir}/secrets" $(nix-build --no-out-link "${cfgDir}/krops/deploy.nix") - } + )} # Print logo if # 1. stdout is a TTY, i.e. we're not piping the output diff --git a/modules/backups.nix b/modules/backups.nix index 2425214..7a95eb3 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -98,6 +98,12 @@ in { }; nix-bitcoin.secrets.backup-encryption-env.user = "root"; + nix-bitcoin.generateSecretsCmds.backups = '' + makePasswordSecret backup-encryption-password + if [[ backup-encryption-password -nt backup-encryption-env ]]; then + echo "PASSPHRASE=$(cat backup-encryption-password)" > backup-encryption-env + fi + ''; services.backups.postgresqlDatabases = mkIf config.services.btcpayserver.enable [ "btcpaydb" ]; }; diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index e2365ab..117f4d9 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -394,15 +394,22 @@ in { }; users.groups.${cfg.group} = {}; users.groups.bitcoinrpc-public = {}; + nix-bitcoin.operator.groups = [ cfg.group ]; - nix-bitcoin.secrets.bitcoin-rpcpassword-privileged.user = cfg.user; - nix-bitcoin.secrets.bitcoin-rpcpassword-public = { - user = cfg.user; - group = "bitcoinrpc-public"; - }; + nix-bitcoin.secrets = { + bitcoin-rpcpassword-privileged.user = cfg.user; + bitcoin-rpcpassword-public = { + user = cfg.user; + group = "bitcoinrpc-public"; + }; - nix-bitcoin.secrets.bitcoin-HMAC-privileged.user = cfg.user; - nix-bitcoin.secrets.bitcoin-HMAC-public.user = cfg.user; + bitcoin-HMAC-privileged.user = cfg.user; + bitcoin-HMAC-public.user = cfg.user; + }; + nix-bitcoin.generateSecretsCmds.bitcoind = '' + makeBitcoinRPCPassword privileged + makeBitcoinRPCPassword public + ''; }; } diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index 0e5d9e6..f38b6ee 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -253,5 +253,8 @@ in { }; bitcoin-HMAC-btcpayserver.user = cfg.bitcoind.user; }; + nix-bitcoin.generateSecretsCmds.btcpayserver = '' + makeBitcoinRPCPassword btcpayserver + ''; }; } diff --git a/modules/joinmarket-ob-watcher.nix b/modules/joinmarket-ob-watcher.nix index 237ddb9..4b9714b 100644 --- a/modules/joinmarket-ob-watcher.nix +++ b/modules/joinmarket-ob-watcher.nix @@ -131,5 +131,8 @@ in { bitcoin-rpcpassword-joinmarket-ob-watcher.user = cfg.user; bitcoin-HMAC-joinmarket-ob-watcher.user = bitcoind.user; }; + nix-bitcoin.generateSecretsCmds.joinmarket-ob-watcher = '' + makeBitcoinRPCPassword joinmarket-ob-watcher + ''; }; } diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index 59fd2d0..a5aacc6 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -305,6 +305,9 @@ in { }; nix-bitcoin.secrets.jm-wallet-password.user = cfg.user; + nix-bitcoin.generateSecretsCmds.joinmarket = '' + makePasswordSecret jm-wallet-password + ''; } (mkIf cfg.yieldgenerator.enable { diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index d6e6a65..530b138 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -112,5 +112,8 @@ in { loop-key.user = lnd.user; loop-cert.user = lnd.user; }; + nix-bitcoin.generateSecretsCmds.lightning-loop = '' + makeCert loop '${optionalString (cfg.rpcAddress != "localhost") "IP:${cfg.rpcAddress}"}' + ''; }; } diff --git a/modules/liquid.nix b/modules/liquid.nix index 7804ba0..6fe7d14 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -247,5 +247,8 @@ in { nix-bitcoin.operator.groups = [ cfg.group ]; nix-bitcoin.secrets.liquid-rpcpassword.user = cfg.user; + nix-bitcoin.generateSecretsCmds.liquid = '' + makePasswordSecret liquid-rpcpassword + ''; }; } diff --git a/modules/lnd.nix b/modules/lnd.nix index 7dec2d2..5c7b5ec 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -293,7 +293,14 @@ in { lnd-wallet-password.user = cfg.user; lnd-key.user = cfg.user; lnd-cert.user = cfg.user; - lnd-cert.permissions = "0444"; # world readable + lnd-cert.permissions = "444"; # world readable }; + # Advantages of manually pre-generating certs: + # - Reduces dynamic state + # - Enables deployment of a mesh of server plus client nodes with predefined certs + nix-bitcoin.generateSecretsCmds.lnd = '' + makePasswordSecret lnd-wallet-password + makeCert lnd '${optionalString (cfg.rpcAddress != "localhost") "IP:${cfg.rpcAddress}"}' + ''; }; } diff --git a/modules/secrets/secrets.nix b/modules/secrets/secrets.nix index 7618949..c44ee29 100644 --- a/modules/secrets/secrets.nix +++ b/modules/secrets/secrets.nix @@ -2,9 +2,6 @@ with lib; let - cfg = config.nix-bitcoin; -in -{ options.nix-bitcoin = { secretsDir = mkOption { type = types.path; @@ -29,6 +26,14 @@ in ''; }; + generateSecretsCmds = mkOption { + type = types.attrsOf types.str; + default = {}; + description = '' + Bash expressions for generating secrets. + ''; + }; + # Currently, this is used only by ../deployment/nixops.nix deployment.secretsDir = mkOption { type = types.path; @@ -72,8 +77,68 @@ in - Set `nix-bitcoin.secretsSetupMethod = "manual"` if you want to manually setup secrets ''; }; + + generateSecretsScript = mkOption { + internal = true; + default = let + rpcauthSrc = builtins.fetchurl { + url = "https://raw.githubusercontent.com/bitcoin/bitcoin/d6cde007db9d3e6ee93bd98a9bbfdce9bfa9b15b/share/rpcauth/rpcauth.py"; + sha256 = "189mpplam6yzizssrgiyv70c9899ggh8cac76j4n7v0xqzfip07n"; + }; + rpcauth = pkgs.writers.writeBash "rpcauth" '' + exec ${pkgs.python3}/bin/python ${rpcauthSrc} "$@" + ''; + in pkgs.writers.writeBash "generate-secrets" '' + set -euo pipefail + + export PATH=${lib.makeBinPath (with pkgs; [ coreutils gnugrep ])} + + makePasswordSecret() { + # Passwords have alphabet {a-z, A-Z, 0-9} and ~119 bits of entropy + [[ -e $1 ]] || ${pkgs.pwgen}/bin/pwgen -s 20 1 > "$1" + } + makeBitcoinRPCPassword() { + user=$1 + file=bitcoin-rpcpassword-$user + HMACfile=bitcoin-HMAC-$user + makePasswordSecret "$file" + if [[ $file -nt $HMACfile ]]; then + ${rpcauth} $user $(cat "$file") | grep rpcauth | cut -d ':' -f 2 > "$HMACfile" + fi + } + makeCert() { + name=$1 + # Add leading comma if not empty + extraAltNames=''${2:+,}''${2:-} + if [[ ! -e $name-key ]]; then + # Create new key and cert + doMakeCert "-newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout $name-key" + elif [[ ! -e $name-cert \ + || $(cat "$name-cert-alt-names" 2>/dev/null) != $extraAltNames ]]; then + # Create cert from existing key + doMakeCert "-key $name-key" + fi; + } + doMakeCert() { + # This fn uses global variables `name` and `extraAltNames` + keyOpts=$1 + ${pkgs.openssl}/bin/openssl req -x509 \ + -sha256 -days 3650 $keyOpts -out "$name-cert" \ + -subj "/CN=localhost/O=$name" \ + -addext "subjectAltName=DNS:localhost,IP:127.0.0.1$extraAltNames" + echo "$extraAltNames" > "$name-cert-alt-names" + } + + umask u=rw,go= + ${builtins.concatStringsSep "\n" (builtins.attrValues cfg.generateSecretsCmds)} + ''; + }; }; + cfg = config.nix-bitcoin; +in { + inherit options; + config = { # This target is active when secrets have been setup successfully. systemd.targets.nix-bitcoin-secrets = mkIf (cfg.secretsSetupMethod != "manual") { @@ -106,7 +171,7 @@ in cd "${cfg.secretsDir}" chown root: . chmod 0700 . - ${cfg.pkgs.generate-secrets} + ${cfg.generateSecretsScript} ''} setupSecret() { @@ -140,9 +205,11 @@ in # 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 + if [[ $unprocessedFiles ]]; then + IFS=$'\n' + chown root: $unprocessedFiles + chmod 0440 $unprocessedFiles + fi # Now make the secrets dir accessible to other users chmod 0751 "$dir" diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index ca307ce..bdefea8 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -83,6 +83,13 @@ in { } // nbLib.allowedIPAddresses cfg.enforceTor // nbLib.nodejs; }; + nix-bitcoin.secrets.spark-wallet-login.user = cfg.user; + nix-bitcoin.generateSecretsCmds.spark-wallet = '' + makePasswordSecret spark-wallet-password + if [[ spark-wallet-password -nt spark-wallet-login ]]; then + echo "login=spark-wallet:$(cat spark-wallet-password)" > spark-wallet-login + fi + ''; }; } diff --git a/pkgs/generate-secrets/default.nix b/pkgs/generate-secrets/default.nix deleted file mode 100644 index d04f06a..0000000 --- a/pkgs/generate-secrets/default.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ pkgs }: with pkgs; - -let - rpcauthSrc = builtins.fetchurl { - url = "https://raw.githubusercontent.com/bitcoin/bitcoin/d6cde007db9d3e6ee93bd98a9bbfdce9bfa9b15b/share/rpcauth/rpcauth.py"; - sha256 = "189mpplam6yzizssrgiyv70c9899ggh8cac76j4n7v0xqzfip07n"; - }; - rpcauth = pkgs.writeScriptBin "rpcauth" '' - exec ${pkgs.python3}/bin/python ${rpcauthSrc} "$@" - ''; -in -writers.writeBash "generate-secrets" '' - export PATH=${lib.makeBinPath [ coreutils pwgen openssl gnugrep rpcauth ]} - . ${./generate-secrets.sh} ${./openssl.cnf} -'' diff --git a/pkgs/generate-secrets/generate-secrets.sh b/pkgs/generate-secrets/generate-secrets.sh deleted file mode 100755 index 6def36b..0000000 --- a/pkgs/generate-secrets/generate-secrets.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -opensslConf=${1:-openssl.cnf} - -makePasswordSecret() { - # Passwords have alphabet {a-z, A-Z, 0-9} and ~119 bits of entropy - [[ -e $1 ]] || pwgen -s 20 1 > "$1" -} -makeHMAC() { - user=$1 - rpcauth $user $(cat bitcoin-rpcpassword-$user) | grep rpcauth | cut -d ':' -f 2 > bitcoin-HMAC-$user -} - -makePasswordSecret bitcoin-rpcpassword-privileged -makePasswordSecret bitcoin-rpcpassword-btcpayserver -makePasswordSecret bitcoin-rpcpassword-joinmarket-ob-watcher -makePasswordSecret bitcoin-rpcpassword-public -makePasswordSecret lnd-wallet-password -makePasswordSecret liquid-rpcpassword -makePasswordSecret spark-wallet-password -makePasswordSecret backup-encryption-password -makePasswordSecret jm-wallet-password - -[[ -e bitcoin-HMAC-privileged ]] || makeHMAC privileged -[[ -e bitcoin-HMAC-public ]] || makeHMAC public -[[ -e bitcoin-HMAC-btcpayserver ]] || makeHMAC btcpayserver -[[ -e bitcoin-HMAC-joinmarket-ob-watcher ]] || makeHMAC joinmarket-ob-watcher -[[ -e spark-wallet-login ]] || echo "login=spark-wallet:$(cat spark-wallet-password)" > spark-wallet-login -[[ -e backup-encryption-env ]] || echo "PASSPHRASE=$(cat backup-encryption-password)" > backup-encryption-env - -makeCert() { - if [[ ! -e $name-key || ! -e $name-cert ]]; then - openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ - -sha256 -days 3650 -nodes -keyout "$name-key" -out "$name-cert" \ - -subj "/CN=localhost/O=$name" \ - -addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:169.254.1.14,IP:169.254.1.22" - # TODO: Remove hardcoded lnd, loopd netns ips - fi -} - -makeCert lnd -makeCert loop diff --git a/pkgs/generate-secrets/update-and-generate.nix b/pkgs/generate-secrets/update-and-generate.nix deleted file mode 100644 index c2cfae9..0000000 --- a/pkgs/generate-secrets/update-and-generate.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ pkgs }: with pkgs; - -let - generate-secrets = callPackage ./. {}; -in -writeScript "make-secrets" '' - # Update from old secrets format - [[ -e secrets.nix ]] && . ${./update-secrets.sh} - ${generate-secrets} -'' diff --git a/pkgs/generate-secrets/update-secrets.sh b/pkgs/generate-secrets/update-secrets.sh deleted file mode 100644 index 6d9ed10..0000000 --- a/pkgs/generate-secrets/update-secrets.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -set -eo pipefail - -# Update secrets from the old format to the current one where each secret -# has a local source file. - -reportError() { - echo "Updating secrets failed. (Error in line $1)" - echo "The secret files have been moved to secrets/old-secrets" -} -trap 'reportError $LINENO' ERR - -echo "Updating old secrets to the current format." - -mkdir old-secrets -# move all files into old-secrets -shopt -s extglob dotglob -mv !(old-secrets) old-secrets -shopt -u dotglob - -secrets=$(cat old-secrets/secrets.nix) - -extractPassword() { - pwName="$1" - destFile="${2:-$pwName}" - echo "$secrets" | sed -nE "s/.*?$pwName = \"(.*?)\".*/\1/p" > "$destFile" -} - -rename() { - old="old-secrets/$1" - if [[ -e $old ]]; then - cp "$old" "$2" - fi -} - -extractPassword bitcoinrpcpassword bitcoin-rpcpassword -extractPassword lnd-wallet-password -extractPassword liquidrpcpassword liquid-rpcpassword -extractPassword spark-wallet-password - -rename lnd.key lnd-key -rename lnd.cert lnd-cert - -rm -r old-secrets