Merge #107: Add LND support

9d029fd1af Remove lnd explicit tor onion service config (Ștefan D. Mihăilă)
1f407ef22c Remove lnd user from onion-chef (Ștefan D. Mihăilă)
5880023158 Increase xxd column size (Ștefan D. Mihăilă)
101ae3c370 Instruct user to backup channel.backup (Ștefan D. Mihăilă)
fccd91972a Fix "value is a list [...]" error when lnd is not enabled (Ștefan D. Mihăilă)
700fdf6feb Add logdir and tor.privatekeypath to lnd.conf (Ștefan D. Mihăilă)
5a2517b926 Check for existing secrets and create them  more granularly (Ștefan D. Mihăilă)
d6f961db89 Reuse lnd seed (Ștefan D. Mihăilă)
9b0753135c Add LND support (Ștefan D. Mihăilă)
4acf5cd32c Remove unused nginx.csr file (Ștefan D. Mihăilă)
19b971f21f Rename nginx certificate files (Ștefan D. Mihăilă)

Pull request description:

ACKs for top commit:
  jonasnick:
    ACK 9d029fd1af

Tree-SHA512: 58ee80bcab6c3a1c4642a5d40b94e10d28311557ae7c69539fee90d6f252a6afc70b8066cc7d7ddc0a45e2675978718a369b0341c518f8ce7590cbde1403eaeb
This commit is contained in:
Jonas Nick 2019-08-31 15:21:30 +00:00
commit e4d2aab561
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
11 changed files with 295 additions and 38 deletions

View File

@ -30,6 +30,19 @@
# default nix-bitcoin nodes offer outgoing connectivity.
# services.clightning.autolisten = true;
### LND
# Disable clightning and uncomment the following line in order to enable lnd,
# a lightning implementation written in Go.
# services.lnd.enable = assert (!config.services.clightning.enable); true;
## 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:
# nixops scp --from bitcoin-node /secrets/lnd-seed-mnemonic ./secrets/lnd-seed-mnemonic
# You should also backup your channel state after opening new channels.
# This will allow you to recover off-chain funds, by force-closing channels.
# nixops scp --from bitcoin-node /var/lib/lnd/chain/bitcoin/mainnet/channel.backup /my-backup-path/channel.backup
### SPARK WALLET
# Enable this module to use spark-wallet, a minimalistic wallet GUI for
# c-lightning, accessible over the web or through mobile and desktop apps.

View File

@ -193,6 +193,18 @@ in {
to stay under the specified target size in MiB)
'';
};
zmqpubrawblock = mkOption {
type = types.nullOr types.string;
default = null;
example = "tcp://127.0.0.1:28332";
description = "ZMQ address for zmqpubrawblock notifications";
};
zmqpubrawtx = mkOption {
type = types.nullOr types.string;
default = null;
example = "tcp://127.0.0.1:28333";
description = "ZMQ address for zmqpubrawtx notifications";
};
enforceTor = nix-bitcoin-services.enforceTor;
};
};
@ -239,7 +251,7 @@ in {
// (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP
);
) // optionalAttrs config.services.lnd.enable nix-bitcoin-services.allowAnyProtocol; # FOR ZMQ
};
systemd.services.bitcoind-import-banlist = {
description = "Bitcoin daemon banlist importer";

View File

@ -11,4 +11,5 @@
nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix;
spark-wallet = ./spark-wallet.nix;
recurring-donations = ./recurring-donations.nix;
lnd = ./lnd.nix;
}

View File

