Merge fort-nix/nix-bitcoin#385: Misc. improvments

fdcb68e96e examples/shell.nix: add new commands (Erik Arvstedt)
a2466b1127 secrets: allow extending generate-secrets (Erik Arvstedt)
24fd1e9bdc improve examples/shell.nix (Erik Arvstedt)
8a757e0486 push-release.sh: improve --dry-run mode (Erik Arvstedt)
82a2b148d8 secrets: minor fixes (Erik Arvstedt)
e1e3d8a92b secrets: simplify cert generation (Erik Arvstedt)
2c8e29b35b lnd: extract option `certPath` (Erik Arvstedt)
be12a49933 lightning-pool/loop: extract lnd variable (Erik Arvstedt)
955b44404c delete helper/fetch-channel (Erik Arvstedt)
5087ce245f minor cleanups (Erik Arvstedt)
0d2db4e79f backups: add option `postgresqlDatabases` (Erik Arvstedt)

Pull request description:

ACKs for top commit:
  nixbitcoin:
    ACK fdcb68e96e
  jonasnick:
    ACK  fdcb68e96e

Tree-SHA512: a0fef5b6f8704a445b0e381a1713c14d1447e16798e7035bb005d2b61c4cde208f96fc6f152238b6ea2e9080c04fffe7f841073fa41a5c1e0597204e9ed805c2
This commit is contained in:
Jonas Nick 2021-09-12 13:22:04 +00:00
commit cf70d05be0
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
27 changed files with 353 additions and 288 deletions

View File

