diff --git a/.travis.yml b/.travis.yml index 907f19b..b0ba652 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ env: jobs: - TestModules=1 STABLE=1 SCENARIO=default - TestModules=1 STABLE=1 SCENARIO=netns - - EvalModules=1 STABLE=1 + - TestModules=1 STABLE=1 SCENARIO=netnsRegtest - PKG=hwi STABLE=1 - PKG=hwi STABLE=0 - PKG=lightning-charge STABLE=1 @@ -35,12 +35,6 @@ env: - PKG=joinmarket STABLE=0 script: - printf '%s (%s)\n' "$NIX_PATH" "$VER" - # - - | - if [[ $EvalModules ]]; then - test/run-tests.sh --scenario full eval - travis_terminate 0 - fi - | getBuildExpr() { if [[ $TestModules ]]; then diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 683b08f..c3021d1 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -39,7 +39,8 @@ let "rpcwhitelist=${user.name}:${lib.strings.concatStringsSep "," user.rpcwhitelist}"} '') (builtins.attrValues cfg.rpc.users) } - ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + rpcbind=${cfg.rpcbind} + rpcconnect=${cfg.rpcbind} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} # Wallet options @@ -149,8 +150,8 @@ in { description = "Set the number of threads to service RPC calls"; }; rpcbind = mkOption { - type = types.listOf types.str; - default = [ "127.0.0.1" ]; + type = types.str; + default = "127.0.0.1"; description = '' Bind to given address to listen for JSON-RPC connections. ''; @@ -182,7 +183,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = null; + default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; description = "Connect through SOCKS5 proxy"; }; listen = mkOption { @@ -275,17 +276,12 @@ in { description = "What type of addresses to use"; }; cli = mkOption { - type = types.package; - # Overriden on netns-isolation - default = cfg.cliBase; - description = "Binary to connect with the bitcoind instance."; - }; - cliBase = 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."; }; enforceTor = nix-bitcoin-services.enforceTor; }; @@ -341,9 +337,8 @@ in { fi ''; postStart = '' - cd ${cfg.cliBase}/bin # Poll until bitcoind accepts commands. This can take a long time. - while ! ./bitcoin-cli getnetworkinfo &> /dev/null; do + while ! ${cfg.cli}/bin/bitcoin-cli getnetworkinfo &> /dev/null; do sleep 1 done ''; @@ -368,7 +363,7 @@ in { bindsTo = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; script = '' - cd ${cfg.cliBase}/bin + cd ${cfg.cli}/bin echo "Importing node banlist..." cat ${./banlist.cli.txt} | while read line; do if ! err=$(eval "$line" 2>&1) && [[ $err != *already\ banned* ]]; then diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index f720076..46ed39e 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -112,7 +112,7 @@ in { configFile = builtins.toFile "config" '' network=${config.services.bitcoind.network} btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name} - btcrpcurl=http://${builtins.elemAt config.services.bitcoind.rpcbind 0}:${toString cfg.bitcoind.rpc.port} + btcrpcurl=http://${config.services.bitcoind.rpcbind}:${toString cfg.bitcoind.rpc.port} btcnodeendpoint=${config.services.bitcoind.bind}:8333 bind=${cfg.nbxplorer.bind} port=${toString cfg.nbxplorer.port} diff --git a/modules/clightning.nix b/modules/clightning.nix index 1b560ad..4b1c0d4 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -13,7 +13,7 @@ let ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} always-use-proxy=${if cfg.always-use-proxy then "true" else "false"} bind-addr=${cfg.bind-addr}:${toString cfg.bindport} - bitcoin-rpcconnect=${builtins.elemAt config.services.bitcoind.rpcbind 0} + bitcoin-rpcconnect=${config.services.bitcoind.rpcbind} bitcoin-rpcport=${toString config.services.bitcoind.rpc.port} bitcoin-rpcuser=${config.services.bitcoind.rpc.users.public.name} rpc-file-mode=0660 @@ -38,12 +38,12 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = null; + default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; description = "Set a socks proxy to use to connect to Tor nodes (or for all connections if *always-use-proxy* is set)"; }; always-use-proxy = mkOption { type = types.bool; - default = false; + default = cfg.enforceTor; description = '' Always use the *proxy*, even to connect to normal IP addresses (you can still connect to Unix domain sockets manually). This also disables all DNS lookups, to avoid leaking information. ''; diff --git a/modules/electrs.nix b/modules/electrs.nix index 83511fb..f1f5899 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -97,7 +97,7 @@ in { --daemon-dir='${bitcoind.dataDir}' \ --electrum-rpc-addr=${cfg.address}:${toString cfg.port} \ --monitoring-addr=${cfg.address}:${toString cfg.monitoringPort} \ - --daemon-rpc-addr=${builtins.elemAt bitcoind.rpcbind 0}:${toString bitcoind.rpc.port} \ + --daemon-rpc-addr=${bitcoind.rpcbind}:${toString bitcoind.rpc.port} \ ${cfg.extraArgs} ''; User = cfg.user; diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index d1d2248..e30dd35 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -20,7 +20,7 @@ let [BLOCKCHAIN] blockchain_source = bitcoin-rpc network = ${bitcoind.network} - rpc_host = ${builtins.elemAt bitcoind.rpcbind 0} + rpc_host = ${bitcoind.rpcbind} rpc_port = ${toString bitcoind.rpc.port} rpc_user = ${bitcoind.rpc.users.privileged.name} @@RPC_PASSWORD@@ diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index bb78f85..6a5d780 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -7,14 +7,17 @@ let inherit (config) nix-bitcoin-services; secretsDir = config.nix-bitcoin.secretsDir; network = config.services.bitcoind.network; + rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; configFile = builtins.toFile "loop.conf" '' datadir=${cfg.dataDir} network=${network} + rpclisten=${rpclisten} + restlisten=${cfg.restAddress}:${toString cfg.restPort} logdir=${cfg.dataDir}/logs tlscertpath=${secretsDir}/loop-cert tlskeypath=${secretsDir}/loop-key - lnd.host=${builtins.elemAt config.services.lnd.rpclisten 0}:${toString config.services.lnd.rpcPort} + lnd.host=${config.services.lnd.rpclisten}:${toString config.services.lnd.rpcPort} lnd.macaroondir=${config.services.lnd.networkDir} lnd.tlspath=${secretsDir}/lnd-cert @@ -25,6 +28,26 @@ let in { options.services.lightning-loop = { enable = mkEnableOption "lightning-loop"; + rpcAddress = mkOption { + type = types.str; + default = "localhost"; + description = "Address to listen for gRPC connections."; + }; + rpcPort = mkOption { + type = types.port; + default = 11010; + description = "Port to listen for gRPC connections."; + }; + restAddress = mkOption { + type = types.str; + default = cfg.rpcAddress; + description = "Address to listen for REST connections."; + }; + restPort = mkOption { + type = types.port; + default = 8081; + description = "Port to listen for REST connections."; + }; package = mkOption { type = types.package; default = pkgs.nix-bitcoin.lightning-loop; @@ -38,7 +61,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = null; + default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; description = "host:port of SOCKS5 proxy for connnecting to the loop server."; }; extraConfig = mkOption { @@ -51,13 +74,13 @@ in { }; cli = mkOption { default = pkgs.writeScriptBin "loop" '' - ${cfg.cliExec} ${cfg.package}/bin/loop \ + ${cfg.package}/bin/loop \ + --rpcserver ${rpclisten} \ --macaroonpath '${cfg.dataDir}/${network}/loop.macaroon' \ --tlscertpath '${secretsDir}/loop-cert' "$@" ''; description = "Binary to connect with the lightning-loop instance."; }; - inherit (nix-bitcoin-services) cliExec; enforceTor = nix-bitcoin-services.enforceTor; }; diff --git a/modules/liquid.nix b/modules/liquid.nix index 5e6b1fb..e315c87 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -26,11 +26,12 @@ let (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") (attrValues cfg.rpc.users) } - ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + rpcbind=${cfg.rpcbind} + rpcconnect=${cfg.rpcbind} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} ${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} ${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} - mainchainrpchost=${builtins.elemAt config.services.bitcoind.rpcbind 0} + mainchainrpchost=${config.services.bitcoind.rpcbind} mainchainrpcport=${toString config.services.bitcoind.rpc.port} mainchainrpcuser=${config.services.bitcoind.rpc.users.public.name} @@ -125,8 +126,8 @@ in { }; rpcbind = mkOption { - type = types.listOf types.str; - default = [ "127.0.0.1" ]; + type = types.str; + default = "127.0.0.1"; description = '' Bind to given address to listen for JSON-RPC connections. ''; @@ -160,7 +161,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = null; + default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; description = "Connect through SOCKS5 proxy"; }; listen = mkOption { @@ -205,17 +206,16 @@ in { cli = mkOption { readOnly = true; default = pkgs.writeScriptBin "elements-cli" '' - ${cfg.cliExec} ${pkgs.nix-bitcoin.elementsd}/bin/elements-cli -datadir='${cfg.dataDir}' "$@" + ${pkgs.nix-bitcoin.elementsd}/bin/elements-cli -datadir='${cfg.dataDir}' "$@" ''; description = "Binary to connect with the liquidd instance."; }; swapCli = mkOption { default = pkgs.writeScriptBin "liquidswap-cli" '' - ${cfg.cliExec} ${pkgs.nix-bitcoin.liquid-swap}/bin/liquidswap-cli -c '${cfg.dataDir}/elements.conf' "$@" + ${pkgs.nix-bitcoin.liquid-swap}/bin/liquidswap-cli -c '${cfg.dataDir}/elements.conf' "$@" ''; description = "Binary for managing liquid swaps."; }; - inherit (nix-bitcoin-services) cliExec; enforceTor = nix-bitcoin-services.enforceTor; }; }; diff --git a/modules/lnd.nix b/modules/lnd.nix index c60dfa9..7fb69b8 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -8,7 +8,7 @@ let secretsDir = config.nix-bitcoin.secretsDir; bitcoind = config.services.bitcoind; - bitcoindRpcAddress = builtins.elemAt bitcoind.rpcbind 0; + bitcoindRpcAddress = bitcoind.rpcbind; onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []); networkDir = "${cfg.dataDir}/chain/bitcoin/${bitcoind.network}"; configFile = pkgs.writeText "lnd.conf" '' @@ -18,14 +18,14 @@ let tlskeypath=${secretsDir}/lnd-key listen=${toString cfg.listen}:${toString cfg.listenPort} - ${lib.concatMapStrings (rpclisten: "rpclisten=${rpclisten}:${toString cfg.rpcPort}\n") cfg.rpclisten} - ${lib.concatMapStrings (restlisten: "restlisten=${restlisten}:${toString cfg.restPort}\n") cfg.restlisten} + rpclisten=${cfg.rpclisten} + restlisten=${cfg.restlisten} bitcoin.${bitcoind.network}=1 bitcoin.active=1 bitcoin.node=bitcoind - tor.active=true + ${optionalString (cfg.enforceTor) "tor.active=true"} ${optionalString (cfg.tor-socks != null) "tor.socks=${cfg.tor-socks}"} bitcoind.rpchost=${bitcoindRpcAddress}:${toString bitcoind.rpc.port} @@ -66,15 +66,15 @@ in { description = "Bind to given port to listen to peer connections"; }; rpclisten = mkOption { - type = types.listOf types.str; - default = [ "localhost" ]; + type = types.str; + default = "localhost"; description = '' Bind to given address to listen to RPC connections. ''; }; restlisten = mkOption { - type = types.listOf types.str; - default = [ "localhost" ]; + type = types.str; + default = "localhost"; description = '' Bind to given address to listen to REST connections. ''; @@ -91,7 +91,7 @@ in { }; tor-socks = mkOption { type = types.nullOr types.str; - default = null; + default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; description = "Set a socks proxy to use to connect to Tor nodes"; }; announce-tor = mkOption { @@ -138,12 +138,13 @@ in { default = pkgs.writeScriptBin "lncli" # Switch user because lnd makes datadir contents readable by user only '' - ${cfg.cliExec} sudo -u lnd ${cfg.package}/bin/lncli --tlscertpath ${secretsDir}/lnd-cert \ + sudo -u lnd ${cfg.package}/bin/lncli \ + --rpcserver ${cfg.rpclisten}:${toString cfg.rpcPort} \ + --tlscertpath '${secretsDir}/lnd-cert' \ --macaroonpath '${networkDir}/admin.macaroon' "$@" ''; description = "Binary to connect with the lnd instance."; }; - inherit (nix-bitcoin-services) cliExec; enforceTor = nix-bitcoin-services.enforceTor; }; @@ -188,12 +189,12 @@ in { RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; ExecStartPost = let - restPort = toString cfg.restPort; + restUrl = "https://${cfg.restlisten}:${toString cfg.restPort}/v1"; in [ # Run fully privileged for secrets dir write access "+${nix-bitcoin-services.script '' attempts=250 - while ! { exec 3>/dev/tcp/127.0.0.1/${restPort} && exec 3>&-; } &>/dev/null; do + while ! { exec 3>/dev/tcp/${cfg.restlisten}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } sleep 0.1 done @@ -204,7 +205,7 @@ in { umask u=r,go= ${pkgs.curl}/bin/curl -s \ --cacert ${secretsDir}/lnd-cert \ - -X GET https://127.0.0.1:${restPort}/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic" + -X GET ${restUrl}/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic" fi chown lnd: "$mnemonic" ''}" @@ -216,7 +217,7 @@ in { --cacert ${secretsDir}/lnd-cert \ -X POST -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\", \ \"cipher_seed_mnemonic\": $(cat ${secretsDir}/lnd-seed-mnemonic | tr -d '\n')}" \ - https://127.0.0.1:${restPort}/v1/initwallet + ${restUrl}/initwallet # Guarantees that RPC calls with cfg.cli succeed after the service is started echo Wait until wallet is created @@ -231,11 +232,11 @@ in { --cacert ${secretsDir}/lnd-cert \ -X POST \ -d "{\"wallet_password\": \"$(cat ${secretsDir}/lnd-wallet-password | tr -d '\n' | base64 -w0)\"}" \ - https://127.0.0.1:${restPort}/v1/unlockwallet + ${restUrl}/unlockwallet fi # Wait until the RPC port is open - while ! { exec 3>/dev/tcp/127.0.0.1/${toString cfg.rpcPort}; } &>/dev/null; do + while ! { exec 3>/dev/tcp/${cfg.rpclisten}/${toString cfg.rpcPort}; } &>/dev/null; do sleep 0.1 done @@ -251,7 +252,7 @@ in { --cacert ${secretsDir}/lnd-cert \ -X POST \ -d '{"permissions":[${cfg.macaroons.${macaroon}.permissions}]}' \ - https://127.0.0.1:${restPort}/v1/macaroon |\ + ${restUrl}/macaroon |\ ${pkgs.jq}/bin/jq -c '.macaroon' | ${pkgs.xxd}/bin/xxd -p -r > "$macaroonPath" chown ${cfg.macaroons.${macaroon}.user}: "$macaroonPath" '') (attrNames cfg.macaroons)} diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index aa664cd..9f49cef 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -50,7 +50,7 @@ in { addressblock = mkOption { type = types.ints.u8; - default = "1"; + default = 1; description = '' The address block N in 169.254.N.0/24, used as the prefix for netns addresses. ''; @@ -141,6 +141,7 @@ in { inherit (v) netnsName; ipNetns = "${ip} -n ${netnsName}"; netnsIptables = "${ip} netns exec ${netnsName} ${config.networking.firewall.package}/bin/iptables"; + allowedAddresses = concatMapStringsSep "," (available: netns.${available}.address) v.availableNetns; in { "${n}".serviceConfig.NetworkNamespacePath = "/var/run/netns/${netnsName}"; @@ -151,6 +152,7 @@ in { requiredBy = bindsTo; before = bindsTo; script = '' + ${ip} netns delete ${netnsName} 2> /dev/null || true ${ip} netns add ${netnsName} ${ipNetns} link set lo up ${ip} link add ${veth} type veth peer name ${peer} @@ -164,23 +166,19 @@ in { ${netnsIptables} -w -A INPUT -s 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT # allow return traffic to outgoing connections initiated by the service itself ${netnsIptables} -w -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT - '' + (optionalString (config.services.${n}.enforceTor or false)) '' + '' + 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; + '' + optionalString (v.availableNetns != []) '' + ${netnsIptables} -w -A INPUT -s ${allowedAddresses} -j ACCEPT + ${netnsIptables} -w -A OUTPUT -d ${allowedAddresses} -j ACCEPT + ''; preStop = '' ${ip} netns delete ${netnsName} - ${ip} link del ${peer} ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = "yes"; - ExecStartPre = "-${ip} netns delete ${netnsName}"; }; }; }; @@ -252,18 +250,11 @@ in { services.bitcoind = { bind = netns.bitcoind.address; - rpcbind = [ - "${netns.bitcoind.address}" - "127.0.0.1" - ]; + rpcbind = netns.bitcoind.address; rpcallowip = [ - "127.0.0.1" - ] ++ map (n: "${netns.${n}.address}") netns.bitcoind.availableNetns; - cli = let - inherit (config.services.bitcoind) cliBase; - in pkgs.writeScriptBin cliBase.name '' - exec netns-exec ${netns.bitcoind.netnsName} ${cliBase}/bin/${cliBase.name} "$@" - ''; + bridgeIp # For operator user + netns.bitcoind.address + ] ++ map (n: netns.${n}.address) netns.bitcoind.availableNetns; }; systemd.services.bitcoind-import-banlist.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-bitcoind"; @@ -271,27 +262,17 @@ in { services.lnd = { listen = netns.lnd.address; - rpclisten = [ - "${netns.lnd.address}" - "127.0.0.1" - ]; - restlisten = [ - "${netns.lnd.address}" - "127.0.0.1" - ]; - cliExec = mkCliExec "lnd"; + rpclisten = netns.lnd.address; + restlisten = netns.lnd.address; }; services.liquidd = { bind = netns.liquidd.address; - rpcbind = [ - "${netns.liquidd.address}" - "127.0.0.1" - ]; + rpcbind = netns.liquidd.address; rpcallowip = [ - "127.0.0.1" - ] ++ map (n: "${netns.${n}.address}") netns.liquidd.availableNetns; - cliExec = mkCliExec "liquidd"; + bridgeIp # For operator user + netns.liquidd.address + ] ++ map (n: netns.${n}.address) netns.liquidd.availableNetns; }; services.electrs.address = netns.electrs.address; @@ -308,7 +289,7 @@ in { host = netns.nanopos.address; }; - services.lightning-loop.cliExec = mkCliExec "lightning-loop"; + services.lightning-loop.rpcAddress = netns.lightning-loop.address; services.nbxplorer.bind = netns.nbxplorer.address; services.btcpayserver.bind = netns.btcpayserver.address; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index f7320e6..0decc67 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -49,17 +49,11 @@ in { hiddenServices.sshd = mkHiddenService { port = 22; }; }; - # netns-isolation - nix-bitcoin.netns-isolation = { - addressblock = 1; - }; - # bitcoind services.bitcoind = { enable = true; listen = true; dataDirReadableByGroup = mkIf cfg.electrs.high-memory true; - proxy = cfg.tor.client.socksListenAddress; enforceTor = true; port = 8333; assumevalid = "00000000000000000000e5abc3a74fe27dc0ead9c70ea1deb456f11c15fd7bc6"; @@ -74,11 +68,7 @@ in { services.tor.hiddenServices.bitcoind = mkHiddenService { port = cfg.bitcoind.port; toHost = cfg.bitcoind.bind; }; # clightning - services.clightning = { - proxy = cfg.tor.client.socksListenAddress; - enforceTor = true; - always-use-proxy = true; - }; + services.clightning.enforceTor = true; services.tor.hiddenServices.clightning = mkIf cfg.clightning.enable (mkHiddenService { port = cfg.clightning.onionport; toHost = cfg.clightning.bind-addr; @@ -86,17 +76,11 @@ in { }); # lnd - services.lnd = { - tor-socks = cfg.tor.client.socksListenAddress; - enforceTor = true; - }; + services.lnd.enforceTor = true; services.tor.hiddenServices.lnd = mkIf cfg.lnd.enable (mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; toPort = cfg.lnd.listenPort; }); # lightning-loop - services.lightning-loop = { - proxy = cfg.tor.client.socksListenAddress; - enforceTor = true; - }; + services.lightning-loop.enforceTor = true; # liquidd services.liquidd = { @@ -104,7 +88,6 @@ in { prune = 1000; validatepegin = true; listen = true; - proxy = cfg.tor.client.socksListenAddress; enforceTor = true; port = 7042; }; diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 67c75b2..e86e6af 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -10,10 +10,6 @@ #include static char *allowed_netns[] = { - "nb-lnd", - "nb-lightning-loop", - "nb-bitcoind", - "nb-liquidd", "nb-joinmarket" }; diff --git a/test/run-tests.sh b/test/run-tests.sh index 0776b40..dd9845e 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -169,12 +169,12 @@ EOF } # A basic subset of tests to keep the total runtime within -# manageable bounds (<3 min on desktop systems). +# manageable bounds (<4 min on desktop systems). # These are also run on the CI server. basic() { scenario=default buildTest "$@" scenario=netns buildTest "$@" - scenario=full evalTest "$@" + scenario=netnsRegtest buildTest "$@" } all() { @@ -182,6 +182,7 @@ all() { scenario=netns buildTest "$@" scenario=full buildTest "$@" scenario=regtest buildTest "$@" + scenario=netnsRegtest buildTest "$@" } build() { diff --git a/test/tests.nix b/test/tests.nix index 634b97f..07fe9b6 100644 --- a/test/tests.nix +++ b/test/tests.nix @@ -111,11 +111,7 @@ let testEnv = rec { }; netns = { - imports = [ scenarios.secureNode ]; - nix-bitcoin.netns-isolation.enable = true; - test.data.netns = config.nix-bitcoin.netns-isolation.netns; - tests.netns-isolation = true; - + imports = with scenarios; [ netnsBase secureNode ]; # This test is rather slow and unaffected by netns settings tests.backups = mkForce false; }; @@ -132,6 +128,18 @@ let testEnv = rec { services.joinmarket.enable = true; }; + # netns and regtest, without secure-node.nix + netnsRegtest = { + imports = with scenarios; [ netnsBase regtest ]; + }; + + netnsBase = { + nix-bitcoin.netns-isolation.enable = true; + test.data.netns = config.nix-bitcoin.netns-isolation.netns; + tests.netns-isolation = true; + environment.systemPackages = [ pkgs.fping ]; + }; + regtestBase = { tests.regtest = true; diff --git a/test/tests.py b/test/tests.py index ea3b3ad..0179034 100644 --- a/test/tests.py +++ b/test/tests.py @@ -237,50 +237,46 @@ def _(): # (and their corresponding network namespaces). @test("netns-isolation") def _(): - ping_bitcoind = "ip netns exec nb-bitcoind ping -c 1 -w 1" - ping_nanopos = "ip netns exec nb-nanopos ping -c 1 -w 1" - ping_nbxplorer = "ip netns exec nb-nbxplorer ping -c 1 -w 1" + def get_ips(services): + enabled = enabled_tests.intersection(services) + return " ".join(ip(service) for service in enabled) - # Positive ping tests (non-exhaustive) - machine.succeed( - "%s %s &&" % (ping_bitcoind, ip("bitcoind")) - + "%s %s &&" % (ping_bitcoind, ip("clightning")) - + "%s %s &&" % (ping_bitcoind, ip("lnd")) - + "%s %s &&" % (ping_bitcoind, ip("liquidd")) - + "%s %s &&" % (ping_bitcoind, ip("nbxplorer")) - + "%s %s &&" % (ping_nbxplorer, ip("btcpayserver")) - + "%s %s &&" % (ping_nanopos, ip("lightning-charge")) - + "%s %s &&" % (ping_nanopos, ip("nanopos")) - + "%s %s" % (ping_nanopos, ip("nginx")) + def assert_reachable(src, dests): + dest_ips = get_ips(dests) + if src in enabled_tests and dest_ips: + machine.succeed(f"ip netns exec nb-{src} fping -c1 -t100 {dest_ips}") + + def assert_unreachable(src, dests): + dest_ips = get_ips(dests) + if src in enabled_tests and dest_ips: + machine.fail( + # This fails when no host is reachable within 100 ms + f"ip netns exec nb-{src} fping -c1 -t100 --reachable=1 {dest_ips}" + ) + + # These reachability tests are non-exhaustive + assert_reachable("bitcoind", ["clightning", "lnd", "liquidd"]) + assert_unreachable("bitcoind", ["btcpayserver", "spark-wallet", "lightning-loop"]) + assert_unreachable("btcpayserver", ["bitcoind", "lightning-loop", "liquidd"]) + + # netns addresses can not be bound to in the main netns. + # This prevents processes in the main netns from impersonating nix-bitcoin services. + assert_matches( + f"nc -l {ip('bitcoind')} 1080 2>&1 || true", "nc: Cannot assign requested address" ) - # Negative ping tests (non-exhaustive) - machine.fail( - "%s %s ||" % (ping_bitcoind, ip("spark-wallet")) - + "%s %s ||" % (ping_bitcoind, ip("lightning-loop")) - + "%s %s ||" % (ping_bitcoind, ip("lightning-charge")) - + "%s %s ||" % (ping_bitcoind, ip("nanopos")) - + "%s %s ||" % (ping_bitcoind, ip("nginx")) - + "%s %s ||" % (ping_nanopos, ip("bitcoind")) - + "%s %s ||" % (ping_nanopos, ip("clightning")) - + "%s %s ||" % (ping_nanopos, ip("lnd")) - + "%s %s ||" % (ping_nanopos, ip("lightning-loop")) - + "%s %s ||" % (ping_nanopos, ip("liquidd")) - + "%s %s ||" % (ping_nanopos, ip("electrs")) - + "%s %s ||" % (ping_nanopos, ip("spark-wallet")) - + "%s %s" % (ping_nanopos, ip("btcpayserver")) - ) + if "joinmarket" in enabled_tests: + # netns-exec should drop capabilities + assert_full_match( + "su operator -c 'netns-exec nb-joinmarket capsh --print | grep Current'", "Current: =\n" + ) - # test that netns-exec can't be run for unauthorized namespace - machine.fail("netns-exec nb-electrs ip a") + if "clightning" in enabled_tests: + # netns-exec should fail for unauthorized namespaces + machine.fail("netns-exec nb-clightning ip a") - # test that netns-exec drops capabilities - assert_full_match( - "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") + # netns-exec should only be executable by the operator user + machine.fail("sudo -u clightning netns-exec nb-bitcoind ip a") # Impure: stops bitcoind (and dependent services) @@ -347,7 +343,7 @@ def _(): machine.wait_until_succeeds( log_has_string("lightning-loop", "Starting event loop at height 10") ) - succeed("sudo -u operator loop getparams | jq -e '.rules'") + succeed("sudo -u operator loop getparams") if "netns-isolation" in enabled_tests: