diff --git a/examples/shell.nix b/examples/shell.nix index 47b1e47..d378e67 100644 --- a/examples/shell.nix +++ b/examples/shell.nix @@ -1,53 +1,8 @@ let - # This is either a path to a local nix-bitcoin source or an attribute set to - # be used as the fetchurl argument. - nix-bitcoin-release = import ./nix-bitcoin-release.nix; - - nix-bitcoin-path = - if builtins.isAttrs nix-bitcoin-release then nix-bitcoin-unpacked - else nix-bitcoin-release; - - nixpkgs-path = (import "${toString nix-bitcoin-path}/pkgs/nixpkgs-pinned.nix").nixpkgs; - pkgs = import nixpkgs-path {}; - nix-bitcoin = pkgs.callPackage nix-bitcoin-path {}; - - nix-bitcoin-unpacked = (import {}).runCommand "nix-bitcoin-src" {} '' - mkdir $out; tar xf ${builtins.fetchurl nix-bitcoin-release} -C $out - ''; + nix-bitcoin = toString (import ./nix-bitcoin-release.nix); in -with pkgs; -stdenv.mkDerivation rec { - name = "nix-bitcoin-environment"; - - path = lib.makeBinPath [ nix-bitcoin.extra-container ]; - - shellHook = '' - export NIX_PATH="nixpkgs=${nixpkgs-path}:nix-bitcoin=${toString nix-bitcoin-path}:." - export PATH="${path}''${PATH:+:}$PATH" - - export NIX_BITCOIN_EXAMPLES_DIR="${toString ./.}" - - fetch-release() { - ${toString nix-bitcoin-path}/helper/fetch-release - } - - krops-deploy() { - # Ensure strict permissions on secrets/ directory before rsyncing it to - # the target machine - chmod 700 ${toString ./secrets} - $(nix-build --no-out-link ${toString ./krops/deploy.nix}) - } - - # Print logo if - # 1. stdout is a TTY, i.e. we're not piping the output - # 2. the shell is interactive - if [[ -t 1 && $- == *i* ]]; then - ${figlet}/bin/figlet "nix-bitcoin" - fi - - (mkdir -p secrets; cd secrets; env -i ${nix-bitcoin.generate-secrets}) - - # Don't run this hook when another nix-shell is run inside this shell - unset shellHook - ''; -} + import "${nix-bitcoin}/helper/makeShell.nix" { + configDir = ./.; + # Set this to modify your shell + # extraShellInitCmds = (pkgs: ''''); + } diff --git a/flake.nix b/flake.nix index 12828a1..302b612 100644 --- a/flake.nix +++ b/flake.nix @@ -62,7 +62,7 @@ nbPkgs = self.mkNbPkgs { inherit system pkgs; }; packages = flake-utils.lib.flattenTree (removeAttrs nbPkgs [ - "pinned" "modulesPkgs" "nixops19_09" "krops" + "pinned" "modulesPkgs" "nixops19_09" "krops" "generate-secrets" ]) // { runVM = mkVMScript packages.vm; diff --git a/helper/fetch-channel b/helper/fetch-channel deleted file mode 100755 index bdc64d1..0000000 --- a/helper/fetch-channel +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -rev=$1 -sha256=$(nix-prefetch-url --unpack https://github.com/nixos/nixpkgs/archive/$rev.tar.gz) -echo "rev = \"$rev\";" -echo "sha256 = \"$sha256\";" diff --git a/helper/fetch-release b/helper/fetch-release index 13513d8..2fe87ee 100755 --- a/helper/fetch-release +++ b/helper/fetch-release @@ -15,26 +15,28 @@ trap "rm -rf $TMPDIR" EXIT GPG_HOME=$TMPDIR/gpg-home mkdir -p -m 700 "$GPG_HOME" -cd $TMPDIR -baseUrl=https://github.com/$repo/releases/download/v$version -curl --silent -L -O $baseUrl/SHA256SUMS.txt -curl --silent -L -O $baseUrl/SHA256SUMS.txt.asc - # Import key gpg --homedir $GPG_HOME --import "$scriptDir/key-jonasnick.bin" &> /dev/null # Verify key fingerprint gpg --homedir $GPG_HOME --list-keys 36C71A37C9D988BDE82508D9B1A70E4F8DCD0366 > /dev/null -# Verify signature for SHA256SUMS.txt -gpg --homedir $GPG_HOME --verify SHA256SUMS.txt.asc &> /dev/null || { +# Fetch nar-hash of release +cd $TMPDIR +baseUrl=https://github.com/$repo/releases/download/v$version +curl --silent -L -O $baseUrl/nar-hash.txt +curl --silent -L -O $baseUrl/nar-hash.txt.asc + +# Verify signature for nar-hash +gpg --homedir $GPG_HOME --verify nar-hash.txt.asc &> /dev/null || { echo "Error: Signature verification failed. Please open an issue in the project repository." exit 1 } -sha256=$(cat SHA256SUMS.txt | cut -d\ -f1) +>&2 echo "Fetched and verified release $version" + cat < "${cfgDir}/nix-bitcoin-release.nix" + exec nix-shell + } + + 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) + )} + + deploy() {( + set -euo pipefail + krops-deploy & + kropsPid=$! + if eval-config; then + wait $kropsPid + else + # Kill all subprocesses + kill $(pidClosure $kropsPid) + return 1 + fi + )} + + 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") + )} + + eval-config() { + NIXOS_CONFIG="${cfgDir}/krops/krops-configuration.nix" nix eval --raw -f ${nixpkgs}/nixos system.outPath + echo + } + + pidClosure() { + echo "$1" + for pid in $(ps -o pid= --ppid "$1"); do + pidClosure "$pid" + done + } + + # Print welcome message if + # 1. stdout is a TTY, i.e. we're not piping the output + # 2. the shell is interactive + if [[ -t 1 && $- == *i* ]]; then + ${figlet}/bin/figlet "nix-bitcoin" + echo 'Enter "h" or "help" for documentation.' + fi + + # Don't run this hook when another nix-shell is run inside this shell + unset shellHook + + ${extraShellInitCmds pkgs} + ''; +} diff --git a/helper/push-release.sh b/helper/push-release.sh index 9058214..cbf93a9 100755 --- a/helper/push-release.sh +++ b/helper/push-release.sh @@ -3,14 +3,10 @@ set -euo pipefail REPO=fort-nix/nix-bitcoin BRANCH=master -OAUTH_TOKEN=$(pass show nix-bitcoin/github/oauth-token) +OAUTH_TOKEN= DRY_RUN= TAG_NAME= -if [[ ! $OAUTH_TOKEN ]]; then - echo "Please set OAUTH_TOKEN variable" -fi - for arg in "$@"; do case $arg in --dry-run|-n) @@ -26,18 +22,28 @@ if [[ ! $TAG_NAME ]]; then echo "$0 [--dry-run|-n] " exit fi -if [[ $DRY_RUN ]]; then echo "Dry run"; fi +if [[ $DRY_RUN ]]; then + echo "Dry run" +else + OAUTH_TOKEN=$(pass show nix-bitcoin/github/oauth-token) + if [[ ! $OAUTH_TOKEN ]]; then + echo "Please set OAUTH_TOKEN variable" + fi +fi RESPONSE=$(curl https://api.github.com/repos/$REPO/releases/latest 2> /dev/null) echo "Latest release" $(echo $RESPONSE | jq -r '.tag_name' | tail -c +2) -while true; do - read -p "Create release $TAG_NAME? [yn] " yn - case $yn in - [Yy]* ) break;; - [Nn]* ) exit;; - * ) echo "Please answer y or n.";; - esac -done + +if [[ ! $DRY_RUN ]]; then + while true; do + read -p "Create release $TAG_NAME? [yn] " yn + case $yn in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer y or n.";; + esac + done +fi TMPDIR=$(mktemp -d) if [[ ! $DRY_RUN ]]; then trap "rm -rf $TMPDIR" EXIT; fi @@ -53,6 +59,10 @@ SHA256SUMS=$TMPDIR/SHA256SUMS.txt (cd $TMPDIR; sha256sum $ARCHIVE_NAME > $SHA256SUMS) gpg -o $SHA256SUMS.asc -a --detach-sig $SHA256SUMS +cd $TMPDIR +nix hash to-sri --type sha256 $(nix-prefetch-url --unpack file://$ARCHIVE 2> /dev/null) > nar-hash.txt +gpg -o nar-hash.txt.asc -a --detach-sig nar-hash.txt + if [[ $DRY_RUN ]]; then echo "Created v$TAG_NAME in $TMPDIR" exit 0 @@ -71,6 +81,10 @@ post_asset() { curl -H "Authorization: token $OAUTH_TOKEN" --data-binary "@$1" -H "Content-Type: application/octet-stream" \ $GH_ASSET/$(basename $1) &> /dev/null } +post_asset nar-hash.txt +post_asset nar-hash.txt.asc +# Post additional assets for backwards compatibility. +# This allows older nix-bitcoin installations to upgrade via `fetch-release`. post_asset $ARCHIVE post_asset $SHA256SUMS post_asset $SHA256SUMS.asc diff --git a/modules/backups.nix b/modules/backups.nix index a7747c6..7a95eb3 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -18,17 +18,22 @@ let ${config.services.nbxplorer.dataDir} ${config.services.btcpayserver.dataDir} ${config.services.joinmarket.dataDir} - ${config.services.postgresqlBackup.location}/btcpaydb.sql.gz ${optionalString config.nix-bitcoin.generateSecrets "${config.nix-bitcoin.secretsDir}"} /var/lib/tor /var/lib/nixos + ${builtins.concatStringsSep "\n" postgresqlBackupPaths} + # Extra files ${cfg.extraFiles} # Exclude all unspecified files and directories - / ''; + + postgresqlBackupDir = config.services.postgresqlBackup.location; + postgresqlBackupPaths = map (db: "${postgresqlBackupDir}/${db}.sql.gz") cfg.postgresqlDatabases; + postgresqlBackupServices = map (db: "postgresqlBackup-${db}.service") cfg.postgresqlDatabases; in { options.services.backups = { enable = mkEnableOption "Backups service"; @@ -53,6 +58,11 @@ in { Run backup with the given frequency. If null, do not run automatically. ''; }; + postgresqlDatabases = mkOption { + type = types.listOf types.str; + default = []; + description = "List of database names to backup."; + }; extraFiles = mkOption { type = types.lines; default = ""; @@ -63,8 +73,7 @@ in { }; }; - config = mkIf cfg.enable (mkMerge [ - { + config = mkIf cfg.enable { environment.systemPackages = [ pkgs.duplicity ]; services.duplicity = { @@ -78,17 +87,24 @@ in { secretFile = "${config.nix-bitcoin.secretsDir}/backup-encryption-env"; }; - nix-bitcoin.secrets.backup-encryption-env.user = "root"; - } - (mkIf config.services.btcpayserver.enable { + systemd.services.duplicity = { + wants = postgresqlBackupServices; + after = postgresqlBackupServices; + }; + services.postgresqlBackup = { - enable = true; - databases = [ "btcpaydb" ]; + enable = mkIf (cfg.postgresqlDatabases != []) true; + databases = cfg.postgresqlDatabases; }; - systemd.services.duplicity = rec { - wants = [ "postgresqlBackup-btcpaydb.service" ]; - after = wants; - }; - }) - ]); + + 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 1e1f45f..f38b6ee 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -210,9 +210,9 @@ in { install -m 600 ${configFile} '${cfg.btcpayserver.dataDir}/settings.config' ${optionalString (cfg.btcpayserver.lightningBackend == "lnd") '' { - echo -n "${lndConfig}"; - ${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.nix-bitcoin.secretsDir}/lnd-cert \ - | sed -e 's/.*=//;s/://g'; + echo -n "${lndConfig}" + ${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.services.lnd.certPath} \ + | sed -e 's/.*=//;s/://g' } >> '${cfg.btcpayserver.dataDir}/settings.config' ''} ''; @@ -253,5 +253,8 @@ in { }; bitcoin-HMAC-btcpayserver.user = cfg.bitcoind.user; }; + nix-bitcoin.generateSecretsCmds.btcpayserver = '' + makeBitcoinRPCPassword btcpayserver + ''; }; } diff --git a/modules/charge-lnd.nix b/modules/charge-lnd.nix index b6cf8aa..c3d4580 100644 --- a/modules/charge-lnd.nix +++ b/modules/charge-lnd.nix @@ -105,7 +105,7 @@ in macaroonDir=${dataDir}/lnddir-proxy/data/chain/bitcoin/mainnet mkdir -p $macaroonDir ln -sf /run/lnd/charge-lnd.macaroon $macaroonDir - ln -sf ${config.nix-bitcoin.secretsDir}/lnd-cert ${dataDir}/lnddir-proxy/tls.cert + ln -sf ${lnd.certPath} ${dataDir}/lnddir-proxy/tls.cert ''; serviceConfig = nbLib.defaultHardening // { ExecStart = '' 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 18cce78..530b138 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -6,6 +6,9 @@ let cfg = config.services.lightning-loop; nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; + + lnd = config.services.lnd; + network = config.services.bitcoind.network; rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; configFile = builtins.toFile "loop.conf" '' @@ -17,9 +20,9 @@ let tlscertpath=${secretsDir}/loop-cert tlskeypath=${secretsDir}/loop-key - lnd.host=${config.services.lnd.rpcAddress}:${toString config.services.lnd.rpcPort} - lnd.macaroonpath=${config.services.lnd.networkDir}/admin.macaroon - lnd.tlspath=${secretsDir}/lnd-cert + lnd.host=${lnd.rpcAddress}:${toString lnd.rpcPort} + lnd.macaroonpath=${lnd.networkDir}/admin.macaroon + lnd.tlspath=${lnd.certPath} ${optionalString (cfg.proxy != null) "server.proxy=${cfg.proxy}"} @@ -89,7 +92,7 @@ in { environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ]; systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' 0770 ${config.services.lnd.user} ${config.services.lnd.group} - -" + "d '${cfg.dataDir}' 0770 ${lnd.user} ${lnd.group} - -" ]; systemd.services.lightning-loop = { @@ -98,7 +101,7 @@ in { after = [ "lnd.service" ]; serviceConfig = nbLib.defaultHardening // { ExecStart = "${cfg.package}/bin/loopd --configfile=${configFile}"; - User = config.services.lnd.user; + User = lnd.user; Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = cfg.dataDir; @@ -106,8 +109,11 @@ in { }; nix-bitcoin.secrets = { - loop-key.user = config.services.lnd.user; - loop-cert.user = config.services.lnd.user; + 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/lightning-pool.nix b/modules/lightning-pool.nix index 74ba9d5..7f49f3d 100644 --- a/modules/lightning-pool.nix +++ b/modules/lightning-pool.nix @@ -5,7 +5,9 @@ with lib; let cfg = config.services.lightning-pool; nbLib = config.nix-bitcoin.lib; - secretsDir = config.nix-bitcoin.secretsDir; + + lnd = config.services.lnd; + network = config.services.bitcoind.network; rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; configFile = builtins.toFile "pool.conf" '' @@ -13,9 +15,9 @@ let restlisten=${cfg.restAddress}:${toString cfg.restPort} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} - lnd.host=${config.services.lnd.rpcAddress}:${toString config.services.lnd.rpcPort} - lnd.macaroondir=${config.services.lnd.networkDir} - lnd.tlspath=${secretsDir}/lnd-cert + lnd.host=${lnd.rpcAddress}:${toString lnd.rpcPort} + lnd.macaroondir=${lnd.networkDir} + lnd.tlspath=${lnd.certPath} ${cfg.extraConfig} ''; 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-rest-onion-service.nix b/modules/lnd-rest-onion-service.nix index 1d873cc..344885e 100644 --- a/modules/lnd-rest-onion-service.nix +++ b/modules/lnd-rest-onion-service.nix @@ -5,7 +5,6 @@ with lib; let cfg = config.services.lnd.restOnionService; nbLib = config.nix-bitcoin.lib; - secretsDir = config.nix-bitcoin.secretsDir; runAsUser = config.nix-bitcoin.runAsUserCmd; lnd = config.services.lnd; @@ -17,7 +16,7 @@ let --host=$(cat ${config.nix-bitcoin.onionAddresses.dataDir}/lnd/lnd-rest) \ --port=${toString lnd.restPort} \ --lnddir=${lnd.dataDir} \ - --tlscertpath=${secretsDir}/lnd-cert "$@" + --tlscertpath=${lnd.certPath} "$@" ''; in { options.services.lnd.restOnionService = { diff --git a/modules/lnd.nix b/modules/lnd.nix index fcadc5c..5c7b5ec 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -14,7 +14,7 @@ let configFile = pkgs.writeText "lnd.conf" '' datadir=${cfg.dataDir} logdir=${cfg.dataDir}/logs - tlscertpath=${secretsDir}/lnd-cert + tlscertpath=${cfg.certPath} tlskeypath=${secretsDir}/lnd-key listen=${toString cfg.address}:${toString cfg.port} @@ -126,7 +126,7 @@ in { '' ${runAsUser} ${cfg.user} ${cfg.package}/bin/lncli \ --rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \ - --tlscertpath '${secretsDir}/lnd-cert' \ + --tlscertpath '${cfg.certPath}' \ --macaroonpath '${networkDir}/admin.macaroon' "$@" ''; description = "Binary to connect with the lnd instance."; @@ -149,6 +149,11 @@ in { default = cfg.user; description = "The group as which to run LND."; }; + certPath = mkOption { + readOnly = true; + default = "${secretsDir}/lnd-cert"; + description = "LND TLS certificate path."; + }; inherit (nbLib) enforceTor; }; @@ -211,7 +216,7 @@ in { # Retrying is necessary because it can happen that the lnd socket is # existing, but the RPC service isn't yet, which results in error # "waiting to start, RPC services not available". - curl = "${pkgs.curl}/bin/curl -s --show-error --retry 10 --cacert ${secretsDir}/lnd-cert"; + curl = "${pkgs.curl}/bin/curl -s --show-error --retry 10 --cacert ${cfg.certPath}"; restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1"; in [ (nbLib.script "lnd-create-wallet" '' @@ -288,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 6c7e0a9..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; @@ -24,8 +21,16 @@ in type = types.bool; default = false; description = '' - Automatically generate all required secrets. - Make sure to create a backup of the generated secrets. + Automatically generate all required secrets at system startup. + Note: Make sure to create a backup of the generated secrets. + ''; + }; + + generateSecretsCmds = mkOption { + type = types.attrsOf types.str; + default = {}; + description = '' + Bash expressions for generating secrets. ''; }; @@ -60,7 +65,6 @@ in }; secretsSetupMethod = mkOption { - internal = true; type = types.str; default = throw '' Error: No secrets setup method has been defined. @@ -73,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") { @@ -107,7 +171,7 @@ in cd "${cfg.secretsDir}" chown root: . chmod 0700 . - ${cfg.pkgs.generate-secrets} + ${cfg.generateSecretsScript} ''} setupSecret() { @@ -141,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/default.nix b/pkgs/default.nix index 1e41b01..39df452 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -9,7 +9,7 @@ let self = { spark-wallet = pkgs.callPackage ./spark-wallet { }; liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { }; joinmarket = pkgs.callPackage ./joinmarket { inherit (self) nbPython3Packages; }; - generate-secrets = pkgs.callPackage ./generate-secrets { }; + generate-secrets = import ./generate-secrets-deprecated.nix; nixops19_09 = pkgs.callPackage ./nixops { }; krops = import ./krops { }; netns-exec = pkgs.callPackage ./netns-exec { }; diff --git a/pkgs/generate-secrets-deprecated.nix b/pkgs/generate-secrets-deprecated.nix new file mode 100644 index 0000000..4bb491b --- /dev/null +++ b/pkgs/generate-secrets-deprecated.nix @@ -0,0 +1,14 @@ +throw '' + Please update the `shell.nix` of your node configuration. + + To update, do the following: + 1. Switch to the directory containing your `configuration.nix` and `shell.nix`. + 2. Run the following Bash expression (Warning: This overwrites your `shell.nix`): + + # Only update nix-bitcoin-release.nix if it contains a release hash + if grep -q sha256 nix-bitcoin-release.nix; then + ${toString ../helper/fetch-release} > nix-bitcoin-release.nix && cp ${toString ../examples/shell.nix} . + else + cp ${toString ../examples/shell.nix} . + 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 56d0f4a..0000000 --- a/pkgs/generate-secrets/generate-secrets.sh +++ /dev/null @@ -1,45 +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 - -if [[ ! -e lnd-key || ! -e lnd-cert ]]; then - openssl ecparam -genkey -name prime256v1 -out lnd-key - openssl req -config $opensslConf -new -sha256 -key lnd-key -out lnd.csr -subj '/CN=localhost/O=lnd' - openssl req -config $opensslConf -x509 -sha256 -days 1825 -key lnd-key -in lnd.csr -out lnd-cert - rm lnd.csr -fi - -if [[ ! -e loop-key || ! -e loop-cert ]]; then - openssl ecparam -genkey -name prime256v1 -out loop-key - openssl req -config $opensslConf -new -sha256 -key loop-key -out loop.csr -subj '/CN=localhost/O=loopd' - openssl req -config $opensslConf -x509 -sha256 -days 1825 -key loop-key -in loop.csr -out loop-cert - rm loop.csr -fi diff --git a/pkgs/generate-secrets/openssl.cnf b/pkgs/generate-secrets/openssl.cnf deleted file mode 100644 index efc6cb5..0000000 --- a/pkgs/generate-secrets/openssl.cnf +++ /dev/null @@ -1,36 +0,0 @@ -[ req ] -#default_bits = 2048 -#default_md = sha256 -#default_keyfile = privkey.pem -distinguished_name = req_distinguished_name -attributes = req_attributes -x509_extensions = v3_ca - -[ req_distinguished_name ] -countryName = Country Name (2 letter code) -countryName_min = 2 -countryName_max = 2 -stateOrProvinceName = State or Province Name (full name) -localityName = Locality Name (eg, city) -0.organizationName = Organization Name (eg, company) -organizationalUnitName = Organizational Unit Name (eg, section) -commonName = Common Name (eg, fully qualified host name) -commonName_max = 64 -emailAddress = Email Address -emailAddress_max = 64 - -[ req_attributes ] -challengePassword = A challenge password -challengePassword_min = 4 -challengePassword_max = 20 - -[ v3_ca ] -subjectAltName = @alt_names - -[ alt_names ] -IP.1 = 127.0.0.1 -DNS.1 = localhost -# TODO: Remove hardcoded lnd IP -IP.2 = 169.254.1.14 -# TODO: Remove hardcoded loopd IP -IP.3 = 169.254.1.22 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 diff --git a/pkgs/krops/get-sha256.sh b/pkgs/krops/get-sha256.sh index 91b4049..d7cd792 100755 --- a/pkgs/krops/get-sha256.sh +++ b/pkgs/krops/get-sha256.sh @@ -5,7 +5,7 @@ set -euo pipefail archive_hash () { repo=$1 rev=$2 - nix-prefetch-url --unpack "https://github.com/${repo}/archive/${rev}.tar.gz" 2> /dev/null | tail -n 1 + nix-prefetch-url --unpack "https://github.com/${repo}/archive/${rev}.tar.gz" 2> /dev/null } echo "Fetching latest krops commit"