@ -1,53 +1,8 @@
let let
# This is either a path to a local nix-bitcoin source or an attribute set to nix-bitcoin = toString (import ./nix-bitcoin-release.nix);
# 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 <nixpkgs> {}).runCommand "nix-bitcoin-src" {} ''
mkdir $out; tar xf ${builtins.fetchurl nix-bitcoin-release} -C $out
'';
in in
with pkgs; import "${nix-bitcoin}/helper/makeShell.nix" {
stdenv.mkDerivation rec { configDir = ./.;
name = "nix-bitcoin-environment"; # Set this to modify your shell
# extraShellInitCmds = (pkgs: ''<my bash code>'');
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
'';
}

View File

@ -62,7 +62,7 @@
nbPkgs = self.mkNbPkgs { inherit system pkgs; }; nbPkgs = self.mkNbPkgs { inherit system pkgs; };
packages = flake-utils.lib.flattenTree (removeAttrs nbPkgs [ packages = flake-utils.lib.flattenTree (removeAttrs nbPkgs [
"pinned" "modulesPkgs" "nixops19_09" "krops" "pinned" "modulesPkgs" "nixops19_09" "krops" "generate-secrets"
]) // { ]) // {
runVM = mkVMScript packages.vm; runVM = mkVMScript packages.vm;

View File

@ -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\";"

View File

@ -15,26 +15,28 @@ trap "rm -rf $TMPDIR" EXIT
GPG_HOME=$TMPDIR/gpg-home GPG_HOME=$TMPDIR/gpg-home
mkdir -p -m 700 "$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 # Import key
gpg --homedir $GPG_HOME --import "$scriptDir/key-jonasnick.bin" &> /dev/null gpg --homedir $GPG_HOME --import "$scriptDir/key-jonasnick.bin" &> /dev/null
# Verify key fingerprint # Verify key fingerprint
gpg --homedir $GPG_HOME --list-keys 36C71A37C9D988BDE82508D9B1A70E4F8DCD0366 > /dev/null gpg --homedir $GPG_HOME --list-keys 36C71A37C9D988BDE82508D9B1A70E4F8DCD0366 > /dev/null
# Verify signature for SHA256SUMS.txt # Fetch nar-hash of release
gpg --homedir $GPG_HOME --verify SHA256SUMS.txt.asc &> /dev/null || { 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." echo "Error: Signature verification failed. Please open an issue in the project repository."
exit 1 exit 1
} }
sha256=$(cat SHA256SUMS.txt | cut -d\ -f1) >&2 echo "Fetched and verified release $version"
cat <<EOF cat <<EOF
{ builtins.fetchTarball {
url = "$baseUrl/nix-bitcoin-$version.tar.gz"; url = "https://github.com/$repo/archive/v$version.tar.gz";
sha256 = "$sha256"; sha256 = "$(cat nar-hash.txt)";
} }
EOF EOF

110
helper/makeShell.nix Normal file
View File

@ -0,0 +1,110 @@
{ configDir, extraShellInitCmds ? (pkgs: "") }:
let
nixpkgs = (import ../pkgs/nixpkgs-pinned.nix).nixpkgs;
pkgs = import nixpkgs {};
nbPkgs = import ../pkgs { inherit pkgs; };
cfgDir = toString configDir;
in
with pkgs;
stdenv.mkDerivation rec {
name = "nix-bitcoin-environment";
path = lib.makeBinPath [ nbPkgs.extra-container ];
shellHook = ''
export NIX_PATH="nixpkgs=${nixpkgs}:nix-bitcoin=${toString ../.}:."
export PATH="${path}''${PATH:+:}$PATH"
export NIX_BITCOIN_EXAMPLES_DIR="${cfgDir}"
help() {
echo "nix-bitcoin path: ${toString ../.}"
echo
echo "Available commands"
echo "=================="
echo "deploy"
echo " Run krops-deploy and eval-config in parallel."
echo " This ensures that eval failures appear quickly when deploying."
echo " In this case, deployment is stopped."
echo
echo "krops-deploy"
echo " Deploy your node via krops"
echo
echo "eval-config"
echo " Evaluate your node system configuration"
echo
echo "generate-secrets"
echo " Create secrets required by your node configuration."
echo " Secrets are written to ./secrets/"
echo " This function is automatically called by krops-deploy."
echo
echo "update-nix-bitcoin"
echo " Fetch and use the latest version of nix-bitcoin"
}
h() { help; }
fetch-release() {
${toString ./fetch-release}
}
update-nix-bitcoin() {
fetch-release > "${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" \
'<nixpkgs/nixos>' -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}
'';
}

View File

@ -3,14 +3,10 @@ set -euo pipefail
REPO=fort-nix/nix-bitcoin REPO=fort-nix/nix-bitcoin
BRANCH=master BRANCH=master
OAUTH_TOKEN=$(pass show nix-bitcoin/github/oauth-token) OAUTH_TOKEN=
DRY_RUN= DRY_RUN=
TAG_NAME= TAG_NAME=
if [[ ! $OAUTH_TOKEN ]]; then
echo "Please set OAUTH_TOKEN variable"
fi
for arg in "$@"; do for arg in "$@"; do
case $arg in case $arg in
--dry-run|-n) --dry-run|-n)
@ -26,18 +22,28 @@ if [[ ! $TAG_NAME ]]; then
echo "$0 [--dry-run|-n] <tag_name>" echo "$0 [--dry-run|-n] <tag_name>"
exit exit
fi 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) 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) echo "Latest release" $(echo $RESPONSE | jq -r '.tag_name' | tail -c +2)
while true; do
if [[ ! $DRY_RUN ]]; then
while true; do
read -p "Create release $TAG_NAME? [yn] " yn read -p "Create release $TAG_NAME? [yn] " yn
case $yn in case $yn in
[Yy]* ) break;; [Yy]* ) break;;
[Nn]* ) exit;; [Nn]* ) exit;;
* ) echo "Please answer y or n.";; * ) echo "Please answer y or n.";;
esac esac
done done
fi
TMPDIR=$(mktemp -d) TMPDIR=$(mktemp -d)
if [[ ! $DRY_RUN ]]; then trap "rm -rf $TMPDIR" EXIT; fi if [[ ! $DRY_RUN ]]; then trap "rm -rf $TMPDIR" EXIT; fi
@ -53,6 +59,10 @@ SHA256SUMS=$TMPDIR/SHA256SUMS.txt
(cd $TMPDIR; sha256sum $ARCHIVE_NAME > $SHA256SUMS) (cd $TMPDIR; sha256sum $ARCHIVE_NAME > $SHA256SUMS)
gpg -o $SHA256SUMS.asc -a --detach-sig $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 if [[ $DRY_RUN ]]; then
echo "Created v$TAG_NAME in $TMPDIR" echo "Created v$TAG_NAME in $TMPDIR"
exit 0 exit 0
@ -71,6 +81,10 @@ post_asset() {
curl -H "Authorization: token $OAUTH_TOKEN" --data-binary "@$1" -H "Content-Type: application/octet-stream" \ curl -H "Authorization: token $OAUTH_TOKEN" --data-binary "@$1" -H "Content-Type: application/octet-stream" \
$GH_ASSET/$(basename $1) &> /dev/null $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 $ARCHIVE
post_asset $SHA256SUMS post_asset $SHA256SUMS
post_asset $SHA256SUMS.asc post_asset $SHA256SUMS.asc

View File

@ -18,17 +18,22 @@ let
${config.services.nbxplorer.dataDir} ${config.services.nbxplorer.dataDir}
${config.services.btcpayserver.dataDir} ${config.services.btcpayserver.dataDir}
${config.services.joinmarket.dataDir} ${config.services.joinmarket.dataDir}
${config.services.postgresqlBackup.location}/btcpaydb.sql.gz
${optionalString config.nix-bitcoin.generateSecrets "${config.nix-bitcoin.secretsDir}"} ${optionalString config.nix-bitcoin.generateSecrets "${config.nix-bitcoin.secretsDir}"}
/var/lib/tor /var/lib/tor
/var/lib/nixos /var/lib/nixos
${builtins.concatStringsSep "\n" postgresqlBackupPaths}
# Extra files # Extra files
${cfg.extraFiles} ${cfg.extraFiles}
# Exclude all unspecified files and directories # 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 { in {
options.services.backups = { options.services.backups = {
enable = mkEnableOption "Backups service"; enable = mkEnableOption "Backups service";
@ -53,6 +58,11 @@ in {
Run backup with the given frequency. If null, do not run automatically. 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 { extraFiles = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
@ -63,8 +73,7 @@ in {
}; };
}; };
config = mkIf cfg.enable (mkMerge [ config = mkIf cfg.enable {
{
environment.systemPackages = [ pkgs.duplicity ]; environment.systemPackages = [ pkgs.duplicity ];
services.duplicity = { services.duplicity = {
@ -78,17 +87,24 @@ in {
secretFile = "${config.nix-bitcoin.secretsDir}/backup-encryption-env"; secretFile = "${config.nix-bitcoin.secretsDir}/backup-encryption-env";
}; };
nix-bitcoin.secrets.backup-encryption-env.user = "root"; systemd.services.duplicity = {
} wants = postgresqlBackupServices;
(mkIf config.services.btcpayserver.enable { after = postgresqlBackupServices;
};
services.postgresqlBackup = { services.postgresqlBackup = {
enable = true; enable = mkIf (cfg.postgresqlDatabases != []) true;
databases = [ "btcpaydb" ]; databases = cfg.postgresqlDatabases;
}; };
systemd.services.duplicity = rec {
wants = [ "postgresqlBackup-btcpaydb.service" ]; nix-bitcoin.secrets.backup-encryption-env.user = "root";
after = wants; 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" ];
}; };
})
]);
} }

View File

@ -394,15 +394,22 @@ in {
}; };
users.groups.${cfg.group} = {}; users.groups.${cfg.group} = {};
users.groups.bitcoinrpc-public = {}; users.groups.bitcoinrpc-public = {};
nix-bitcoin.operator.groups = [ cfg.group ]; nix-bitcoin.operator.groups = [ cfg.group ];
nix-bitcoin.secrets.bitcoin-rpcpassword-privileged.user = cfg.user; nix-bitcoin.secrets = {
nix-bitcoin.secrets.bitcoin-rpcpassword-public = { bitcoin-rpcpassword-privileged.user = cfg.user;
bitcoin-rpcpassword-public = {
user = cfg.user; user = cfg.user;
group = "bitcoinrpc-public"; group = "bitcoinrpc-public";
}; };
nix-bitcoin.secrets.bitcoin-HMAC-privileged.user = cfg.user; bitcoin-HMAC-privileged.user = cfg.user;
nix-bitcoin.secrets.bitcoin-HMAC-public.user = cfg.user; bitcoin-HMAC-public.user = cfg.user;
};
nix-bitcoin.generateSecretsCmds.bitcoind = ''
makeBitcoinRPCPassword privileged
makeBitcoinRPCPassword public
'';
}; };
} }