@ -106,8 +106,8 @@ in {
listen ${toString config.services.electrs.nginxport} ssl;
proxy_pass electrs;
ssl_certificate /secrets/ssl_certificate;
ssl_certificate_key /secrets/ssl_certificate_key;
ssl_certificate /secrets/nginx_cert;
ssl_certificate_key /secrets/nginx_key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 4h;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

132
modules/lnd.nix Normal file
View File

@ -0,0 +1,132 @@
{ config, lib, pkgs, ... }:
with lib;
let
nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { };
cfg = config.services.lnd;
configFile = pkgs.writeText "lnd.conf" ''
datadir=${cfg.dataDir}
logdir=${cfg.dataDir}/logs
bitcoin.mainnet=1
tlscertpath=/secrets/lnd_cert
tlskeypath=/secrets/lnd_key
bitcoin.active=1
bitcoin.node=bitcoind
tor.active=true
tor.v3=true
tor.streamisolation=true
tor.privatekeypath=${cfg.dataDir}/v3_onion_private_key
bitcoind.rpcuser=${config.services.bitcoind.rpcuser}
bitcoind.zmqpubrawblock=${config.services.bitcoind.zmqpubrawblock}
bitcoind.zmqpubrawtx=${config.services.bitcoind.zmqpubrawtx}
${cfg.extraConfig}
'';
init-lnd-wallet-script = pkgs.writeScript "init-lnd-wallet.sh" ''
#!/bin/sh
set -e
umask 377
${pkgs.coreutils}/bin/sleep 5
if [ ! -f /secrets/lnd-seed-mnemonic ]
then
${pkgs.coreutils}/bin/echo Creating lnd seed
${pkgs.curl}/bin/curl -s \
--cacert /secrets/lnd_cert \
-X GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic
fi
if [ ! -f ${cfg.dataDir}/chain/bitcoin/mainnet/wallet.db ]
then
${pkgs.coreutils}/bin/echo Creating lnd wallet
${pkgs.curl}/bin/curl -s \
--cacert /secrets/lnd_cert \
-X POST -d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\", \
\"cipher_seed_mnemonic\": $(${pkgs.coreutils}/bin/cat /secrets/lnd-seed-mnemonic | ${pkgs.coreutils}/bin/tr -d '\n')}" \
https://127.0.0.1:8080/v1/initwallet
else
${pkgs.coreutils}/bin/echo Unlocking lnd wallet
${pkgs.curl}/bin/curl -s \
-H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 ${cfg.dataDir}/chain/bitcoin/mainnet/admin.macaroon)" \
--cacert /secrets/lnd_cert \
-X POST \
-d "{\"wallet_password\": \"$(${pkgs.coreutils}/bin/cat /secrets/lnd-wallet-password | ${pkgs.coreutils}/bin/tr -d '\n' | ${pkgs.coreutils}/bin/base64 -w0)\"}" \
https://127.0.0.1:8080/v1/unlockwallet
fi
exit 0
'';
in {
options.services.lnd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, the LND service will be installed.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/lnd";
description = "The data directory for LND.";
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
autopilot.active=1
'';
description = "Additional configurations to be appended to <filename>lnd.conf</filename>.";
};
enforceTor = nix-bitcoin-services.enforceTor;
};
config = mkIf cfg.enable {
users.users.lnd = {
description = "LND User";
group = "lnd";
extraGroups = [ "bitcoinrpc" "keys" ];
home = cfg.dataDir;
};
users.groups.lnd = {
name = "lnd";
};
systemd.services.lnd = {
description = "Run LND";
path = [ pkgs.altcoins.bitcoind ];
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
preStart = ''
mkdir -m 0770 -p ${cfg.dataDir}
cp ${configFile} ${cfg.dataDir}/lnd.conf
chown -R 'lnd:lnd' '${cfg.dataDir}'
chmod u=rw,g=r,o= ${cfg.dataDir}/lnd.conf
echo "bitcoind.rpcpass=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/lnd.conf'
'';
serviceConfig = {
PermissionsStartOnly = "true";
ExecStart = "${pkgs.lnd}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf";
ExecStartPost = "${pkgs.bash}/bin/bash ${init-lnd-wallet-script}";
User = "lnd";
Restart = "on-failure";
RestartSec = "10s";
} // nix-bitcoin-services.defaultHardening
// (if cfg.enforceTor
then nix-bitcoin-services.allowTor
else nix-bitcoin-services.allowAnyIP
) // nix-bitcoin-services.allowAnyProtocol; # For ZMQ
};
};
}

View File

@ -15,5 +15,6 @@ in {
bitcoin = nixpkgs-unstable.bitcoin.override { miniupnpc = null; };
altcoins.bitcoind = nixpkgs-unstable.altcoins.bitcoind.override { miniupnpc = null; };
clightning = nixpkgs-unstable.clightning.override { };
lnd = nixpkgs-unstable.lnd.override { };
};
}

View File

