From 9b0753135cfb0ca16df61a48fd157d84b5543a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20D=2E=20Mih=C4=83il=C4=83?= Date: Mon, 5 Aug 2019 10:44:38 +0200 Subject: [PATCH] Add LND support --- configuration.nix | 9 +++ modules/bitcoind.nix | 14 +++- modules/default.nix | 1 + modules/lnd.nix | 128 +++++++++++++++++++++++++++++++ modules/nix-bitcoin-pkgs.nix | 1 + modules/nix-bitcoin-services.nix | 3 - modules/nix-bitcoin.nix | 39 ++++++++-- network/network.nix | 22 ++++++ pkgs/nodeinfo/nodeinfo.sh | 21 +++-- secrets/generate_secrets.sh | 8 ++ secrets/openssl.cnf | 32 ++++++++ 11 files changed, 261 insertions(+), 17 deletions(-) create mode 100644 modules/lnd.nix create mode 100644 secrets/openssl.cnf diff --git a/configuration.nix b/configuration.nix index 5bbed3b..4cd716d 100644 --- a/configuration.nix +++ b/configuration.nix @@ -30,6 +30,15 @@ # 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. In order to do so, you can run the following command after the + # lnd service starts: + # nixops scp --from bitcoin-node /secrets/lnd-seed-mnemonic ./secrets/lnd-seed-mnemonic + ### 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. diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 04d5aee..0751b71 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -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 - ); + ) // optionals config.services.lnd.enable nix-bitcoin-services.allowAnyProtocol; # FOR ZMQ }; systemd.services.bitcoind-import-banlist = { description = "Bitcoin daemon banlist importer"; diff --git a/modules/default.nix b/modules/default.nix index c4f0079..621ab8f 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -11,4 +11,5 @@ nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix; spark-wallet = ./spark-wallet.nix; recurring-donations = ./recurring-donations.nix; + lnd = ./lnd.nix; } diff --git a/modules/lnd.nix b/modules/lnd.nix new file mode 100644 index 0000000..831c680 --- /dev/null +++ b/modules/lnd.nix @@ -0,0 +1,128 @@ +{ 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} + 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 + + 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 + +${pkgs.coreutils}/bin/sleep 5 + +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 GET https://127.0.0.1:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /secrets/lnd-seed-mnemonic + + ${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 + + ${pkgs.coreutils}/bin/echo wallet created +else + ${pkgs.coreutils}/bin/echo unlocking wallet + + ${pkgs.curl}/bin/curl -s \ + -H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 1000 ${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 + + ${pkgs.coreutils}/bin/echo wallet unlocked +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 lnd.conf."; + }; + 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 + }; + }; +} diff --git a/modules/nix-bitcoin-pkgs.nix b/modules/nix-bitcoin-pkgs.nix index 918a257..b88274c 100644 --- a/modules/nix-bitcoin-pkgs.nix +++ b/modules/nix-bitcoin-pkgs.nix @@ -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 { }; }; } diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix index fd43bde..2caee5b 100644 --- a/modules/nix-bitcoin-services.nix +++ b/modules/nix-bitcoin-services.nix @@ -42,6 +42,3 @@ in ''; }; } - - - diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 530a347..43152e0 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -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,32 +103,51 @@ in { version = 3; }; + # lnd + services.lnd.enforceTor = true; + services.tor.hiddenServices.lnd = { + map = [{ + port = 9735; toPort = 9735; + }]; + version = 3; + }; + # 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 [ ]); }; # Give operator access to onion hostnames services.onion-chef.enable = true; - services.onion-chef.access.operator = [ "bitcoind" "clightning" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ]; + services.onion-chef.access.operator = [ "bitcoind" "clightning" "lnd" "nginx" "liquidd" "spark-wallet" "electrs" "sshd" ]; environment.interactiveShellInit = '' alias bitcoin-cli='bitcoin-cli -datadir=${config.services.bitcoind.dataDir}' - alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}' - '' + (if config.services.liquidd.enable then '' - alias elements-cli='elements-cli -datadir=${config.services.liquidd.dataDir}' - alias liquidswap-cli='liquidswap-cli -c ${config.services.liquidd.dataDir}/elements.conf' - '' else ""); + ${optionalString (config.services.clightning.enable) '' + alias lightning-cli='sudo -u clightning lightning-cli --lightning-dir=${config.services.clightning.dataDir}' + ''} + ${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' + ''} + ''; # 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 +202,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] diff --git a/network/network.nix b/network/network.nix index 8195f5b..96997ca 100644 --- a/network/network.nix +++ b/network/network.nix @@ -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/"; @@ -50,6 +57,20 @@ let 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,6 +82,7 @@ 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 { }) diff --git a/pkgs/nodeinfo/nodeinfo.sh b/pkgs/nodeinfo/nodeinfo.sh index 62529fa..a013fd3 100644 --- a/pkgs/nodeinfo/nodeinfo.sh +++ b/pkgs/nodeinfo/nodeinfo.sh @@ -2,14 +2,21 @@ set -e set -o pipefail BITCOIND_ONION="$(cat /var/lib/onion-chef/operator/bitcoind)" -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" + +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 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 diff --git a/secrets/generate_secrets.sh b/secrets/generate_secrets.sh index 18890da..bc56859 100755 --- a/secrets/generate_secrets.sh +++ b/secrets/generate_secrets.sh @@ -11,6 +11,7 @@ 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)\";" @@ -24,3 +25,10 @@ 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 + +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 diff --git a/secrets/openssl.cnf b/secrets/openssl.cnf new file mode 100644 index 0000000..66f25e4 --- /dev/null +++ b/secrets/openssl.cnf @@ -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