View File

@ -210,9 +210,9 @@ in {
install -m 600 ${configFile} '${cfg.btcpayserver.dataDir}/settings.config' install -m 600 ${configFile} '${cfg.btcpayserver.dataDir}/settings.config'
${optionalString (cfg.btcpayserver.lightningBackend == "lnd") '' ${optionalString (cfg.btcpayserver.lightningBackend == "lnd") ''
{ {
echo -n "${lndConfig}"; echo -n "${lndConfig}"
${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.nix-bitcoin.secretsDir}/lnd-cert \ ${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.services.lnd.certPath} \
| sed -e 's/.*=//;s/://g'; | sed -e 's/.*=//;s/://g'
} >> '${cfg.btcpayserver.dataDir}/settings.config' } >> '${cfg.btcpayserver.dataDir}/settings.config'
''} ''}
''; '';
@ -253,5 +253,8 @@ in {
}; };
bitcoin-HMAC-btcpayserver.user = cfg.bitcoind.user; bitcoin-HMAC-btcpayserver.user = cfg.bitcoind.user;
}; };
nix-bitcoin.generateSecretsCmds.btcpayserver = ''
makeBitcoinRPCPassword btcpayserver
'';
}; };
} }

View File

@ -105,7 +105,7 @@ in
macaroonDir=${dataDir}/lnddir-proxy/data/chain/bitcoin/mainnet macaroonDir=${dataDir}/lnddir-proxy/data/chain/bitcoin/mainnet
mkdir -p $macaroonDir mkdir -p $macaroonDir
ln -sf /run/lnd/charge-lnd.macaroon $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 // { serviceConfig = nbLib.defaultHardening // {
ExecStart = '' ExecStart = ''

