lnd, joinmarket: don't write to secrets dir

Keeping the secrets dir read-only is more simple and robust.

- lnd seed mnemonic creation and joinmarket wallet creation can be
  run as the regular service user instead of root.

- It is easier to switch to a third-party secrets deployment
  method in the future.

Don't create a seed mnemonic for lnd when a wallet exists.
This avoids creating unused mnemonics and helps simplifying
the migration command in `versioning.nix`.
This commit is contained in:
Erik Arvstedt 2021-03-10 14:08:37 +01:00
parent 55d87490ec
commit 03db1a61b1
No known key found for this signature in database
GPG Key ID: 33312B944DD97846
6 changed files with 56 additions and 33 deletions

View File

@ -71,11 +71,13 @@
## WARNING ## WARNING
# If you use lnd, you should manually backup your wallet mnemonic # If you use lnd, you should manually backup your wallet mnemonic
# seed. This will allow you to recover on-chain funds. You can run the # seed. This will allow you to recover on-chain funds. You can run the
# following command after the lnd service starts: # following commands after the lnd service starts:
# scp bitcoin-node:/secrets/lnd-seed-mnemonic ./secrets/lnd-seed-mnemonic # mkdir -p ./backups/lnd/
# scp bitcoin-node:/var/lib/lnd/lnd-seed-mnemonic ./backups/lnd/
#
# You should also backup your channel state after opening new channels. # You should also backup your channel state after opening new channels.
# This will allow you to recover off-chain funds, by force-closing channels. # This will allow you to recover off-chain funds, by force-closing channels.
# scp bitcoin-node:/var/lib/lnd/chain/bitcoin/mainnet/channel.backup /my-backup-path/channel.backup # scp bitcoin-node:/var/lib/lnd/chain/bitcoin/mainnet/channel.backup ./backups/lnd/
### SPARK WALLET ### SPARK WALLET
# Enable this module to use spark-wallet, a minimalistic wallet GUI for # Enable this module to use spark-wallet, a minimalistic wallet GUI for
@ -229,5 +231,6 @@
# The nix-bitcoin release version that your config is compatible with. # The nix-bitcoin release version that your config is compatible with.
# When upgrading to a backwards-incompatible release, nix-bitcoin will display an # When upgrading to a backwards-incompatible release, nix-bitcoin will display an
# an error and provide hints for migrating your config to the new release. # an error and provide hints for migrating your config to the new release.
nix-bitcoin.configVersion = "0.0.30"; nix-bitcoin.configVersion = "0.0.41";
} }

View File

