From 4a7199a3daaf0f4b9360203fd89d9e290c1d256a Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Thu, 18 Jun 2020 10:21:55 +0000 Subject: [PATCH 01/22] netns-exec: add c program to execute commands in netns c program allows executing commands in nb-bitcoind, nb-lnd, nb-liquidd (the netns's needed for operator cli scripts). --- pkgs/default.nix | 1 + pkgs/netns-exec/default.nix | 11 +++++ pkgs/netns-exec/src/Makefile | 2 + pkgs/netns-exec/src/main.c | 86 ++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 pkgs/netns-exec/default.nix create mode 100644 pkgs/netns-exec/src/Makefile create mode 100644 pkgs/netns-exec/src/main.c diff --git a/pkgs/default.nix b/pkgs/default.nix index ee95b3e..f710f04 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -10,6 +10,7 @@ liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { }; generate-secrets = pkgs.callPackage ./generate-secrets { }; nixops19_09 = pkgs.callPackage ./nixops { }; + netns-exec = pkgs.callPackage ./netns-exec { }; pinned = import ./pinned.nix; } diff --git a/pkgs/netns-exec/default.nix b/pkgs/netns-exec/default.nix new file mode 100644 index 0000000..bafd516 --- /dev/null +++ b/pkgs/netns-exec/default.nix @@ -0,0 +1,11 @@ +{ stdenv, pkgs }: + +stdenv.mkDerivation { + name = "netns-exec"; + buildInputs = [ pkgs.libcap ]; + src = ./src; + installPhase = '' + mkdir -p $out + cp main $out/netns-exec + ''; +} diff --git a/pkgs/netns-exec/src/Makefile b/pkgs/netns-exec/src/Makefile new file mode 100644 index 0000000..8fa3e95 --- /dev/null +++ b/pkgs/netns-exec/src/Makefile @@ -0,0 +1,2 @@ +main: main.c + gcc -lcap -o main main.c diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c new file mode 100644 index 0000000..aec799c --- /dev/null +++ b/pkgs/netns-exec/src/main.c @@ -0,0 +1,86 @@ +/* This program must be run with CAP_SYS_ADMIN. This can be achieved for example + * with + * # setcap CAP_SYS_ADMIN+ep ./main + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +static char *available_netns[] = { + "nb-lnd", + "nb-bitcoind", + "nb-liquidd" +}; + +int check_netns(char *netns) { + int i; + int n_available_netns = sizeof(available_netns) / sizeof(available_netns[0]); + for (i = 0; i < n_available_netns; i++) { + if (strcmp(available_netns[i], netns) == 0) { + return 1; + } + } + return 0; +} + +void print_capabilities() { + cap_t caps = cap_get_proc(); + printf("Capabilities: %s\n", cap_to_text(caps, NULL)); + cap_free(caps); +} +void drop_capabilities() { + cap_t caps = cap_get_proc(); + cap_clear(caps); + cap_set_proc(caps); + cap_free(caps); +} + +int main(int argc, char **argv) { + int fd; + char netns_path[256]; + + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + if (!check_netns(argv[1])) { + printf("Failed checking %s against available netns.\n", argv[1]); + return 1; + } + + if(snprintf(netns_path, sizeof(netns_path), "/var/run/netns/%s", argv[1]) < 0) { + printf("Failed concatenating %s to the netns path.\n", argv[1]); + return 1; + } + + fd = open(netns_path, O_RDONLY); + if (fd < 0) { + printf("Failed opening netns %s: %d, %s \n", netns_path, errno, strerror(errno)); + return 1; + } + + if (setns(fd, CLONE_NEWNET) < 0) { + printf("Failed setns %d, %s \n", errno, strerror(errno)); + return 1; + } + + /* Drop capabilities */ + #ifdef DEBUG + print_capabilities(); + #endif + drop_capabilities(); + #ifdef DEBUG + print_capabilities(); + #endif + + execvp(argv[2], &argv[2]); + return 0; +} + From e5e07b91f7c8babdf216931b3c770a99eee2baaf Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Fri, 29 May 2020 10:53:35 +0000 Subject: [PATCH 02/22] netns-isolation: netns architecture - Adds network namespace instantiation and routing architecture. - netns-isolation disabled by default. Can be enabled with configuration.nix FIXME. - Uses mkMerge to toggle certain options for non netns and netns systems. - Adds security wrapper for netns-exec which allows operator to exec with cap_sys_admin - User can select the 169.254.N.0/24 addressblock netns's are created in. - nix-bitcoin-services IpAddressAllow is amended with link-local addresses --- examples/configuration.nix | 7 ++ modules/modules.nix | 1 + modules/netns-isolation.nix | 167 +++++++++++++++++++++++++++++++ modules/nix-bitcoin-services.nix | 2 +- modules/presets/secure-node.nix | 5 + 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 modules/netns-isolation.nix diff --git a/examples/configuration.nix b/examples/configuration.nix index 7d4ff87..6ed2300 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -117,6 +117,13 @@ # `docs/usage.md`. # services.hardware-wallets.trezor = true; + ### netns-isolation (EXPERIMENTAL) + # Enable this module to use Network Namespace Isolation. This feature places + # every service in its own network namespace and only allows truly necessary + # connections between network namespaces, making sure services are isolated on + # a network-level as much as possible. + # nix-bitcoin.netns-isolation.enable = true; + # FIXME: Define your hostname. networking.hostName = "nix-bitcoin"; time.timeZone = "UTC"; diff --git a/modules/modules.nix b/modules/modules.nix index 93774c4..6eb1d04 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -14,6 +14,7 @@ ./hardware-wallets.nix ./lnd.nix ./secrets/secrets.nix + ./netns-isolation.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix new file mode 100644 index 0000000..9b558e0 --- /dev/null +++ b/modules/netns-isolation.nix @@ -0,0 +1,167 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.nix-bitcoin.netns-isolation; + + netns = builtins.mapAttrs (n: v: { + inherit (v) id; + address = "169.254.${toString cfg.addressblock}.${toString v.id}"; + availableNetns = builtins.filter isEnabled availableNetns.${n}; + }) enabledServices; + + # Symmetric netns connection matrix + # if clightning.connections = [ "bitcoind" ]; then + # availableNetns.bitcoind = [ "clighting" ]; + # and + # availableNetns.clighting = [ "bitcoind" ]; + availableNetns = let + # base = { clightning = [ "bitcoind" ]; ... } + base = builtins.mapAttrs (n: v: + builtins.filter isEnabled v.connections + ) enabledServices; + in + foldl (xs: s1: + foldl (xs: s2: + xs // { "${s2}" = xs.${s2} ++ [ s1 ]; } + ) xs cfg.services.${s1}.connections + ) base (builtins.attrNames base); + + enabledServices = filterAttrs (n: v: isEnabled n) cfg.services; + isEnabled = x: config.services.${x}.enable; + + ip = "${pkgs.iproute}/bin/ip"; + iptables = "${config.networking.firewall.package}/bin/iptables"; + + bridgeIp = "169.254.${toString cfg.addressblock}.10"; + +in { + options.nix-bitcoin.netns-isolation = { + enable = mkEnableOption "netns isolation"; + + addressblock = mkOption { + type = types.ints.u8; + default = "1"; + description = '' + Specify the N address block in 169.254.N.0/24. + ''; + }; + + services = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + id = mkOption { + # TODO: Exclude 10 + # TODO: Assert uniqueness + type = types.int; + description = '' + id for the netns, that is used for the IP address host part and + naming the interfaces. Must be unique. Must not be 10. + ''; + }; + connections = mkOption { + type = with types; listOf str; + default = []; + }; + }; + }); + }; + }; + + config = mkMerge [ + (mkIf cfg.enable { + # Prerequisites + networking.dhcpcd.denyInterfaces = [ "br0" "br-nb*" "nb-veth*" ]; + services.tor.client.socksListenAddress = "${bridgeIp}:9050"; + networking.firewall.interfaces.br0.allowedTCPPorts = [ 9050 ]; + boot.kernel.sysctl."net.ipv4.ip_forward" = true; + security.wrappers.netns-exec = { + source = "${pkgs.nix-bitcoin.netns-exec}/netns-exec"; + capabilities = "cap_sys_admin=ep"; + owner = "${config.nix-bitcoin.operatorName}"; + permissions = "u+rx,g+rx,o-rwx"; + }; + + nix-bitcoin.netns-isolation.services = { + }; + + systemd.services = { + netns-bridge = { + description = "Create bridge"; + requiredBy = [ "tor.service" ]; + before = [ "tor.service" ]; + script = '' + ${ip} link add name br0 type bridge + ${ip} link set br0 up + ${ip} addr add ${bridgeIp}/24 brd + dev br0 + ${iptables} -w -t nat -A POSTROUTING -s 169.254.${toString cfg.addressblock}.0/24 -j MASQUERADE + ''; + preStop = '' + ${iptables} -w -t nat -D POSTROUTING -s 169.254.${toString cfg.addressblock}.0/24 -j MASQUERADE + ${ip} link del br0 + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + }; + } // + (let + makeNetnsServices = n: v: let + vethName = "nb-veth-${toString v.id}"; + netnsName = "nb-${n}"; + ipNetns = "${ip} -n ${netnsName}"; + netnsIptables = "${ip} netns exec ${netnsName} ${config.networking.firewall.package}/bin/iptables"; + in { + "${n}".serviceConfig.NetworkNamespacePath = "/var/run/netns/${netnsName}"; + + "netns-${n}" = rec { + requires = [ "netns-bridge.service" ]; + after = [ "netns-bridge.service" ]; + bindsTo = [ "${n}.service" ]; + requiredBy = bindsTo; + before = bindsTo; + script = '' + ${ip} netns add ${netnsName} + ${ipNetns} link set lo up + ${ip} link add ${vethName} type veth peer name br-${vethName} + ${ip} link set ${vethName} netns ${netnsName} + ${ipNetns} addr add ${v.address}/24 dev ${vethName} + ${ip} link set br-${vethName} up + ${ipNetns} link set ${vethName} up + ${ip} link set br-${vethName} master br0 + ${ipNetns} route add default via ${bridgeIp} + ${netnsIptables} -w -P INPUT DROP + ${netnsIptables} -w -A INPUT -s 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT + '' + (optionalString (config.services.${n}.enforceTor or false)) '' + ${netnsIptables} -w -P OUTPUT DROP + ${netnsIptables} -w -A OUTPUT -d 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT + '' + concatMapStrings (otherNetns: let + other = netns.${otherNetns}; + in '' + ${netnsIptables} -w -A INPUT -s ${other.address} -j ACCEPT + ${netnsIptables} -w -A OUTPUT -d ${other.address} -j ACCEPT + '') v.availableNetns; + preStop = '' + ${ip} netns delete ${netnsName} + ${ip} link del br-${vethName} + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + ExecStartPre = "-${ip} netns delete ${netnsName}"; + }; + }; + }; + in foldl (services: n: + services // (makeNetnsServices n netns.${n}) + ) {} (builtins.attrNames netns)); + + }) + # Custom netns config option values if netns-isolation not enabled + (mkIf (!cfg.enable) { + }) + ]; +} diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix index daf1355..097567e 100644 --- a/modules/nix-bitcoin-services.nix +++ b/modules/nix-bitcoin-services.nix @@ -36,7 +36,7 @@ with lib; nodejs = { MemoryDenyWriteExecute = "false"; }; # Allow tor traffic. Allow takes precedence over Deny. allowTor = { - IPAddressAllow = "127.0.0.1/32 ::1/128"; + IPAddressAllow = "127.0.0.1/32 ::1/128 169.254.0.0/16"; }; # Allow any traffic allowAnyIP = { IPAddressAllow = "any"; }; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index ca4ab6a..ea62323 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -52,6 +52,11 @@ in { hiddenServices.sshd = mkHiddenService { port = 22; }; }; + # netns-isolation + nix-bitcoin.netns-isolation = { + addressblock = 1; + }; + # bitcoind services.bitcoind = { enable = true; From 75ca6f186c36ed1c4c63facc3d93e24ca85177ae Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:56:07 +0000 Subject: [PATCH 03/22] bitcoind: add netns - Adds bitcoind to netns-isolation.services - Adds rpcbind and rpcallowip options to allow using bitcoind with network namespaces - Adds bind option (defaults to localhost), used as target of hidden service - Makes bitcoind-import-banlist run in netns --- modules/bitcoind.nix | 24 ++++++++++++++++++++++++ modules/netns-isolation.nix | 19 +++++++++++++++++++ modules/presets/secure-node.nix | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 653c52b..94bf583 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -18,6 +18,7 @@ let ${optionalString (cfg.assumevalid != null) "assumevalid=${cfg.assumevalid}"} # Connection options + ${optionalString cfg.listen "bind=${cfg.bind}"} ${optionalString (cfg.port != null) "port=${toString cfg.port}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} listen=${if cfg.listen then "1" else "0"} @@ -30,6 +31,8 @@ let (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") (attrValues cfg.rpc.users) } + ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} ${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} ${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} @@ -68,6 +71,13 @@ in { default = "/var/lib/bitcoind"; description = "The data directory for bitcoind."; }; + bind = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + Bind to given address and always listen on it. + ''; + }; user = mkOption { type = types.str; default = "bitcoin"; @@ -117,6 +127,20 @@ in { ''; }; }; + rpcbind = mkOption { + type = types.listOf types.str; + default = [ "127.0.0.1" ]; + description = '' + Bind to given address to listen for JSON-RPC connections. + ''; + }; + rpcallowip = mkOption { + type = types.listOf types.str; + default = [ "127.0.0.1" ]; + description = '' + Allow JSON-RPC connections from specified source. + ''; + }; rpcuser = mkOption { type = types.nullOr types.str; default = "bitcoinrpc"; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 9b558e0..0793fd0 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -85,6 +85,9 @@ in { }; nix-bitcoin.netns-isolation.services = { + bitcoind = { + id = 12; + }; }; systemd.services = { @@ -107,6 +110,8 @@ in { RemainAfterExit = "yes"; }; }; + + bitcoind-import-banlist.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-bitcoind"; } // (let makeNetnsServices = n: v: let @@ -159,6 +164,20 @@ in { services // (makeNetnsServices n netns.${n}) ) {} (builtins.attrNames netns)); + # bitcoin: Custom netns configs + services.bitcoind = { + bind = netns.bitcoind.address; + rpcbind = [ + "${netns.bitcoind.address}" + "127.0.0.1" + ]; + rpcallowip = [ + "127.0.0.1" + ] ++ lib.lists.concatMap (s: [ + "${netns.${s}.address}" + ]) netns.bitcoind.availableNetns; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index ea62323..7ffdb86 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -71,7 +71,7 @@ in { addresstype = "bech32"; dbCache = 1000; }; - services.tor.hiddenServices.bitcoind = mkHiddenService { port = cfg.bitcoind.port; }; + services.tor.hiddenServices.bitcoind = mkHiddenService { port = cfg.bitcoind.port; toHost = cfg.bitcoind.bind; }; # clightning services.clightning = { From 515aae28257a2a895044552fa9292ddc10ab501f Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 18 Jun 2020 10:18:11 +0000 Subject: [PATCH 04/22] bitcoind: add netns and nonetns cli scripts nonetns script needed for bitcoind-import-banlist --- modules/bitcoind.nix | 13 +++++++++++-- modules/netns-isolation.nix | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 94bf583..fd15040 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -257,11 +257,20 @@ in { }; cli = mkOption { type = types.package; + default = cfg.cli-nonetns-exec; + description = "Binary to connect with the bitcoind instance."; + }; + # Needed because bitcoind-import-banlist already executes inside + # nb-bitcoind, hence it doesn't need netns-exec prefixed. + cli-nonetns-exec = mkOption { readOnly = true; + type = types.package; default = pkgs.writeScriptBin "bitcoin-cli" '' exec ${cfg.package}/bin/bitcoin-cli -datadir='${cfg.dataDir}' "$@" ''; - description = "Binary to connect with the bitcoind instance."; + description = '' + Binary to connect with the bitcoind instance without netns-exec. + ''; }; enforceTor = nix-bitcoin-services.enforceTor; }; @@ -321,7 +330,7 @@ in { bindsTo = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; script = '' - cd ${cfg.cli}/bin + cd ${cfg.cli-nonetns-exec}/bin # Poll until bitcoind accepts commands. This can take a long time. while ! ./bitcoin-cli getnetworkinfo &> /dev/null; do sleep 1 diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 0793fd0..9c9757e 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -176,6 +176,9 @@ in { ] ++ lib.lists.concatMap (s: [ "${netns.${s}.address}" ]) netns.bitcoind.availableNetns; + cli = pkgs.writeScriptBin "bitcoin-cli" '' + netns-exec nb-bitcoind ${config.services.bitcoind.package}/bin/bitcoin-cli -datadir='${config.services.bitcoind.dataDir}' "$@" + ''; }; }) From 65b5dab3d4a453525e4d9e6a54a631c2644004b3 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 4 Jun 2020 08:23:02 +0000 Subject: [PATCH 05/22] clightning: add announce-tor From the clightning manpage: autolisten=BOOL By default, we bind (and maybe announce) on IPv4 and IPv6 interfaces if no addr, bind-addr or announce-addr options are specified. Setting this to false disables that. We already set bind-addr by default, so autolisten had no effect. Therefore, this commit replaces autolisten with the more granular announce-addr option. For now we are Tor-only, so we only need to announce our hidden service to accept incoming connections. In the future, we can add clearnet connectivity with `addr` and route connections into our netns with NAT. --- examples/configuration.nix | 7 ++++--- modules/clightning.nix | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/configuration.nix b/examples/configuration.nix index 6ed2300..62f9312 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -38,9 +38,10 @@ # Enable this module to use clightning, a Lightning Network implementation # in C. services.clightning.enable = true; - # Enable this option to listen for incoming lightning connections. By - # default nix-bitcoin nodes offer outgoing connectivity. - # services.clightning.autolisten = true; + # Enable this option to announce our Tor Hidden Service. By default clightning + # offers outgoing functionality, but doesn't announce the Tor Hidden Service + # under which peers can reach us. + # services.clightning.announce-tor = true; ### LND # Disable clightning and uncomment the following line in order to enable lnd, diff --git a/modules/clightning.nix b/modules/clightning.nix index 1b6ffff..3bb516f 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -5,8 +5,8 @@ with lib; let cfg = config.services.clightning; inherit (config) nix-bitcoin-services; + onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []); configFile = pkgs.writeText "config" '' - autolisten=${if cfg.autolisten then "true" else "false"} network=bitcoin bitcoin-datadir=${config.services.bitcoind.dataDir} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} @@ -28,7 +28,8 @@ in { type = types.bool; default = false; description = '' - If enabled, the clightning service will listen. + Bind (and maybe announce) on IPv4 and IPv6 interfaces if no addr, + bind-addr or announce-addr options are specified. ''; }; proxy = mkOption { @@ -48,6 +49,11 @@ in { default = null; description = "Set an IP address or UNIX domain socket to listen to"; }; + announce-tor = mkOption { + type = types.bool; + default = false; + description = "Announce clightning Tor Hidden Service"; + }; bitcoin-rpcuser = mkOption { type = types.str; description = '' @@ -93,12 +99,13 @@ in { "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -" ]; + services.onion-chef.access.clightning = if cfg.announce-tor then [ "clightning" ] else []; systemd.services.clightning = { description = "Run clightningd"; path = [ pkgs.nix-bitcoin.bitcoind ]; wantedBy = [ "multi-user.target" ]; - requires = [ "bitcoind.service" ]; - after = [ "bitcoind.service" ]; + requires = [ "bitcoind.service" ] ++ onion-chef-service; + after = [ "bitcoind.service" ] ++ onion-chef-service; preStart = '' cp ${configFile} ${cfg.dataDir}/config chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}' @@ -106,6 +113,7 @@ in { rm -f ${cfg.dataDir}/bitcoin/lightning-rpc chmod 600 ${cfg.dataDir}/config echo "bitcoin-rpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/config' + ${optionalString cfg.announce-tor "echo announce-addr=$(cat /var/lib/onion-chef/clightning/clightning) >> '${cfg.dataDir}/config'"} ''; serviceConfig = nix-bitcoin-services.defaultHardening // { ExecStart = "${pkgs.nix-bitcoin.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; From ae1230e13b81bf6707ffe4572d6ccf8d6a7bef3a Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 15:31:57 +0000 Subject: [PATCH 06/22] clightning: remove bitcoin-rpcuser option Simplifies the clightning module. --- modules/clightning.nix | 8 +------- modules/presets/secure-node.nix | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/clightning.nix b/modules/clightning.nix index 3bb516f..14574c9 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -12,7 +12,7 @@ let ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} always-use-proxy=${if cfg.always-use-proxy then "true" else "false"} ${optionalString (cfg.bind-addr != null) "bind-addr=${cfg.bind-addr}"} - bitcoin-rpcuser=${cfg.bitcoin-rpcuser} + bitcoin-rpcuser=${config.services.bitcoind.rpcuser} rpc-file-mode=0660 ''; in { @@ -54,12 +54,6 @@ in { default = false; description = "Announce clightning Tor Hidden Service"; }; - bitcoin-rpcuser = mkOption { - type = types.str; - description = '' - Bitcoin RPC user - ''; - }; dataDir = mkOption { type = types.path; default = "/var/lib/clightning"; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 7ffdb86..0f5c634 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -75,7 +75,6 @@ in { # clightning services.clightning = { - bitcoin-rpcuser = cfg.bitcoind.rpcuser; proxy = cfg.tor.client.socksListenAddress; enforceTor = true; always-use-proxy = true; From 3c0c4465470fb81d91e641c5a784c2008a2bc714 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:31:38 +0000 Subject: [PATCH 07/22] clightning: add netns - Adds clightning to netns-isolation.services - Adds bitcoin-rpcconnect option to allow using clightning with network namespaces - Uses bind-addr option (defaults to localhost) as target of hidden service - Adds different bind-addr options depending on if netns-isolation is enabled or not. --- modules/clightning.nix | 6 ++++++ modules/netns-isolation.nix | 12 ++++++++++++ modules/presets/secure-node.nix | 3 +-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/clightning.nix b/modules/clightning.nix index 14574c9..16b94b3 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -12,6 +12,7 @@ let ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} always-use-proxy=${if cfg.always-use-proxy then "true" else "false"} ${optionalString (cfg.bind-addr != null) "bind-addr=${cfg.bind-addr}"} + ${optionalString (cfg.bitcoin-rpcconnect != null) "bitcoin-rpcconnect=${cfg.bitcoin-rpcconnect}"} bitcoin-rpcuser=${config.services.bitcoind.rpcuser} rpc-file-mode=0660 ''; @@ -54,6 +55,11 @@ in { default = false; description = "Announce clightning Tor Hidden Service"; }; + bitcoin-rpcconnect = mkOption { + type = types.nullOr types.str; + default = null; + description = "The bitcoind RPC host to connect to."; + }; dataDir = mkOption { type = types.path; default = "/var/lib/clightning"; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 9c9757e..8d1bce9 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -88,6 +88,10 @@ in { bitcoind = { id = 12; }; + clightning = { + id = 13; + connections = [ "bitcoind" ]; + }; }; systemd.services = { @@ -181,9 +185,17 @@ in { ''; }; + # clightning: Custom netns configs + services.clightning = mkIf config.services.clightning.enable { + bitcoin-rpcconnect = netns.bitcoind.address; + bind-addr = "${netns.clightning.address}:${toString config.services.clightning.onionport}"; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { + # clightning + services.clightning.bind-addr = "127.0.0.1:${toString config.services.clightning.onionport}"; }) ]; } diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 0f5c634..7e452ce 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -78,9 +78,8 @@ in { proxy = cfg.tor.client.socksListenAddress; enforceTor = true; always-use-proxy = true; - bind-addr = "127.0.0.1:${toString cfg.clightning.onionport}"; }; - services.tor.hiddenServices.clightning = mkHiddenService { port = cfg.clightning.onionport; }; + services.tor.hiddenServices.clightning = mkHiddenService { port = cfg.clightning.onionport; toHost = (builtins.head (builtins.split ":" cfg.clightning.bind-addr)); }; # lnd services.lnd.enforceTor = true; From f3d2aaa5d44f93fc58738d0b6e73183b1d6919ad Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 4 Jun 2020 09:30:17 +0000 Subject: [PATCH 08/22] lnd: prepare for netns and bring in line with clightning - Adds bitcoind-host, and tor-socks options to allow using with network namespaces. - Adds listen, rpclisten, and restlisten option to specify host on which to listen on for peer, rpc and rest connections respectively - Adds announce-tor option and generates Tor Hidden Service with nix instead of lnd to bring in line with clightning. WARNING: Breaking changes for Tor Hidden Service. Manual migration necessary. --- examples/configuration.nix | 4 +++ modules/lnd.nix | 57 +++++++++++++++++++++++++++------ modules/presets/secure-node.nix | 11 ++++++- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/examples/configuration.nix b/examples/configuration.nix index 62f9312..da23ca2 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -47,6 +47,10 @@ # Disable clightning and uncomment the following line in order to enable lnd, # a lightning implementation written in Go. # services.lnd.enable = true; + # Enable this option to announce our Tor Hidden Service. By default lnd + # offers outgoing functionality, but doesn't announce the Tor Hidden Service + # under which peers can reach us. + # services.lnd.announce-tor = 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 diff --git a/modules/lnd.nix b/modules/lnd.nix index ef60dcb..80c592d 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.lnd; inherit (config) nix-bitcoin-services; + onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []); secretsDir = config.nix-bitcoin.secretsDir; configFile = pkgs.writeText "lnd.conf" '' datadir=${cfg.dataDir} @@ -13,17 +14,17 @@ let tlscertpath=${secretsDir}/lnd-cert tlskeypath=${secretsDir}/lnd-key - rpclisten=localhost:${toString cfg.rpcPort} - restlisten=localhost:${toString cfg.restPort} + listen=${toString cfg.listen} + ${lib.concatMapStrings (rpclisten: "rpclisten=${rpclisten}:${toString cfg.rpcPort}\n") cfg.rpclisten} + ${lib.concatMapStrings (restlisten: "restlisten=${restlisten}:${toString cfg.restPort}\n") cfg.restlisten} bitcoin.active=1 bitcoin.node=bitcoind tor.active=true - tor.v3=true - tor.streamisolation=true - tor.privatekeypath=${cfg.dataDir}/v3_onion_private_key + ${optionalString (cfg.tor-socks != null) "tor.socks=${cfg.tor-socks}"} + bitcoind.rpchost=${cfg.bitcoind-host} bitcoind.rpcuser=${config.services.bitcoind.rpcuser} bitcoind.zmqpubrawblock=${config.services.bitcoind.zmqpubrawblock} bitcoind.zmqpubrawtx=${config.services.bitcoind.zmqpubrawtx} @@ -45,6 +46,25 @@ in { default = "/var/lib/lnd"; description = "The data directory for LND."; }; + listen = mkOption { + type = types.str; + default = "localhost"; + description = "Bind to given address to listen to peer connections"; + }; + rpclisten = mkOption { + type = types.listOf types.str; + default = [ "localhost" ]; + description = '' + Bind to given address to listen to RPC connections. + ''; + }; + restlisten = mkOption { + type = types.listOf types.str; + default = [ "localhost" ]; + description = '' + Bind to given address to listen to REST connections. + ''; + }; rpcPort = mkOption { type = types.port; default = 10009; @@ -55,6 +75,23 @@ in { default = 8080; description = "Port on which to listen for REST connections."; }; + bitcoind-host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + The host that your local bitcoind daemon is listening on. + ''; + }; + tor-socks = mkOption { + type = types.nullOr types.str; + default = null; + description = "Set a socks proxy to use to connect to Tor nodes"; + }; + announce-tor = mkOption { + type = types.bool; + default = false; + description = "Announce LND Tor Hidden Service"; + }; extraConfig = mkOption { type = types.lines; default = ""; @@ -96,19 +133,21 @@ in { ]; services.bitcoind = { - zmqpubrawblock = "tcp://127.0.0.1:28332"; - zmqpubrawtx = "tcp://127.0.0.1:28333"; + zmqpubrawblock = "tcp://${cfg.bitcoind-host}:28332"; + zmqpubrawtx = "tcp://${cfg.bitcoind-host}:28333"; }; + services.onion-chef.access.lnd = if cfg.announce-tor then [ "lnd" ] else []; systemd.services.lnd = { description = "Run LND"; path = [ pkgs.nix-bitcoin.bitcoind ]; wantedBy = [ "multi-user.target" ]; - requires = [ "bitcoind.service" ]; - after = [ "bitcoind.service" ]; + requires = [ "bitcoind.service" ] ++ onion-chef-service; + after = [ "bitcoind.service" ] ++ onion-chef-service; preStart = '' install -m600 ${configFile} '${cfg.dataDir}/lnd.conf' echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword)" >> '${cfg.dataDir}/lnd.conf' + ${optionalString cfg.announce-tor "echo externalip=$(cat /var/lib/onion-chef/lnd/lnd) >> '${cfg.dataDir}/lnd.conf'"} ''; serviceConfig = nix-bitcoin-services.defaultHardening // { ExecStart = "${cfg.package}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf"; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 7e452ce..46e9c51 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -24,6 +24,11 @@ in { default = 9735; description = "Port on which to listen for tor client connections."; }; + services.lnd.onionport = mkOption { + type = types.ints.u16; + default = 9735; + description = "Port on which to listen for tor client connections."; + }; services.electrs.onionport = mkOption { type = types.port; default = 50002; @@ -82,7 +87,11 @@ in { services.tor.hiddenServices.clightning = mkHiddenService { port = cfg.clightning.onionport; toHost = (builtins.head (builtins.split ":" cfg.clightning.bind-addr)); }; # lnd - services.lnd.enforceTor = true; + services.lnd = { + tor-socks = cfg.tor.client.socksListenAddress; + enforceTor = true; + }; + services.tor.hiddenServices.lnd = mkHiddenService { port = cfg.lnd.onionport; }; # liquidd services.liquidd = { From c55296433d4daff89ba8061447ec0519d801d267 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:34:14 +0000 Subject: [PATCH 09/22] lnd: add netns - Adds lnd to netns-isolation.services - Specifies listen option (defaults to localhost) as target of hiddenService. - Amends hardcoded lnd ip to lnd-cert WARNING: Breaking changes for lnd cert. lnd-key and lnd-cert will have to be deleted and redeployed. --- modules/netns-isolation.nix | 18 ++++++++++++++++++ modules/presets/secure-node.nix | 2 +- pkgs/generate-secrets/openssl.cnf | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 8d1bce9..f99bc84 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -92,6 +92,10 @@ in { id = 13; connections = [ "bitcoind" ]; }; + lnd = { + id = 14; + connections = [ "bitcoind" ]; + }; }; systemd.services = { @@ -191,6 +195,20 @@ in { bind-addr = "${netns.clightning.address}:${toString config.services.clightning.onionport}"; }; + # lnd: Custom netns configs + services.lnd = mkIf config.services.lnd.enable { + listen = netns.lnd.address; + rpclisten = [ + "${netns.lnd.address}" + "127.0.0.1" + ]; + restlisten = [ + "${netns.lnd.address}" + "127.0.0.1" + ]; + bitcoind-host = netns.bitcoind.address; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 46e9c51..50638aa 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -91,7 +91,7 @@ in { tor-socks = cfg.tor.client.socksListenAddress; enforceTor = true; }; - services.tor.hiddenServices.lnd = mkHiddenService { port = cfg.lnd.onionport; }; + services.tor.hiddenServices.lnd = mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; }; # liquidd services.liquidd = { diff --git a/pkgs/generate-secrets/openssl.cnf b/pkgs/generate-secrets/openssl.cnf index 66f25e4..641d13d 100644 --- a/pkgs/generate-secrets/openssl.cnf +++ b/pkgs/generate-secrets/openssl.cnf @@ -30,3 +30,5 @@ 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 From 4b8ca52647accf351afd7416253576c27424e5a5 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 18 Jun 2020 10:19:04 +0000 Subject: [PATCH 10/22] lnd: add netns cli script --- modules/lnd.nix | 1 - modules/netns-isolation.nix | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 80c592d..8e5ccc3 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -107,7 +107,6 @@ in { description = "The package providing lnd binaries."; }; cli = mkOption { - readOnly = true; default = pkgs.writeScriptBin "lncli" # Switch user because lnd makes datadir contents readable by user only '' diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index f99bc84..fb100ef 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -207,6 +207,12 @@ in { "127.0.0.1" ]; bitcoind-host = netns.bitcoind.address; + cli = pkgs.writeScriptBin "lncli" + # Switch user because lnd makes datadir contents readable by user only + '' + netns-exec nb-lnd sudo -u lnd ${config.services.lnd.package}/bin/lncli --tlscertpath ${config.nix-bitcoin.secretsDir}/lnd-cert \ + --macaroonpath '${config.services.lnd.dataDir}/chain/bitcoin/mainnet/admin.macaroon' "$@" + ''; }; }) From 672a416ede22fad6927ecfe3dfd9726a4ab0bc50 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:36:03 +0000 Subject: [PATCH 11/22] liquidd: add netns - Adds liquidd to netns-isolation.services - Adds rpcbind, rpcallowip, and mainchainrpchost options to allow using liquidd with network namespaces - Adds bind option (defaults to localhost) as target of hidden service --- modules/liquid.nix | 33 +++++++++++++++++++++++++++++++++ modules/netns-isolation.nix | 19 +++++++++++++++++++ modules/presets/secure-node.nix | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/liquid.nix b/modules/liquid.nix index 1e58cbb..64cce23 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -15,6 +15,7 @@ let ${optionalString (cfg.validatepegin != null) "validatepegin=${if cfg.validatepegin then "1" else "0"}"} # Connection options + ${optionalString cfg.listen "bind=${cfg.bind}"} ${optionalString (cfg.port != null) "port=${toString cfg.port}"} ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} listen=${if cfg.listen then "1" else "0"} @@ -25,8 +26,11 @@ let (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") (attrValues cfg.rpc.users) } + ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} ${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} ${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} + ${optionalString (cfg.mainchainrpchost != null) "mainchainrpchost=${cfg.mainchainrpchost}"} # Extra config options (from liquidd nixos service) ${cfg.extraConfig} @@ -80,6 +84,13 @@ in { default = "/var/lib/liquidd"; description = "The data directory for liquidd."; }; + bind = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + Bind to given address and always listen on it. + ''; + }; user = mkOption { type = types.str; @@ -111,6 +122,20 @@ in { }; }; + rpcbind = mkOption { + type = types.listOf types.str; + default = [ "127.0.0.1" ]; + description = '' + Bind to given address to listen for JSON-RPC connections. + ''; + }; + rpcallowip = mkOption { + type = types.listOf types.str; + default = [ "127.0.0.1" ]; + description = '' + Allow JSON-RPC connections from specified source. + ''; + }; rpcuser = mkOption { type = types.nullOr types.str; default = null; @@ -121,6 +146,14 @@ in { default = null; description = "Password for JSON-RPC connections"; }; + mainchainrpchost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The address which the daemon will try to connect to the trusted + mainchain daemon to validate peg-ins. + ''; + }; testnet = mkOption { type = types.bool; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index fb100ef..7e2968a 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -96,6 +96,10 @@ in { id = 14; connections = [ "bitcoind" ]; }; + liquidd = { + id = 15; + connections = [ "bitcoind" ]; + }; }; systemd.services = { @@ -215,6 +219,21 @@ in { ''; }; + # liquidd: Custom netns configs + services.liquidd = mkIf config.services.liquidd.enable { + bind = netns.liquidd.address; + rpcbind = [ + "${netns.liquidd.address}" + "127.0.0.1" + ]; + rpcallowip = [ + "127.0.0.1" + ] ++ lib.lists.concatMap (s: [ + "${netns.${s}.address}" + ]) netns.liquidd.availableNetns; + mainchainrpchost = netns.bitcoind.address; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 50638aa..64a7db0 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -107,7 +107,7 @@ in { enforceTor = true; port = 7042; }; - services.tor.hiddenServices.liquidd = mkHiddenService { port = cfg.liquidd.port; }; + services.tor.hiddenServices.liquidd = mkHiddenService { port = cfg.liquidd.port; toHost = cfg.liquidd.bind; }; # electrs services.electrs = { From c0b02ac93a30abd606efe3f024bc2f1bb3beb33e Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 18 Jun 2020 10:21:17 +0000 Subject: [PATCH 12/22] liquid: add netns cli script --- modules/liquid.nix | 2 -- modules/netns-isolation.nix | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/liquid.nix b/modules/liquid.nix index 64cce23..ea15e31 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -210,14 +210,12 @@ in { ''; }; cli = mkOption { - readOnly = true; default = pkgs.writeScriptBin "elements-cli" '' exec ${pkgs.nix-bitcoin.elementsd}/bin/elements-cli -datadir='${cfg.dataDir}' "$@" ''; description = "Binary to connect with the liquidd instance."; }; swap-cli = mkOption { - readOnly = true; default = pkgs.writeScriptBin "liquidswap-cli" '' exec ${pkgs.nix-bitcoin.liquid-swap}/bin/liquidswap-cli -c '${cfg.dataDir}/elements.conf' "$@" ''; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 7e2968a..73989d0 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -232,6 +232,12 @@ in { "${netns.${s}.address}" ]) netns.liquidd.availableNetns; mainchainrpchost = netns.bitcoind.address; + cli = pkgs.writeScriptBin "elements-cli" '' + netns-exec nb-liquidd ${pkgs.nix-bitcoin.elementsd}/bin/elements-cli -datadir='${config.services.liquidd.dataDir}' "$@" + ''; + swap-cli = pkgs.writeScriptBin "liquidswap-cli" '' + netns-exec nb-liquidd ${pkgs.nix-bitcoin.liquid-swap}/bin/liquidswap-cli -c '${config.services.liquidd.dataDir}/elements.conf' "$@" + ''; }; }) From d6296acabafdca807dcc10a9bdf71ecb7388988e Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Fri, 29 May 2020 11:13:50 +0000 Subject: [PATCH 13/22] electrs: add netns - Adds electrs to netns-isolation.services - Adds daemonrpc option and specifies address option to allow using electrs with network namespaces - Adds host option (defaults to localhost) as target of hidden service --- modules/electrs.nix | 18 +++++++++++++++++- modules/netns-isolation.nix | 12 ++++++++++++ modules/presets/secure-node.nix | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/electrs.nix b/modules/electrs.nix index 13b886a..a8773a6 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -17,6 +17,14 @@ in { default = "/var/lib/electrs"; description = "The data directory for electrs."; }; + # Needed until electrs tls proxy is removed + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + The host on which incoming connections arrive. + ''; + }; user = mkOption { type = types.str; default = "electrs"; @@ -44,6 +52,13 @@ in { default = 50001; description = "RPC port."; }; + daemonrpc = mkOption { + type = types.str; + default = "127.0.0.1:8332"; + description = '' + Bitcoin daemon JSONRPC 'addr:port' to connect + ''; + }; extraArgs = mkOption { type = types.separatedString " "; default = ""; @@ -97,7 +112,8 @@ in { "--jsonrpc-import --index-batch-size=10" } \ --db-dir '${cfg.dataDir}' --daemon-dir '${config.services.bitcoind.dataDir}' \ - --electrum-rpc-addr=${toString cfg.address}:${toString cfg.port} ${cfg.extraArgs} + --electrum-rpc-addr=${toString cfg.address}:${toString cfg.port} \ + --daemon-rpc-addr=${toString cfg.daemonrpc} ${cfg.extraArgs} ''; User = cfg.user; Group = cfg.group; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 73989d0..1716c7c 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -100,6 +100,11 @@ in { id = 15; connections = [ "bitcoind" ]; }; + electrs = { + id = 16; + connections = [ "bitcoind" ] + ++ ( optionals config.services.electrs.TLSProxy.enable [ "nginx" ]); + }; }; systemd.services = { @@ -240,6 +245,13 @@ in { ''; }; + # electrs: Custom netns configs + services.electrs = mkIf config.services.electrs.enable { + host = if config.services.electrs.TLSProxy.enable then netns.nginx.address else netns.electrs.address; + address = netns.electrs.address; + daemonrpc = "${netns.bitcoind.address}:${toString config.services.bitcoind.rpc.port}"; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 64a7db0..d3fcf08 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -119,6 +119,7 @@ in { services.tor.hiddenServices.electrs = mkHiddenService { port = cfg.electrs.onionport; toPort = if cfg.electrs.TLSProxy.enable then cfg.electrs.TLSProxy.port else cfg.electrs.port; + toHost = cfg.electrs.host; }; services.spark-wallet.onion-service = true; From c4ab73d51fc86434c8f3890e2622f522cac34481 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:41:13 +0000 Subject: [PATCH 14/22] spark-wallet: add netns - Adds spark-wallet to netns-isolation.services - Adds extraArgs option to allow using spark-wallet with network namespaces - Adds host option (defaults to localhost) as target of hidden service - Adds enforceTor option to bring in line with other services --- modules/netns-isolation.nix | 11 +++++++++++ modules/presets/secure-node.nix | 5 ++++- modules/spark-wallet.nix | 21 +++++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 1716c7c..d42b492 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -105,6 +105,11 @@ in { connections = [ "bitcoind" ] ++ ( optionals config.services.electrs.TLSProxy.enable [ "nginx" ]); }; + spark-wallet = { + id = 17; + # communicates with clightning over lightning-rpc socket + connections = []; + }; }; systemd.services = { @@ -252,6 +257,12 @@ in { daemonrpc = "${netns.bitcoind.address}:${toString config.services.bitcoind.rpc.port}"; }; + # spark-wallet: Custom netns configs + services.spark-wallet = mkIf config.services.spark-wallet.enable { + host = netns.spark-wallet.address; + extraArgs = "--no-tls"; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index d3fcf08..983d8fb 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -122,7 +122,10 @@ in { toHost = cfg.electrs.host; }; - services.spark-wallet.onion-service = true; + services.spark-wallet = { + onion-service = true; + enforceTor = true; + }; services.nix-bitcoin-webindex.enforceTor = true; diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index 477bdfd..5887eb0 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -7,7 +7,7 @@ let inherit (config) nix-bitcoin-services; onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []); run-spark-wallet = pkgs.writeScript "run-spark-wallet" '' - CMD="${pkgs.nix-bitcoin.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} -Q -k -c ${config.nix-bitcoin.secretsDir}/spark-wallet-login" + CMD="${pkgs.nix-bitcoin.spark-wallet}/bin/spark-wallet --ln-path ${cfg.ln-path} --host ${cfg.host} -Q -k -c ${config.nix-bitcoin.secretsDir}/spark-wallet-login ${cfg.extraArgs}" ${optionalString cfg.onion-service '' echo Getting onion hostname @@ -29,6 +29,11 @@ in { If enabled, the spark-wallet service will be installed. ''; }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "http(s) server listen address."; + }; ln-path = mkOption { type = types.path; default = "${config.services.clightning.dataDir}/bitcoin"; @@ -43,6 +48,12 @@ in { "If enabled, configures spark-wallet to be reachable through an onion service."; ''; }; + extraArgs = mkOption { + type = types.separatedString " "; + default = ""; + description = "Extra command line arguments passed to spark-wallet."; + }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -65,7 +76,7 @@ in { services.tor.client.enable = true; services.tor.hiddenServices.spark-wallet = mkIf cfg.onion-service { map = [{ - port = 80; toPort = 9737; + port = 80; toPort = 9737; toHost = cfg.host; }]; version = 3; }; @@ -82,8 +93,10 @@ in { Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = "/var/lib/onion-chef"; - } // nix-bitcoin-services.nodejs - // nix-bitcoin-services.allowTor; + } // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP) + // nix-bitcoin-services.nodejs; }; nix-bitcoin.secrets.spark-wallet-login.user = "spark-wallet"; }; From 7369f0a7ec5ec057a3360651bf2eab297fa829a0 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:43:03 +0000 Subject: [PATCH 15/22] lightning-charge: add netns - Adds lightning-charge to netns-isolation.services - Adds cfg.enforceTor to bring lightning-charge in line with other services - Adds extraArgs option to allow using lightning-charge with network namespaces - Adds host option (defaults to localhost) as target of hidden service --- modules/lightning-charge.nix | 19 ++++++++++++++++--- modules/netns-isolation.nix | 8 ++++++++ modules/presets/secure-node.nix | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index e2d4ce5..18e27ba 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -21,6 +21,17 @@ in { default = "/var/lib/lightning-charge"; description = "The data directory for lightning-charge."; }; + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "http server listen address"; + }; + extraArgs = mkOption { + type = types.separatedString " "; + default = ""; + description = "Extra command line arguments passed to lightning-charge."; + }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -60,13 +71,15 @@ in { # Needed to access clightning.dataDir in preStart PermissionsStartOnly = "true"; EnvironmentFile = "${config.nix-bitcoin.secretsDir}/lightning-charge-env"; - ExecStart = "${pkgs.nix-bitcoin.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir}/bitcoin -d ${cfg.dataDir}/lightning-charge.db"; + ExecStart = "${pkgs.nix-bitcoin.lightning-charge}/bin/charged -l ${config.services.clightning.dataDir}/bitcoin -d ${cfg.dataDir}/lightning-charge.db -i ${cfg.host} ${cfg.extraArgs}"; User = user; Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; - } // nix-bitcoin-services.nodejs - // nix-bitcoin-services.allowTor; + } // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP) + // nix-bitcoin-services.nodejs; }; nix-bitcoin.secrets.lightning-charge-env.user = user; }; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index d42b492..130fd3a 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -110,6 +110,11 @@ in { # communicates with clightning over lightning-rpc socket connections = []; }; + lightning-charge = { + id = 18; + # communicates with clightning over lightning-rpc socket + connections = []; + }; }; systemd.services = { @@ -263,6 +268,9 @@ in { extraArgs = "--no-tls"; }; + # lightning-charge: Custom netns configs + services.lightning-charge.host = mkIf config.services.lightning-charge.enable netns.lightning-charge.address; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 983d8fb..c36e4b2 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -127,6 +127,8 @@ in { enforceTor = true; }; + services.lightning-charge.enforceTor = true; + services.nix-bitcoin-webindex.enforceTor = true; From 582cb86d7409bf4481fd2fa79b6d53bc136a6a6e Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:44:50 +0000 Subject: [PATCH 16/22] nanopos: add netns - Adds nanopos to netns-isolation.services - Adds cfg.enforceTor and extraArgs to bring nanopos in line with other services - Adds charged-url option to allow using nanopos with network namespaces. - Modularizes nginx so webindex can be used without nanopos. - Adds host option (defaults to localhost) as target of hidden service - Removes unnecessary after --- modules/nanopos.nix | 42 +++++++++++++++++++++++++++++--- modules/netns-isolation.nix | 10 ++++++++ modules/nix-bitcoin-webindex.nix | 13 +--------- modules/presets/secure-node.nix | 2 ++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/modules/nanopos.nix b/modules/nanopos.nix index 91af50c..eb410d0 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -49,6 +49,26 @@ in { "The items file (see nanopos README)."; ''; }; + charged-url = mkOption { + type = types.str; + default = "http://localhost:9112"; + description = '' + "The lightning charge server url."; + ''; + }; + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + "http server listen address."; + ''; + }; + extraArgs = mkOption { + type = types.separatedString " "; + default = ""; + description = "Extra command line arguments passed to nanopos."; + }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -59,6 +79,20 @@ in { ]; environment.systemPackages = [ pkgs.nix-bitcoin.nanopos ]; + + services.nginx = { + enable = true; + virtualHosts."_" = { + root = "/var/www"; + extraConfig = '' + location /store/ { + proxy_pass http://${toString cfg.host}:${toString cfg.port}; + rewrite /store/(.*) /$1 break; + } + ''; + }; + }; + systemd.services.nanopos = { description = "Run nanopos"; wantedBy = [ "multi-user.target" ]; @@ -66,12 +100,14 @@ in { after = [ "lightning-charge.service" ]; serviceConfig = nix-bitcoin-services.defaultHardening // { EnvironmentFile = "${config.nix-bitcoin.secretsDir}/nanopos-env"; - ExecStart = "${pkgs.nix-bitcoin.nanopos}/bin/nanopos -y ${cfg.itemsFile} -p ${toString cfg.port} --show-bolt11"; + ExecStart = "${pkgs.nix-bitcoin.nanopos}/bin/nanopos -y ${cfg.itemsFile} -i ${toString cfg.host} -p ${toString cfg.port} -c ${toString cfg.charged-url} --show-bolt11 ${cfg.extraArgs}"; User = "nanopos"; Restart = "on-failure"; RestartSec = "10s"; - } // nix-bitcoin-services.nodejs - // nix-bitcoin-services.allowTor; + } // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP) + // nix-bitcoin-services.nodejs; }; users.users.nanopos = { description = "nanopos User"; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 130fd3a..bfe5040 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -115,6 +115,10 @@ in { # communicates with clightning over lightning-rpc socket connections = []; }; + nanopos = { + id = 19; + connections = [ "nginx" "lightning-charge" ]; + }; }; systemd.services = { @@ -271,6 +275,12 @@ in { # lightning-charge: Custom netns configs services.lightning-charge.host = mkIf config.services.lightning-charge.enable netns.lightning-charge.address; + # nanopos: Custom netns configs + services.nanopos = mkIf config.services.nanopos.enable { + charged-url = "http://${netns.lightning-charge.address}:9112"; + host = netns.nanopos.address; + }; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index 39ba9b9..a259ca1 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -13,11 +13,7 @@ let nix-bitcoin

-

-

- store -

-

+ ${optionalString config.services.nanopos.enable ''

store

''}

lightning node: CLIGHTNING_ID @@ -61,12 +57,6 @@ in { enable = true; virtualHosts."_" = { root = "/var/www"; - extraConfig = '' - location /store/ { - proxy_pass http://127.0.0.1:${toString config.services.nanopos.port}; - rewrite /store/(.*) /$1 break; - } - ''; }; }; services.tor.hiddenServices.nginx = { @@ -82,7 +72,6 @@ in { systemd.services.create-web-index = { description = "Get node info"; wantedBy = [ "multi-user.target" ]; - after = [ "nodeinfo.service" ]; path = with pkgs; [ config.programs.nodeinfo config.services.clightning.cli diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index c36e4b2..ba0f0bf 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -129,6 +129,8 @@ in { services.lightning-charge.enforceTor = true; + services.nanopos.enforceTor = true; + services.nix-bitcoin-webindex.enforceTor = true; From ef89607704cef17566e6b9f38e1d36eab0249001 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:46:30 +0000 Subject: [PATCH 17/22] recurring-donations: add netns - Adds recurring-donations to netns-isolation.services - Adds cfg.enforceTor to bring recurring-donations in line with other services - Removes torsocks dependency in favor of `curl --socks-hostname` --- modules/netns-isolation.nix | 5 +++++ modules/presets/secure-node.nix | 2 ++ modules/recurring-donations.nix | 9 ++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index bfe5040..6371a6c 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -119,6 +119,11 @@ in { id = 19; connections = [ "nginx" "lightning-charge" ]; }; + recurring-donations = { + id = 20; + # communicates with clightning over lightning-rpc socket + connections = []; + }; }; systemd.services = { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index ba0f0bf..d8ece9e 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -131,6 +131,8 @@ in { services.nanopos.enforceTor = true; + services.recurring-donations.enforceTor = true; + services.nix-bitcoin-webindex.enforceTor = true; diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 901c8f7..a4f9444 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -11,7 +11,7 @@ let NAME=$1 AMOUNT=$2 echo Attempting to pay $AMOUNT sat to $NAME - INVOICE=$(torsocks curl -d "satoshi_amount=$AMOUNT&payment_method=ln&id=$NAME&type=profile" -X POST https://api.tallyco.in/v1/payment/request/ | jq -r '.lightning_pay_request') 2> /dev/null + INVOICE=$(curl --socks5-hostname ${config.services.tor.client.socksListenAddress} -d "satoshi_amount=$AMOUNT&payment_method=ln&id=$NAME&type=profile" -X POST https://api.tallyco.in/v1/payment/request/ | jq -r '.lightning_pay_request') 2> /dev/null if [ -z "$INVOICE" ] || [ "$INVOICE" = "null" ]; then echo "ERROR: did not get invoice from tallycoin" return @@ -75,6 +75,7 @@ in { Random delay to add to scheduled time for donation. Default is one day. ''; }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -95,12 +96,14 @@ in { description = "Run recurring-donations"; requires = [ "clightning.service" ]; after = [ "clightning.service" ]; - path = with pkgs; [ nix-bitcoin.clightning curl torsocks sudo jq ]; + path = with pkgs; [ nix-bitcoin.clightning curl sudo jq ]; serviceConfig = nix-bitcoin-services.defaultHardening // { ExecStart = "${pkgs.bash}/bin/bash ${recurring-donations-script}"; User = "recurring-donations"; Type = "oneshot"; - } // nix-bitcoin-services.allowTor; + } // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP); }; systemd.timers.recurring-donations = { requires = [ "clightning.service" ]; From c542b92e55d06a169e018d70565cfcf0e1b5d9a4 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Wed, 10 Jun 2020 14:48:20 +0000 Subject: [PATCH 18/22] nginx: add netns - Adds nginx to netns-isolation.services - Adds host option (defaults to localhost) as target of hidden service --- modules/netns-isolation.nix | 7 +++++++ modules/nix-bitcoin-webindex.nix | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 6371a6c..22eaf12 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -124,6 +124,10 @@ in { # communicates with clightning over lightning-rpc socket connections = []; }; + nginx = { + id = 21; + connections = []; + }; }; systemd.services = { @@ -286,6 +290,9 @@ in { host = netns.nanopos.address; }; + # nginx: Custom netns configs + services.nix-bitcoin-webindex.host = mkIf config.services.nix-bitcoin-webindex.enable netns.nginx.address; + }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index a259ca1..b75ab2e 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -39,6 +39,11 @@ in { If enabled, the webindex service will be installed. ''; }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "HTTP server listen address."; + }; enforceTor = nix-bitcoin-services.enforceTor; }; @@ -61,9 +66,9 @@ in { }; services.tor.hiddenServices.nginx = { map = [{ - port = 80; + port = 80; toHost = cfg.host; } { - port = 443; + port = 443; toHost = cfg.host; }]; version = 3; }; From 25adce29e508bf9013b5dc8096d5d1926e5f9542 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 9 Jul 2020 11:08:39 +0000 Subject: [PATCH 19/22] secure-node: only mkHiddenServices if services are enabled --- modules/presets/secure-node.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index d8ece9e..269557e 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -84,14 +84,14 @@ in { enforceTor = true; always-use-proxy = true; }; - services.tor.hiddenServices.clightning = mkHiddenService { port = cfg.clightning.onionport; toHost = (builtins.head (builtins.split ":" cfg.clightning.bind-addr)); }; + services.tor.hiddenServices.clightning = mkIf cfg.clightning.enable (mkHiddenService { port = cfg.clightning.onionport; toHost = (builtins.head (builtins.split ":" cfg.clightning.bind-addr)); }); # lnd services.lnd = { tor-socks = cfg.tor.client.socksListenAddress; enforceTor = true; }; - services.tor.hiddenServices.lnd = mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; }; + services.tor.hiddenServices.lnd = mkIf cfg.lnd.enable (mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; }); # liquidd services.liquidd = { @@ -107,7 +107,7 @@ in { enforceTor = true; port = 7042; }; - services.tor.hiddenServices.liquidd = mkHiddenService { port = cfg.liquidd.port; toHost = cfg.liquidd.bind; }; + services.tor.hiddenServices.liquidd = mkIf cfg.liquidd.enable (mkHiddenService { port = cfg.liquidd.port; toHost = cfg.liquidd.bind; }); # electrs services.electrs = { @@ -116,11 +116,11 @@ in { TLSProxy.enable = true; TLSProxy.port = 50003; }; - services.tor.hiddenServices.electrs = mkHiddenService { + services.tor.hiddenServices.electrs = mkIf cfg.electrs.enable (mkHiddenService { port = cfg.electrs.onionport; toPort = if cfg.electrs.TLSProxy.enable then cfg.electrs.TLSProxy.port else cfg.electrs.port; toHost = cfg.electrs.host; - }; + }); services.spark-wallet = { onion-service = true; From 8783f38fba8ae6bc898c60b6a861c893089d5b46 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 18 Jun 2020 10:22:44 +0000 Subject: [PATCH 20/22] tests: add netns to testing framework --- test/test-script.py | 90 +++++++++++++++++++++++++++++++++++++++------ test/test.nix | 2 + 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/test/test-script.py b/test/test-script.py index 5c35f9b..785a481 100644 --- a/test/test-script.py +++ b/test/test-script.py @@ -9,6 +9,12 @@ def assert_matches(cmd, regexp): raise Exception(f"Pattern '{regexp}' not found in '{out}'") +def assert_matches_exactly(cmd, regexp): + out = succeed(cmd) + if not re.fullmatch(regexp, out): + raise Exception(f"Pattern '{regexp}' doesn't match '{out}'") + + def log_has_string(unit, str): return f"journalctl -b --output=cat -u {unit} --grep='{str}'" @@ -27,6 +33,18 @@ def assert_running(unit): if "is_interactive" in vars(): raise Exception() +# netns IP addresses +bitcoind_ip = "169.254.1.12" +clightning_ip = "169.254.1.13" +lnd_ip = "169.254.1.14" +liquidd_ip = "169.254.1.15" +electrs_ip = "169.254.1.16" +sparkwallet_ip = "169.254.1.17" +lightningcharge_ip = "169.254.1.18" +nanopos_ip = "169.254.1.19" +recurringdonations_ip = "169.254.1.20" +nginx_ip = "169.254.1.21" + ### Tests assert_running("setup-secrets") @@ -38,13 +56,15 @@ machine.wait_until_succeeds("bitcoin-cli getnetworkinfo") assert_matches("su operator -c 'bitcoin-cli getnetworkinfo' | jq", '"version"') assert_running("electrs") -machine.wait_for_open_port(4224) # prometeus metrics provider +machine.wait_until_succeeds( + "ip netns exec nb-electrs nc -z localhost 4224" +) # prometeus metrics provider # Check RPC connection to bitcoind machine.wait_until_succeeds(log_has_string("electrs", "NetworkInfo")) assert_running("nginx") # SSL stratum server via nginx. Only check for open port, no content is served here # as electrs isn't ready. -machine.wait_for_open_port(50003) +machine.wait_until_succeeds("ip netns exec nb-nginx nc -z localhost 50003") # Stop electrs from spamming the test log with 'wait for bitcoind sync' messages succeed("systemctl stop electrs") @@ -58,17 +78,23 @@ assert_matches("su operator -c 'lightning-cli getinfo' | jq", '"id"') assert_running("spark-wallet") spark_auth = re.search("login=(.*)", succeed("cat /secrets/spark-wallet-login"))[1] -machine.wait_for_open_port(9737) -assert_matches(f"curl -s {spark_auth}@localhost:9737", "Spark") +machine.wait_until_succeeds("ip netns exec nb-spark-wallet nc -z %s 9737" % sparkwallet_ip) +assert_matches( + f"ip netns exec nb-spark-wallet curl -s {spark_auth}@%s:9737" % sparkwallet_ip, "Spark" +) assert_running("lightning-charge") charge_auth = re.search("API_TOKEN=(.*)", succeed("cat /secrets/lightning-charge-env"))[1] -machine.wait_for_open_port(9112) -assert_matches(f"curl -s api-token:{charge_auth}@localhost:9112/info | jq", '"id"') +machine.wait_until_succeeds("ip netns exec nb-nanopos nc -z %s 9112" % lightningcharge_ip) +assert_matches( + f"ip netns exec nb-nanopos curl -s api-token:{charge_auth}@%s:9112/info | jq" + % lightningcharge_ip, + '"id"', +) assert_running("nanopos") -machine.wait_for_open_port(9116) -assert_matches("curl localhost:9116", "tshirt") +machine.wait_until_succeeds("ip netns exec nb-lightning-charge nc -z %s 9116" % nanopos_ip) +assert_matches("ip netns exec nb-lightning-charge curl %s:9116" % nanopos_ip, "tshirt") assert_running("onion-chef") @@ -76,13 +102,55 @@ assert_running("onion-chef") # to incomplete unit dependencies. # 'create-web-index' implicitly tests 'nodeinfo'. machine.wait_for_unit("create-web-index") -machine.wait_for_open_port(80) -assert_matches("curl localhost", "nix-bitcoin") -assert_matches("curl -L localhost/store", "tshirt") +machine.wait_until_succeeds("ip netns exec nb-nginx nc -z localhost 80") +assert_matches("ip netns exec nb-nginx curl localhost", "nix-bitcoin") +assert_matches("ip netns exec nb-nginx curl -L localhost/store", "tshirt") machine.wait_until_succeeds(log_has_string("bitcoind-import-banlist", "Importing node banlist")) assert_no_failure("bitcoind-import-banlist") +### Security tests + +ping_bitcoind = "ip netns exec nb-bitcoind ping -c 1 -w 1" +ping_nanopos = "ip netns exec nb-nanopos ping -c 1 -w 1" + +# Positive ping tests (non-exhaustive) +machine.succeed( + "%s %s &&" % (ping_bitcoind, bitcoind_ip) + + "%s %s &&" % (ping_bitcoind, clightning_ip) + + "%s %s &&" % (ping_bitcoind, liquidd_ip) + + "%s %s &&" % (ping_nanopos, lightningcharge_ip) + + "%s %s &&" % (ping_nanopos, nanopos_ip) + + "%s %s" % (ping_nanopos, nginx_ip) +) + +# Negative ping tests (non-exhaustive) +machine.fail( + "%s %s ||" % (ping_bitcoind, sparkwallet_ip) + + "%s %s ||" % (ping_bitcoind, lightningcharge_ip) + + "%s %s ||" % (ping_bitcoind, nanopos_ip) + + "%s %s ||" % (ping_bitcoind, recurringdonations_ip) + + "%s %s ||" % (ping_bitcoind, nginx_ip) + + "%s %s ||" % (ping_nanopos, bitcoind_ip) + + "%s %s ||" % (ping_nanopos, clightning_ip) + + "%s %s ||" % (ping_nanopos, lnd_ip) + + "%s %s ||" % (ping_nanopos, liquidd_ip) + + "%s %s ||" % (ping_nanopos, electrs_ip) + + "%s %s ||" % (ping_nanopos, sparkwallet_ip) + + "%s %s" % (ping_nanopos, recurringdonations_ip) +) + +# test that netns-exec can't be run for unauthorized namespace +machine.fail("netns-exec nb-electrs ip a") + +# test that netns-exec drops capabilities +assert_matches_exactly( + "su operator -c 'netns-exec nb-bitcoind capsh --print | grep Current '", "Current: =\n" +) + +# test that netns-exec can not be executed by users that are not operator +machine.fail("sudo -u clightning netns-exec nb-bitcoind ip a") + ### Additional tests # Current time in µs diff --git a/test/test.nix b/test/test.nix index d609d3d..2982f7c 100644 --- a/test/test.nix +++ b/test/test.nix @@ -16,6 +16,8 @@ import ./make-test.nix rec { # hardened ]; + nix-bitcoin.netns-isolation.enable = mkForce true; + services.bitcoind.extraConfig = mkForce "connect=0"; services.clightning.enable = true; From 43ce847e2b5c728018ac3219e29a14a36d6d25d5 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 16 Jul 2020 14:48:41 +0000 Subject: [PATCH 21/22] tests: allow running integration tests with different configurations --- test/run-tests.sh | 43 +++++++++-- test/scenarios/default.py | 77 +++++++++++++++++++ test/scenarios/lib.py | 34 ++++++++ .../withnetns.py} | 35 --------- test/test.nix | 12 ++- 5 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 test/scenarios/default.py create mode 100644 test/scenarios/lib.py rename test/{test-script.py => scenarios/withnetns.py} (85%) diff --git a/test/run-tests.sh b/test/run-tests.sh index 5d59d35..def34be 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -5,19 +5,52 @@ # # Usage: # Run test -# ./run-tests.sh +# ./run-tests.sh --scenario # # Run test and save result to avoid garbage collection -# ./run-tests.sh build --out-link /tmp/nix-bitcoin-test +# ./run-tests.sh --scenario build --out-link /tmp/nix-bitcoin-test # # Run interactive test debugging -# ./run-tests.sh debug +# ./run-tests.sh --scenario debug # # This starts the testing VM and drops you into a Python REPL where you can # manually execute the tests from ./test-script.py set -eo pipefail +die() { + printf '%s\n' "$1" >&2 + exit 1 +} + +# Initialize all the option variables. +# This ensures we are not contaminated by variables from the environment. +scenario= + +while :; do + case $1 in + --scenario) + if [ "$2" ]; then + scenario=$2 + shift + else + die 'ERROR: "--scenario" requires a non-empty option argument.' + fi + ;; + -?*) + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) + break + esac + + shift +done + +if [[ -z $scenario ]]; then + die 'ERROR: "--scenario" is required' +fi + numCPUs=${numCPUs:-$(nproc)} # Min. 800 MiB needed to avoid 'out of memory' errors memoryMiB=${memoryMiB:-2048} @@ -32,7 +65,7 @@ run() { export TMPDIR=$(mktemp -d /tmp/nix-bitcoin-test.XXX) trap "rm -rf $TMPDIR" EXIT - nix-build --out-link $TMPDIR/driver "$scriptDir/test.nix" -A driver + nix-build --out-link $TMPDIR/driver -E "import \"$scriptDir/test.nix\" { scenario = \"$scenario\"; }" -A driver # Variable 'tests' contains the Python code that is executed by the driver on startup if [[ $1 == --interactive ]]; then @@ -95,7 +128,7 @@ exprForCI() { vmTestNixExpr() { cat < Date: Thu, 16 Jul 2020 14:49:36 +0000 Subject: [PATCH 22/22] tests: run scenarios as multiple Travis jobs --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2514c0a..1942ce7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,8 @@ env: # CACHIX_SIGNING_KEY - secure: "xXCFZ7g+k5YmCGm8R8l3bZElVmt+RD1KscG3kGr5w4HyyDPTzFetPo+sT8bUpysDU0u3HWhfVhHtpog2mhNhwVl3tQwKXea3dHKC1i6ypBg3gjDngmJRR5wo++ocYDpK8qPaU7m/jHQTNFnTA4CbmMcc05GcYx/1Ai/ZGkNwWFjdIcVeOUoiol33gykMOXIGDg2qlXudt33wP53FHbX8L4fxzodWfAuxKK4AoGprxy5eSnU7LCaXxxJmu4HwuV+Ux2U1NfE/E33cvhlUvTQCswVSZFG06mg8rwhMG1ozsDvlL2itZlu/BeUQH5y3XMMlnJIUXUazkRBibf1w/ebVjpOF+anqkqmq8tcbFEa7T+RJeVTIsvP+L8rE8fcmuZtdg9hNmgRnLmaeT0vVwD1L2UqW9HdRyujdoS0jPYuoc1W7f1JQWfAPhBPQ1SrtKyNNqcbVJ34aN7b+4vCzRpQL1JTbmjzQIWhkiKN1qMo1v/wbIydW8yka4hc4JOfdQLaAJEPI1eAC1MLotSAegMnwKWE1dzm66MuPSipksYjZrvsB28cV4aCVUffIuRhrSr1i2afRHwTpNbK9U4/576hah15ftUdR79Sfkcoi1ekSQTFGRvkRIPYtkKLYwFa3jVA41qz7+IIZCf4TsApy3XDdFx91cRub7yPq9BeZ83A+qYQ=" jobs: - - TestModules=1 STABLE=1 + - TestModules=1 STABLE=1 SCENARIO=default + - TestModules=1 STABLE=1 SCENARIO=withnetns - PKG=hwi STABLE=1 - PKG=hwi STABLE=0 - PKG=lightning-charge STABLE=1 @@ -38,7 +39,7 @@ script: exit 1 fi sudo chmod go+rw /dev/kvm - test/run-tests.sh exprForCI + test/run-tests.sh --scenario $SCENARIO exprForCI else echo "(import ./. {}).$PKG" fi