View File

@ -131,5 +131,8 @@ in {
bitcoin-rpcpassword-joinmarket-ob-watcher.user = cfg.user; bitcoin-rpcpassword-joinmarket-ob-watcher.user = cfg.user;
bitcoin-HMAC-joinmarket-ob-watcher.user = bitcoind.user; bitcoin-HMAC-joinmarket-ob-watcher.user = bitcoind.user;
}; };
nix-bitcoin.generateSecretsCmds.joinmarket-ob-watcher = ''
makeBitcoinRPCPassword joinmarket-ob-watcher
'';
}; };
} }

View File

@ -305,6 +305,9 @@ in {
}; };
nix-bitcoin.secrets.jm-wallet-password.user = cfg.user; nix-bitcoin.secrets.jm-wallet-password.user = cfg.user;
nix-bitcoin.generateSecretsCmds.joinmarket = ''
makePasswordSecret jm-wallet-password
'';
} }
(mkIf cfg.yieldgenerator.enable { (mkIf cfg.yieldgenerator.enable {

View File

@ -6,6 +6,9 @@ let
cfg = config.services.lightning-loop; cfg = config.services.lightning-loop;
nbLib = config.nix-bitcoin.lib; nbLib = config.nix-bitcoin.lib;
secretsDir = config.nix-bitcoin.secretsDir; secretsDir = config.nix-bitcoin.secretsDir;
lnd = config.services.lnd;
network = config.services.bitcoind.network; network = config.services.bitcoind.network;
rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}";
configFile = builtins.toFile "loop.conf" '' configFile = builtins.toFile "loop.conf" ''
@ -17,9 +20,9 @@ let
tlscertpath=${secretsDir}/loop-cert tlscertpath=${secretsDir}/loop-cert
tlskeypath=${secretsDir}/loop-key tlskeypath=${secretsDir}/loop-key
lnd.host=${config.services.lnd.rpcAddress}:${toString config.services.lnd.rpcPort} lnd.host=${lnd.rpcAddress}:${toString lnd.rpcPort}
lnd.macaroonpath=${config.services.lnd.networkDir}/admin.macaroon lnd.macaroonpath=${lnd.networkDir}/admin.macaroon
lnd.tlspath=${secretsDir}/lnd-cert lnd.tlspath=${lnd.certPath}
${optionalString (cfg.proxy != null) "server.proxy=${cfg.proxy}"} ${optionalString (cfg.proxy != null) "server.proxy=${cfg.proxy}"}
@ -89,7 +92,7 @@ in {
environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ]; environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ];
systemd.tmpfiles.rules = [ 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 = { systemd.services.lightning-loop = {
@ -98,7 +101,7 @@ in {
after = [ "lnd.service" ]; after = [ "lnd.service" ];
serviceConfig = nbLib.defaultHardening // { serviceConfig = nbLib.defaultHardening // {
ExecStart = "${cfg.package}/bin/loopd --configfile=${configFile}"; ExecStart = "${cfg.package}/bin/loopd --configfile=${configFile}";
User = config.services.lnd.user; User = lnd.user;
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
@ -106,8 +109,11 @@ in {
}; };
nix-bitcoin.secrets = { nix-bitcoin.secrets = {
loop-key.user = config.services.lnd.user; loop-key.user = lnd.user;
loop-cert.user = config.services.lnd.user; loop-cert.user = lnd.user;
}; };
nix-bitcoin.generateSecretsCmds.lightning-loop = ''
makeCert loop '${optionalString (cfg.rpcAddress != "localhost") "IP:${cfg.rpcAddress}"}'
'';
}; };
} }

View File

@ -5,7 +5,9 @@ with lib;
let let
cfg = config.services.lightning-pool; cfg = config.services.lightning-pool;
nbLib = config.nix-bitcoin.lib; nbLib = config.nix-bitcoin.lib;
secretsDir = config.nix-bitcoin.secretsDir;
lnd = config.services.lnd;
network = config.services.bitcoind.network; network = config.services.bitcoind.network;
rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}";
configFile = builtins.toFile "pool.conf" '' configFile = builtins.toFile "pool.conf" ''
@ -13,9 +15,9 @@ let
restlisten=${cfg.restAddress}:${toString cfg.restPort} restlisten=${cfg.restAddress}:${toString cfg.restPort}
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
lnd.host=${config.services.lnd.rpcAddress}:${toString config.services.lnd.rpcPort} lnd.host=${lnd.rpcAddress}:${toString lnd.rpcPort}
lnd.macaroondir=${config.services.lnd.networkDir} lnd.macaroondir=${lnd.networkDir}
lnd.tlspath=${secretsDir}/lnd-cert lnd.tlspath=${lnd.certPath}
${cfg.extraConfig} ${cfg.extraConfig}
''; '';