@ -4,7 +4,6 @@ with lib;
let let
cfg = config.services.backups; cfg = config.services.backups;
secretsDir = config.nix-bitcoin.secretsDir;
filelist = pkgs.writeText "filelist.txt" '' filelist = pkgs.writeText "filelist.txt" ''
${optionalString (!cfg.with-bulk-data) "- ${config.services.bitcoind.dataDir}/blocks"} ${optionalString (!cfg.with-bulk-data) "- ${config.services.bitcoind.dataDir}/blocks"}
@ -12,7 +11,6 @@ let
${config.services.bitcoind.dataDir} ${config.services.bitcoind.dataDir}
${config.services.clightning.dataDir} ${config.services.clightning.dataDir}
${config.services.lnd.dataDir} ${config.services.lnd.dataDir}
${secretsDir}/lnd-seed-mnemonic
${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/blocks"} ${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/blocks"}
${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/chainstate"} ${optionalString (!cfg.with-bulk-data) "- ${config.services.liquidd.dataDir}/*/chainstate"}
${config.services.liquidd.dataDir} ${config.services.liquidd.dataDir}
@ -20,8 +18,8 @@ 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}
${secretsDir}/jm-wallet-seed
${config.services.postgresqlBackup.location}/btcpaydb.sql.gz ${config.services.postgresqlBackup.location}/btcpaydb.sql.gz
${optionalString config.nix-bitcoin.generateSecrets "${config.nix-bitcoin.secretsDir}"}
/var/lib/tor /var/lib/tor
# Extra files # Extra files
${cfg.extraFiles} ${cfg.extraFiles}

View File

@ -240,20 +240,19 @@ in {
''; '';
# Generating wallets (jmclient/wallet.py) is only supported for mainnet or testnet # Generating wallets (jmclient/wallet.py) is only supported for mainnet or testnet
ExecStartPost = mkIf (bitcoind.network == "mainnet") ExecStartPost = mkIf (bitcoind.network == "mainnet")
(nbLib.privileged "joinmarket-create-wallet" '' (nbLib.script "joinmarket-create-wallet" ''
walletname=wallet.jmdat walletname=wallet.jmdat
wallet=${cfg.dataDir}/wallets/$walletname wallet=${cfg.dataDir}/wallets/$walletname
if [[ ! -f $wallet ]]; then if [[ ! -f $wallet ]]; then
echo "Create wallet" echo "Create wallet"
pw=$(cat "${secretsDir}"/jm-wallet-password) pw=$(cat "${secretsDir}"/jm-wallet-password)
cd ${cfg.dataDir} cd ${cfg.dataDir}
if ! ${pkgs.utillinux}/bin/runuser -u ${cfg.user} -- \ if ! ${nbPkgs.joinmarket}/bin/jm-genwallet --datadir=${cfg.dataDir} $walletname $pw \
${nbPkgs.joinmarket}/bin/jm-genwallet --datadir=${cfg.dataDir} $walletname $pw \
| grep 'recovery_seed' \ | grep 'recovery_seed' \
| cut -d ':' -f2 \ | cut -d ':' -f2 \
| (umask u=r,go=; cat > "${secretsDir}/jm-wallet-seed"); then | (umask u=r,go=; cat > jm-wallet-seed); then
echo "wallet creation failed" echo "wallet creation failed"
rm -f "$wallet" "${secretsDir}/jm-wallet-seed" rm -f "$wallet" jm-wallet-seed
exit 1 exit 1
fi fi
fi fi

View File

@ -200,32 +200,28 @@ in {
ExecStartPost = let ExecStartPost = let
restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1"; restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1";
in [ in [
# Run fully privileged for secrets dir write access (nbLib.script "lnd-create-wallet" ''
(nbLib.privileged "lnd-create-mnemonic" ''
attempts=250 attempts=250
while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do
((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; }
sleep 0.1 sleep 0.1
done done
mnemonic=${secretsDir}/lnd-seed-mnemonic
if [[ ! -f $mnemonic ]]; then
echo Create lnd seed
umask u=r,go=
${pkgs.curl}/bin/curl -s \
--cacert ${secretsDir}/lnd-cert \
-X GET ${restUrl}/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic"
fi
chown ${cfg.user}: "$mnemonic"
'')
(nbLib.script "lnd-create-wallet" ''
if [[ ! -f ${networkDir}/wallet.db ]]; then if [[ ! -f ${networkDir}/wallet.db ]]; then
echo Create lnd wallet mnemonic="${cfg.dataDir}/lnd-seed-mnemonic"
if [[ ! -f "$mnemonic" ]]; then
echo Create lnd seed
umask u=r,go=
${pkgs.curl}/bin/curl -s \
--cacert ${secretsDir}/lnd-cert \
-X GET ${restUrl}/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic"
fi
echo Create lnd wallet
${pkgs.curl}/bin/curl -s --output /dev/null --show-error \ ${pkgs.curl}/bin/curl -s --output /dev/null --show-error \
--cacert ${secretsDir}/lnd-cert \ --cacert ${secretsDir}/lnd-cert \
-X POST -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ -X POST -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \
\"cipher_seed_mnemonic\": $(cat ${secretsDir}/lnd-seed-mnemonic | tr -d '\n')}" \ \"cipher_seed_mnemonic\": $(cat "$mnemonic" | tr -d '\n')}" \
${restUrl}/initwallet ${restUrl}/initwallet
# Guarantees that RPC calls with cfg.cli succeed after the service is started # Guarantees that RPC calls with cfg.cli succeed after the service is started
@ -248,9 +244,8 @@ in {
while ! { exec 3>/dev/tcp/${cfg.rpcAddress}/${toString cfg.rpcPort}; } &>/dev/null; do while ! { exec 3>/dev/tcp/${cfg.rpcAddress}/${toString cfg.rpcPort}; } &>/dev/null; do
sleep 0.1 sleep 0.1
done done
'') '')
# Run fully privileged for chown # Setting macaroon permission for other users needs root permissions
(nbLib.privileged "lnd-create-macaroons" '' (nbLib.privileged "lnd-create-macaroons" ''
umask ug=r,o= umask ug=r,o=
${lib.concatMapStrings (macaroon: '' ${lib.concatMapStrings (macaroon: ''

View File

@ -69,6 +69,28 @@ let
(mkOnionServiceChange "clightning") (mkOnionServiceChange "clightning")
(mkOnionServiceChange "lnd") (mkOnionServiceChange "lnd")
(mkOnionServiceChange "btcpayserver") (mkOnionServiceChange "btcpayserver")
{
version = "0.0.41";
condition = config.services.lnd.enable || config.services.joinmarket.enable;
message = let
secretsDir = config.nix-bitcoin.secretsDir;
lnd = config.services.lnd;
jm = config.services.joinmarket;
in ''
Secret files generated by services at runtime are now stored in the service
data dirs instead of the global secrets dir.
To migrate, run the following Bash script as root on your nix-bitcoin node:
if [[ -e ${secretsDir}/lnd-seed-mnemonic ]]; then
install -o ${lnd.user} -g ${lnd.group} -m400 "${secretsDir}/lnd-seed-mnemonic" "${lnd.dataDir}"
fi
if [[ -e ${secretsDir}/jm-wallet-seed ]]; then
install -o ${jm.user} -g ${jm.group} -m400 "${secretsDir}/jm-wallet-seed" "${jm.dataDir}"
fi
rm -f "${secretsDir}"/{lnd-seed-mnemonic,jm-wallet-seed}
'';
}
]; ];
incompatibleChanges = optionals incompatibleChanges = optionals

View File

@ -326,16 +326,22 @@ def _():
files = { files = {
"bitcoind": "var/lib/bitcoind/test/wallet.dat", "bitcoind": "var/lib/bitcoind/test/wallet.dat",
"clightning": "var/lib/clightning/bitcoin/hsm_secret", "clightning": "var/lib/clightning/bitcoin/hsm_secret",
"lnd": "secrets/lnd-seed-mnemonic", "lnd": "var/lib/lnd/lnd-seed-mnemonic",
"joinmarket": "secrets/jm-wallet-seed", "joinmarket": "var/lib/joinmarket/jm-wallet-seed",
"btcpayserver": "var/backup/postgresql/btcpaydb.sql.gz", "btcpayserver": "var/backup/postgresql/btcpaydb.sql.gz",
} }
actual_files = succeed(f"{run_duplicity} list-current-files file:///var/lib/localBackups") actual_files = succeed(f"{run_duplicity} list-current-files file:///var/lib/localBackups")
for test, file in files.items(): def assert_file_exists(file):
if test in enabled_tests and file not in actual_files: if file not in actual_files:
raise Exception(f"Backup file '{file}' is missing.") raise Exception(f"Backup file '{file}' is missing.")
for test, file in files.items():
if test in enabled_tests:
assert_file_exists(file)
assert_file_exists("secrets/lnd-wallet-password")
# Impure: restarts services # Impure: restarts services
@test("banlist-and-restart") @test("banlist-and-restart")