@ -28,6 +28,7 @@ in {
./onion-chef.nix
./recurring-donations.nix
./hardware-wallets.nix
./lnd.nix
];
options.services.nix-bitcoin = {
@ -46,6 +47,8 @@ in {
# Tor
services.tor.enable = true;
services.tor.client.enable = true;
# LND uses ControlPort to create onion services
services.tor.controlPort = if config.services.lnd.enable then 9051 else null;
# Tor SSH service
services.tor.hiddenServices.sshd = {
@ -64,12 +67,16 @@ in {
services.bitcoind.enforceTor = true;
services.bitcoind.port = 8333;
services.bitcoind.rpcuser = "bitcoinrpc";
services.bitcoind.zmqpubrawblock = "tcp://127.0.0.1:28332";
services.bitcoind.zmqpubrawtx = "tcp://127.0.0.1:28333";
services.bitcoind.extraConfig = ''
assumevalid=0000000000000000000726d186d6298b5054b9a5c49639752294b322a305d240
addnode=ecoc5q34tmbq54wl.onion
discover=0
addresstype=bech32
changetype=bech32
${optionalString (config.services.lnd.enable) "zmqpubrawblock=${config.services.bitcoind.zmqpubrawblock}"}
${optionalString (config.services.lnd.enable) "zmqpubrawtx=${config.services.bitcoind.zmqpubrawtx}"}
'';
services.bitcoind.prune = 0;
services.bitcoind.dbCache = 1000;
@ -96,11 +103,15 @@ in {
version = 3;
};
# lnd
services.lnd.enforceTor = true;
# Create user operator which can use bitcoin-cli and lightning-cli
users.users.operator = {
isNormalUser = true;
extraGroups = [ config.services.bitcoind.group ]
++ (if config.services.clightning.enable then [ "clightning" ] else [ ])
++ (if config.services.lnd.enable then [ "lnd" ] else [ ])
++ (if config.services.liquidd.enable then [ config.services.liquidd.group ] else [ ])
++ (if (config.services.hardware-wallets.ledger || config.services.hardware-wallets.trezor)
then [ config.services.hardware-wallets.group ] else [ ]);
@ -111,17 +122,26 @@ in {
environment.interactiveShellInit = ''
alias bitcoin-cli='bitcoin-cli -datadir=${config.services.bitcoind.dataDir}'
${optionalString (config.services.clightning.enable) ''
alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}'
'' + (if config.services.liquidd.enable then ''
''}
${optionalString (config.services.lnd.enable) ''
alias lncli='sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath ${config.services.lnd.dataDir}/chain/bitcoin/mainnet/admin.macaroon'
''}
${optionalString (config.services.liquidd.enable) ''
alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}'
alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf'
'' else "");
''}
'';
# Unfortunately c-lightning doesn't allow setting the permissions of the rpc socket
# https://github.com/ElementsProject/lightning/issues/1366
security.sudo.configFile = (
if config.services.clightning.enable then ''
operator ALL=(clightning) NOPASSWD: ALL
''
else if config.services.lnd.enable then ''
operator ALL=(lnd) NOPASSWD: ALL
''
else ""
);
@ -176,6 +196,7 @@ in {
qrencode
]
++ optionals config.services.clightning.enable [clightning]
++ optionals config.services.lnd.enable [lnd]
++ optionals config.services.lightning-charge.enable [lightning-charge]
++ optionals config.services.nanopos.enable [nanopos]
++ optionals config.services.nix-bitcoin-webindex.enable [nginx]

View File

@ -7,6 +7,13 @@ let
group = "bitcoinrpc";
permissions = "0440";
};
lnd-wallet-password = {
text = secrets.lnd-wallet-password;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
lightning-charge-api-token = {
text = "API_TOKEN=" + secrets.lightning-charge-api-token;
destDir = "/secrets/";
@ -36,20 +43,34 @@ let
group = "clightning";
permissions = "0440";
};
ssl_certificate_key = {
keyFile = ../secrets/ssl_certificate_key.key;
nginx_key = {
keyFile = ../secrets/nginx.key;
destDir = "/secrets/";
user = "nginx";
group = "root";
permissions = "0440";
};
ssl_certificate = {
keyFile = ../secrets/ssl_certificate.crt;
nginx_cert = {
keyFile = ../secrets/nginx.cert;
destDir = "/secrets/";
user = "nginx";
group = "root";
permissions = "0440";
};
lnd_key = {
keyFile = ../secrets/lnd.key;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
lnd_cert = {
keyFile = ../secrets/lnd.cert;
destDir = "/secrets/";
user = "lnd";
group = "lnd";
permissions = "0440";
};
in {
network.description = "Bitcoin Core node";
@ -61,10 +82,11 @@ in {
deployment.keys = {
inherit bitcoin-rpcpassword;
}
// (if (config.services.lnd.enable) then { inherit lnd-wallet-password lnd_key lnd_cert; } else { })
// (if (config.services.lightning-charge.enable) then { inherit lightning-charge-api-token; } else { })
// (if (config.services.nanopos.enable) then { inherit lightning-charge-api-token-for-nanopos; } else { })
// (if (config.services.liquidd.enable) then { inherit liquid-rpcpassword; } else { })
// (if (config.services.spark-wallet.enable) then { inherit spark-wallet-login; } else { })
// (if (config.services.electrs.enable) then { inherit ssl_certificate_key ssl_certificate; } else { });
// (if (config.services.electrs.enable) then { inherit nginx_key nginx_cert; } else { });
} // (bitcoin-node { inherit config pkgs; });
}

View File

@ -2,14 +2,21 @@ set -e
set -o pipefail
BITCOIND_ONION="$(cat /var/lib/onion-chef/operator/bitcoind)"
echo BITCOIND_ONION="$BITCOIND_ONION"
if [ -x "$(command -v clightning)" ]; then
CLIGHTNING_NODEID=$(sudo -u clightning lightning-cli --lightning-dir=/var/lib/clightning getinfo | jq -r '.id')
CLIGHTNING_ONION="$(cat /var/lib/onion-chef/operator/clightning)"
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735"
echo BITCOIND_ONION="$BITCOIND_ONION"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
fi
if [ -x "$(command -v lncli)" ]; then
LND_NODEID=$(sudo -u lnd lncli --tlscertpath /secrets/lnd_cert --macaroonpath /var/lib/lnd/chain/bitcoin/mainnet/admin.macaroon getinfo | jq -r '.uris[0]')
echo LND_NODEID="$LND_NODEID"
fi
NGINX_ONION_FILE=/var/lib/onion-chef/operator/nginx
if [ -e "$NGINX_ONION_FILE" ]; then

View File

@ -2,24 +2,40 @@
SECRETSFILE=secrets/secrets.nix
if [ -e "$SECRETSFILE" ]; then
echo $SECRETSFILE already exists. No new secrets were generated.
exit 1
fi
if [ ! -e "$SECRETSFILE" ]; then
echo Write secrets to $SECRETSFILE
{
echo \{
echo " bitcoinrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " lnd-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " lightning-charge-api-token = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " liquidrpcpassword = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo " spark-wallet-password = \"$(apg -m 20 -x 20 -M Ncl -n 1)\";"
echo \}
} >> $SECRETSFILE
echo Done
else
echo $SECRETSFILE already exists. Skipping.
fi
echo Generate Self-Signed Cert
openssl genrsa -out secrets/ssl_certificate_key.key 2048
openssl req -new -key secrets/ssl_certificate_key.key -out secrets/ssl_certificate.csr -subj "/C=KN"
openssl x509 -req -days 1825 -in secrets/ssl_certificate.csr -signkey secrets/ssl_certificate_key.key -out secrets/ssl_certificate.crt
if [ ! -e secrets/nginx.key ] || [ ! -e secrets/nginx.cert ]; then
echo Generate Nginx Self-Signed Cert
openssl genrsa -out secrets/nginx.key 2048
openssl req -new -key secrets/nginx.key -out secrets/nginx.csr -subj "/C=KN"
openssl x509 -req -days 1825 -in secrets/nginx.csr -signkey secrets/nginx.key -out secrets/nginx.cert
rm secrets/nginx.csr
echo Done
else
echo Nginx Cert already exists. Skipping.
fi
if [ ! -e secrets/lnd.key ] || [ ! -e secrets/lnd.cert ]; then
echo Generate LND compatible TLS Cert
openssl ecparam -genkey -name prime256v1 -out secrets/lnd.key
openssl req -config secrets/openssl.cnf -new -sha256 -key secrets/lnd.key -out secrets/lnd.csr -subj '/CN=localhost/O=lnd'
openssl req -config secrets/openssl.cnf -x509 -sha256 -days 1825 -key secrets/lnd.key -in secrets/lnd.csr -out secrets/lnd.cert
rm secrets/lnd.csr
echo Done
else
echo LND cert already exists. Skipping.
fi

32
secrets/openssl.cnf Normal file
View File

@ -0,0 +1,32 @@
[ 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