View File

@ -247,5 +247,8 @@ in {
nix-bitcoin.operator.groups = [ cfg.group ]; nix-bitcoin.operator.groups = [ cfg.group ];
nix-bitcoin.secrets.liquid-rpcpassword.user = cfg.user; nix-bitcoin.secrets.liquid-rpcpassword.user = cfg.user;
nix-bitcoin.generateSecretsCmds.liquid = ''
makePasswordSecret liquid-rpcpassword
'';
}; };
} }

View File

@ -5,7 +5,6 @@ with lib;
let let
cfg = config.services.lnd.restOnionService; cfg = config.services.lnd.restOnionService;
nbLib = config.nix-bitcoin.lib; nbLib = config.nix-bitcoin.lib;
secretsDir = config.nix-bitcoin.secretsDir;
runAsUser = config.nix-bitcoin.runAsUserCmd; runAsUser = config.nix-bitcoin.runAsUserCmd;
lnd = config.services.lnd; lnd = config.services.lnd;
@ -17,7 +16,7 @@ let
--host=$(cat ${config.nix-bitcoin.onionAddresses.dataDir}/lnd/lnd-rest) \ --host=$(cat ${config.nix-bitcoin.onionAddresses.dataDir}/lnd/lnd-rest) \
--port=${toString lnd.restPort} \ --port=${toString lnd.restPort} \
--lnddir=${lnd.dataDir} \ --lnddir=${lnd.dataDir} \
--tlscertpath=${secretsDir}/lnd-cert "$@" --tlscertpath=${lnd.certPath} "$@"
''; '';
in { in {
options.services.lnd.restOnionService = { options.services.lnd.restOnionService = {

View File

@ -14,7 +14,7 @@ let
configFile = pkgs.writeText "lnd.conf" '' configFile = pkgs.writeText "lnd.conf" ''
datadir=${cfg.dataDir} datadir=${cfg.dataDir}
logdir=${cfg.dataDir}/logs logdir=${cfg.dataDir}/logs
tlscertpath=${secretsDir}/lnd-cert tlscertpath=${cfg.certPath}
tlskeypath=${secretsDir}/lnd-key tlskeypath=${secretsDir}/lnd-key
listen=${toString cfg.address}:${toString cfg.port} listen=${toString cfg.address}:${toString cfg.port}
@ -126,7 +126,7 @@ in {
'' ''
${runAsUser} ${cfg.user} ${cfg.package}/bin/lncli \ ${runAsUser} ${cfg.user} ${cfg.package}/bin/lncli \
--rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \ --rpcserver ${cfg.rpcAddress}:${toString cfg.rpcPort} \
--tlscertpath '${secretsDir}/lnd-cert' \ --tlscertpath '${cfg.certPath}' \
--macaroonpath '${networkDir}/admin.macaroon' "$@" --macaroonpath '${networkDir}/admin.macaroon' "$@"
''; '';
description = "Binary to connect with the lnd instance."; description = "Binary to connect with the lnd instance.";
@ -149,6 +149,11 @@ in {
default = cfg.user; default = cfg.user;
description = "The group as which to run LND."; description = "The group as which to run LND.";
}; };
certPath = mkOption {
readOnly = true;
default = "${secretsDir}/lnd-cert";
description = "LND TLS certificate path.";
};
inherit (nbLib) enforceTor; inherit (nbLib) enforceTor;
}; };
@ -211,7 +216,7 @@ in {
# Retrying is necessary because it can happen that the lnd socket is # Retrying is necessary because it can happen that the lnd socket is
# existing, but the RPC service isn't yet, which results in error # existing, but the RPC service isn't yet, which results in error
# "waiting to start, RPC services not available". # "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"; restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1";
in [ in [
(nbLib.script "lnd-create-wallet" '' (nbLib.script "lnd-create-wallet" ''
@ -288,7 +293,14 @@ in {
lnd-wallet-password.user = cfg.user; lnd-wallet-password.user = cfg.user;
lnd-key.user = cfg.user; lnd-key.user = cfg.user;
lnd-cert.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}"}'
'';
}; };
} }

View File

@ -2,9 +2,6 @@
with lib; with lib;
let let
cfg = config.nix-bitcoin;
in
{
options.nix-bitcoin = { options.nix-bitcoin = {
secretsDir = mkOption { secretsDir = mkOption {
type = types.path; type = types.path;
@ -24,8 +21,16 @@ in
type = types.bool; type = types.bool;
default = false; default = false;
description = '' description = ''
Automatically generate all required secrets. Automatically generate all required secrets at system startup.
Make sure to create a backup of the generated secrets. 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 { secretsSetupMethod = mkOption {
internal = true;
type = types.str; type = types.str;
default = throw '' default = throw ''
Error: No secrets setup method has been defined. Error: No secrets setup method has been defined.
@ -73,7 +77,67 @@ in
- Set `nix-bitcoin.secretsSetupMethod = "manual"` if you want to manually setup secrets - 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 = { config = {
# This target is active when secrets have been setup successfully. # This target is active when secrets have been setup successfully.
@ -107,7 +171,7 @@ in
cd "${cfg.secretsDir}" cd "${cfg.secretsDir}"
chown root: . chown root: .
chmod 0700 . chmod 0700 .
${cfg.pkgs.generate-secrets} ${cfg.generateSecretsScript}
''} ''}
setupSecret() { setupSecret() {
@ -141,9 +205,11 @@ in
# Make all other files accessible to root only # Make all other files accessible to root only
unprocessedFiles=$(comm -23 <(printf '%s\n' *) <(printf '%s\n' "''${processedFiles[@]}" | sort)) unprocessedFiles=$(comm -23 <(printf '%s\n' *) <(printf '%s\n' "''${processedFiles[@]}" | sort))
if [[ $unprocessedFiles ]]; then
IFS=$'\n' IFS=$'\n'
chown root: $unprocessedFiles chown root: $unprocessedFiles
chmod 0440 $unprocessedFiles chmod 0440 $unprocessedFiles
fi
# Now make the secrets dir accessible to other users # Now make the secrets dir accessible to other users
chmod 0751 "$dir" chmod 0751 "$dir"

View File

@ -83,6 +83,13 @@ in {
} // nbLib.allowedIPAddresses cfg.enforceTor } // nbLib.allowedIPAddresses cfg.enforceTor
// nbLib.nodejs; // nbLib.nodejs;
}; };
nix-bitcoin.secrets.spark-wallet-login.user = cfg.user; 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
'';
}; };
} }

View File

@ -9,7 +9,7 @@ let self = {
spark-wallet = pkgs.callPackage ./spark-wallet { }; spark-wallet = pkgs.callPackage ./spark-wallet { };
liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { }; liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { };
joinmarket = pkgs.callPackage ./joinmarket { inherit (self) nbPython3Packages; }; 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 { }; nixops19_09 = pkgs.callPackage ./nixops { };
krops = import ./krops { }; krops = import ./krops { };
netns-exec = pkgs.callPackage ./netns-exec { }; netns-exec = pkgs.callPackage ./netns-exec { };

View File

@ -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
''

View File

@ -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}
''

View File

@ -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

View File

@ -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

View File

@ -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}
''

View File

@ -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

View File

@ -5,7 +5,7 @@ set -euo pipefail
archive_hash () { archive_hash () {
repo=$1 repo=$1
rev=$2 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" echo "Fetching latest krops commit"