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
# 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
# following command after the lnd service starts:
# scp bitcoin-node:/secrets/lnd-seed-mnemonic ./secrets/lnd-seed-mnemonic
# following commands after the lnd service starts:
# 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.
# 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
# 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.
# 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.
nix-bitcoin.configVersion = "0.0.30";
nix-bitcoin.configVersion = "0.0.41";
}

View File

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

View File

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

View File

@ -200,32 +200,28 @@ in {
ExecStartPost = let
restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1";
in [
# Run fully privileged for secrets dir write access
(nbLib.privileged "lnd-create-mnemonic" ''
(nbLib.script "lnd-create-wallet" ''
attempts=250
while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do
((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; }
sleep 0.1
done
mnemonic=${secretsDir}/lnd-seed-mnemonic
if [[ ! -f $mnemonic ]]; then
if [[ ! -f ${networkDir}/wallet.db ]]; then
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
chown ${cfg.user}: "$mnemonic"
'')
(nbLib.script "lnd-create-wallet" ''
if [[ ! -f ${networkDir}/wallet.db ]]; then
echo Create lnd wallet
echo Create lnd wallet
${pkgs.curl}/bin/curl -s --output /dev/null --show-error \
--cacert ${secretsDir}/lnd-cert \
-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
# 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
sleep 0.1
done
'')
# Run fully privileged for chown
# Setting macaroon permission for other users needs root permissions
(nbLib.privileged "lnd-create-macaroons" ''
umask ug=r,o=
${lib.concatMapStrings (macaroon: ''

View File

@ -69,6 +69,28 @@ let
(mkOnionServiceChange "clightning")
(mkOnionServiceChange "lnd")
(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

View File

@ -326,16 +326,22 @@ def _():
files = {
"bitcoind": "var/lib/bitcoind/test/wallet.dat",
"clightning": "var/lib/clightning/bitcoin/hsm_secret",
"lnd": "secrets/lnd-seed-mnemonic",
"joinmarket": "secrets/jm-wallet-seed",
"lnd": "var/lib/lnd/lnd-seed-mnemonic",
"joinmarket": "var/lib/joinmarket/jm-wallet-seed",
"btcpayserver": "var/backup/postgresql/btcpaydb.sql.gz",
}
actual_files = succeed(f"{run_duplicity} list-current-files file:///var/lib/localBackups")
for test, file in files.items():
if test in enabled_tests and file not in actual_files:
def assert_file_exists(file):
if file not in actual_files:
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
@test("banlist-and-restart")