From 5e0e16529c573e8e377fe7ae87bb873a286db51b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Tue, 20 Oct 2020 18:20:36 +0200 Subject: [PATCH 01/19] netns: fix default addressblock value type Also remove redundant definition in secure-node.nix --- modules/netns-isolation.nix | 2 +- modules/presets/secure-node.nix | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index aa664cd..a2578c8 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. ''; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index f7320e6..1ff4103 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -49,11 +49,6 @@ in { hiddenServices.sshd = mkHiddenService { port = 22; }; }; - # netns-isolation - nix-bitcoin.netns-isolation = { - addressblock = 1; - }; - # bitcoind services.bitcoind = { enable = true; From bae1b7f41331073d59a199cd1835e2c5a9889b49 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:24 +0100 Subject: [PATCH 02/19] netns test: improve ping test - Use fping for pinging multiple hosts in parallel. Significantly improves test runtime: >13 s -> ~200 ms for the negative ping tests. - Only test network namespaces that are enabled. This allows running the netns test with a reduced service set for debugging. - Remove deprecated services, instead add btcpayserver, spark-wallet --- test/tests.nix | 1 + test/tests.py | 50 +++++++++++++++++++------------------------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/test/tests.nix b/test/tests.nix index 634b97f..9d2e3a8 100644 --- a/test/tests.nix +++ b/test/tests.nix @@ -115,6 +115,7 @@ let testEnv = rec { nix-bitcoin.netns-isolation.enable = true; test.data.netns = config.nix-bitcoin.netns-isolation.netns; tests.netns-isolation = true; + environment.systemPackages = [ pkgs.fping ]; # This test is rather slow and unaffected by netns settings tests.backups = mkForce false; diff --git a/test/tests.py b/test/tests.py index ea3b3ad..931d1ce 100644 --- a/test/tests.py +++ b/test/tests.py @@ -237,39 +237,27 @@ 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}") - # 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")) - ) + 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"]) # test that netns-exec can't be run for unauthorized namespace machine.fail("netns-exec nb-electrs ip a") From 9a931483b9a4be260b17b63e20eda829bad1281b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:25 +0100 Subject: [PATCH 03/19] netns test: remove strict dependency on clightning, electrs This allows the netns test to be run with a reduced service set for debugging. --- test/tests.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/tests.py b/test/tests.py index 931d1ce..e2f918e 100644 --- a/test/tests.py +++ b/test/tests.py @@ -259,16 +259,17 @@ def _(): assert_unreachable("bitcoind", ["btcpayserver", "spark-wallet", "lightning-loop"]) assert_unreachable("btcpayserver", ["bitcoind", "lightning-loop", "liquidd"]) - # 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 + # netns-exec should drop 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") + if "clightning" in enabled_tests: + # netns-exec should fail for unauthorized namespaces + machine.fail("netns-exec nb-clightning 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) From 0cc8caa737a6ac345af147d68f06cfa9d6bba262 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:26 +0100 Subject: [PATCH 04/19] lnd: only set tor.active on enforceTor This also enables the test scenario 'netnsRegtest' introduced in a later commit by fixing the following bug: For unknown reasons, when tor.active=true and tor is not running, lnd fails with a tor connection error on netns-isolation, but runs fine without netns-isolation. --- modules/lnd.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index c60dfa9..143af5e 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -25,7 +25,7 @@ let 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} From e0675cb2564e5eeb41737554c27171c7bac0df1b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:27 +0100 Subject: [PATCH 05/19] move enforceTor logic to service modules This enables tor support for services without using secure-node.nix --- modules/bitcoind.nix | 2 +- modules/clightning.nix | 4 ++-- modules/lightning-loop.nix | 2 +- modules/liquid.nix | 2 +- modules/lnd.nix | 2 +- modules/presets/secure-node.nix | 18 +++--------------- 6 files changed, 9 insertions(+), 21 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 683b08f..1f3ed98 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -182,7 +182,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 { diff --git a/modules/clightning.nix b/modules/clightning.nix index 1b560ad..7f50901 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -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/lightning-loop.nix b/modules/lightning-loop.nix index bb78f85..f9f8818 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -38,7 +38,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 { diff --git a/modules/liquid.nix b/modules/liquid.nix index 5e6b1fb..8f3c2d9 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -160,7 +160,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 { diff --git a/modules/lnd.nix b/modules/lnd.nix index 143af5e..7e52fd8 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -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 { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 1ff4103..0decc67 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -54,7 +54,6 @@ in { enable = true; listen = true; dataDirReadableByGroup = mkIf cfg.electrs.high-memory true; - proxy = cfg.tor.client.socksListenAddress; enforceTor = true; port = 8333; assumevalid = "00000000000000000000e5abc3a74fe27dc0ead9c70ea1deb456f11c15fd7bc6"; @@ -69,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; @@ -81,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 = { @@ -99,7 +88,6 @@ in { prune = 1000; validatepegin = true; listen = true; - proxy = cfg.tor.client.socksListenAddress; enforceTor = true; port = 7042; }; From 0e2ff948d3223bf1ac9d6d6f7dbc22ab43aee60c Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:28 +0100 Subject: [PATCH 06/19] test: add scenario 'netnsRegtest' The 'basic' test command now cover regtest mode and using nix-bitcoin without the secure-node preset. --- .travis.yml | 8 +------- test/run-tests.sh | 5 +++-- test/tests.nix | 21 ++++++++++++++------- 3 files changed, 18 insertions(+), 16 deletions(-) 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/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 9d2e3a8..436536d 100644 --- a/test/tests.nix +++ b/test/tests.nix @@ -111,12 +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; - environment.systemPackages = [ pkgs.fping ]; - + imports = with scenarios; [ netnsBase secureNode ]; # This test is rather slow and unaffected by netns settings tests.backups = mkForce false; }; @@ -133,12 +128,24 @@ 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; services.bitcoind.regtest = true; systemd.services.bitcoind.postStart = mkAfter '' - cli=${config.services.bitcoind.cli}/bin/bitcoin-cli + cli=${config.services.bitcoind.cliBase}/bin/bitcoin-cli address=$($cli getnewaddress) $cli generatetoaddress 10 $address ''; From 58d24e735de29fb76646b4032b53e82ae62ed9fa Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:29 +0100 Subject: [PATCH 07/19] netns-bitcoind: allow RPC access from main netns --- modules/bitcoind.nix | 13 ++++--------- modules/netns-isolation.nix | 15 ++++----------- pkgs/netns-exec/src/main.c | 1 - test/tests.nix | 2 +- test/tests.py | 9 +++++---- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 1f3ed98..14930ce 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -40,6 +40,7 @@ let '') (builtins.attrValues cfg.rpc.users) } ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + rpcconnect=${builtins.elemAt cfg.rpcbind 0} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} # Wallet options @@ -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/netns-isolation.nix b/modules/netns-isolation.nix index a2578c8..ca6bd0d 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -252,18 +252,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"; diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 67c75b2..8b1d15a 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -12,7 +12,6 @@ static char *allowed_netns[] = { "nb-lnd", "nb-lightning-loop", - "nb-bitcoind", "nb-liquidd", "nb-joinmarket" }; diff --git a/test/tests.nix b/test/tests.nix index 436536d..07fe9b6 100644 --- a/test/tests.nix +++ b/test/tests.nix @@ -145,7 +145,7 @@ let testEnv = rec { services.bitcoind.regtest = true; systemd.services.bitcoind.postStart = mkAfter '' - cli=${config.services.bitcoind.cliBase}/bin/bitcoin-cli + cli=${config.services.bitcoind.cli}/bin/bitcoin-cli address=$($cli getnewaddress) $cli generatetoaddress 10 $address ''; diff --git a/test/tests.py b/test/tests.py index e2f918e..2d56f8d 100644 --- a/test/tests.py +++ b/test/tests.py @@ -259,10 +259,11 @@ def _(): assert_unreachable("bitcoind", ["btcpayserver", "spark-wallet", "lightning-loop"]) assert_unreachable("btcpayserver", ["bitcoind", "lightning-loop", "liquidd"]) - # netns-exec should drop capabilities - assert_full_match( - "su operator -c 'netns-exec nb-bitcoind capsh --print | grep Current '", "Current: =\n" - ) + 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" + ) if "clightning" in enabled_tests: # netns-exec should fail for unauthorized namespaces From 82f4901880e5d8504c8f7d805f4597c0b2c2c6a9 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:30 +0100 Subject: [PATCH 08/19] netns-lnd: allow RPC access from main netns --- modules/lnd.nix | 19 ++++++++++--------- modules/netns-isolation.nix | 11 ++--------- pkgs/netns-exec/src/main.c | 1 - 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/modules/lnd.nix b/modules/lnd.nix index 7e52fd8..ae8f1a6 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -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 ${builtins.elemAt cfg.rpclisten 0}:${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://${builtins.elemAt cfg.restlisten 0}:${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/${builtins.elemAt cfg.restlisten 0}/${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/${builtins.elemAt cfg.rpclisten 0}/${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 ca6bd0d..6c6be44 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -264,15 +264,8 @@ 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 = { diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 8b1d15a..0f31e44 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -10,7 +10,6 @@ #include static char *allowed_netns[] = { - "nb-lnd", "nb-lightning-loop", "nb-liquidd", "nb-joinmarket" From 6903e8afcce3af2689abe2a8c2454dc0c7774a5a Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:31 +0100 Subject: [PATCH 09/19] netns-liquidd: allow RPC access from main netns --- modules/liquid.nix | 6 +++--- modules/netns-isolation.nix | 11 ++++------- pkgs/netns-exec/src/main.c | 1 - 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/modules/liquid.nix b/modules/liquid.nix index 8f3c2d9..15f296b 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -27,6 +27,7 @@ let (attrValues cfg.rpc.users) } ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} + rpcconnect=${builtins.elemAt cfg.rpcbind 0} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} ${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"} ${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"} @@ -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/netns-isolation.nix b/modules/netns-isolation.nix index 6c6be44..aa32e2c 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -270,14 +270,11 @@ in { 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; diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 0f31e44..916de95 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -11,7 +11,6 @@ static char *allowed_netns[] = { "nb-lightning-loop", - "nb-liquidd", "nb-joinmarket" }; From 8b053326ccb9b08b585d65f54ad12ddbf86c1ff2 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:32 +0100 Subject: [PATCH 10/19] bitcoind: use type str for rpcbind Extra RPC bind addresses can still be added via extraConfig. --- modules/bitcoind.nix | 8 ++++---- modules/btcpayserver.nix | 2 +- modules/clightning.nix | 2 +- modules/electrs.nix | 2 +- modules/joinmarket.nix | 2 +- modules/liquid.nix | 2 +- modules/lnd.nix | 2 +- modules/netns-isolation.nix | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 14930ce..c3021d1 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -39,8 +39,8 @@ let "rpcwhitelist=${user.name}:${lib.strings.concatStringsSep "," user.rpcwhitelist}"} '') (builtins.attrValues cfg.rpc.users) } - ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} - rpcconnect=${builtins.elemAt cfg.rpcbind 0} + rpcbind=${cfg.rpcbind} + rpcconnect=${cfg.rpcbind} ${lib.concatMapStrings (rpcallowip: "rpcallowip=${rpcallowip}\n") cfg.rpcallowip} # Wallet options @@ -150,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. ''; 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 7f50901..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 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/liquid.nix b/modules/liquid.nix index 15f296b..8d59b72 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -31,7 +31,7 @@ let ${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} diff --git a/modules/lnd.nix b/modules/lnd.nix index ae8f1a6..b4eac5b 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" '' diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index aa32e2c..12c61be 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -252,7 +252,7 @@ in { services.bitcoind = { bind = netns.bitcoind.address; - rpcbind = [ netns.bitcoind.address ]; + rpcbind = netns.bitcoind.address; rpcallowip = [ bridgeIp # For operator user netns.bitcoind.address From de23fdd37780406c9b09e9f0528d8f7ae7cf1a76 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:33 +0100 Subject: [PATCH 11/19] lnd: use type str for rpclisten, restlisten --- modules/lightning-loop.nix | 2 +- modules/lnd.nix | 20 ++++++++++---------- modules/netns-isolation.nix | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index f9f8818..cf914f4 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -14,7 +14,7 @@ let 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 diff --git a/modules/lnd.nix b/modules/lnd.nix index b4eac5b..7fb69b8 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -18,8 +18,8 @@ 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 @@ -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. ''; @@ -139,7 +139,7 @@ in { # Switch user because lnd makes datadir contents readable by user only '' sudo -u lnd ${cfg.package}/bin/lncli \ - --rpcserver ${builtins.elemAt cfg.rpclisten 0}:${toString cfg.rpcPort} \ + --rpcserver ${cfg.rpclisten}:${toString cfg.rpcPort} \ --tlscertpath '${secretsDir}/lnd-cert' \ --macaroonpath '${networkDir}/admin.macaroon' "$@" ''; @@ -189,12 +189,12 @@ in { RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; ExecStartPost = let - restUrl = "https://${builtins.elemAt cfg.restlisten 0}:${toString cfg.restPort}/v1"; + 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/${builtins.elemAt cfg.restlisten 0}/${toString cfg.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 @@ -236,7 +236,7 @@ in { fi # Wait until the RPC port is open - while ! { exec 3>/dev/tcp/${builtins.elemAt cfg.rpclisten 0}/${toString cfg.rpcPort}; } &>/dev/null; do + while ! { exec 3>/dev/tcp/${cfg.rpclisten}/${toString cfg.rpcPort}; } &>/dev/null; do sleep 0.1 done diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 12c61be..1aa363a 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -264,8 +264,8 @@ in { services.lnd = { listen = netns.lnd.address; - rpclisten = [ netns.lnd.address ]; - restlisten = [ netns.lnd.address ]; + rpclisten = netns.lnd.address; + restlisten = netns.lnd.address; }; services.liquidd = { From e66636ef0e2f35699516594acfb1b265377f4726 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:34 +0100 Subject: [PATCH 12/19] liquidd: use type str for rpcbind --- modules/liquid.nix | 8 ++++---- modules/netns-isolation.nix | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/liquid.nix b/modules/liquid.nix index 8d59b72..e315c87 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -26,8 +26,8 @@ let (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}") (attrValues cfg.rpc.users) } - ${lib.concatMapStrings (rpcbind: "rpcbind=${rpcbind}\n") cfg.rpcbind} - rpcconnect=${builtins.elemAt cfg.rpcbind 0} + 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}"} @@ -126,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. ''; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 1aa363a..bf4eaf7 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -270,7 +270,7 @@ in { services.liquidd = { bind = netns.liquidd.address; - rpcbind = [ netns.liquidd.address ]; + rpcbind = netns.liquidd.address; rpcallowip = [ bridgeIp # For operator user netns.liquidd.address From 9ddf7864a4298df1dd996478f81a6a96d1a8af8f Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:35 +0100 Subject: [PATCH 13/19] lightning-loop regtest: fix incorrectly succeeding test When 'loop getparams' fails, jq gets no stdin and exits with code 0. Because -o pipefail is not enabled in the testing shell, the whole test command succeeds, although it should fail. Just test "loop getparams" instead and ignore its output. --- test/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.py b/test/tests.py index 2d56f8d..dd1d886 100644 --- a/test/tests.py +++ b/test/tests.py @@ -337,7 +337,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: From d76b080b74e7f627ca7806398f716507932fc01a Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:36 +0100 Subject: [PATCH 14/19] lightning-loop: add RPC and REST server options --- modules/lightning-loop.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index cf914f4..5ed44c7 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -7,9 +7,12 @@ 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 @@ -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; @@ -52,6 +75,7 @@ in { cli = mkOption { default = pkgs.writeScriptBin "loop" '' ${cfg.cliExec} ${cfg.package}/bin/loop \ + --rpcserver ${rpclisten} \ --macaroonpath '${cfg.dataDir}/${network}/loop.macaroon' \ --tlscertpath '${secretsDir}/loop-cert' "$@" ''; From 8da01fe8a65a7dacc9114b00a48432f2b93eea6b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:37 +0100 Subject: [PATCH 15/19] lightning-loop: allow RPC access from main netns Note that this also exposes the REST server, which is secured by macaroon auth like the RPC server. --- modules/lightning-loop.nix | 3 +-- modules/netns-isolation.nix | 2 +- pkgs/netns-exec/src/main.c | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index 5ed44c7..6a5d780 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -74,14 +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/netns-isolation.nix b/modules/netns-isolation.nix index bf4eaf7..265414a 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -291,7 +291,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/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 916de95..e86e6af 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -10,7 +10,6 @@ #include static char *allowed_netns[] = { - "nb-lightning-loop", "nb-joinmarket" }; From 4ff88efc500cdd7fd0a407c21087c4479390246a Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:38 +0100 Subject: [PATCH 16/19] netns: add address binding test Proposed by Jonas Nick. --- test/tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/tests.py b/test/tests.py index dd1d886..0179034 100644 --- a/test/tests.py +++ b/test/tests.py @@ -259,6 +259,12 @@ def _(): 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" + ) + if "joinmarket" in enabled_tests: # netns-exec should drop capabilities assert_full_match( From 67068afd6b92883df044678e18486e84d02761c5 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:39 +0100 Subject: [PATCH 17/19] netns: fix error when stopping netns A short time after `netns delete` finishes, the peer link in the main netns is automatically removed. When `link del` is run before that, it fails with `Cannot find device "nb-veth-br-*"` and the netns service enters a failed state. --- modules/netns-isolation.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 265414a..752d8e9 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -175,7 +175,6 @@ in { '') v.availableNetns; preStop = '' ${ip} netns delete ${netnsName} - ${ip} link del ${peer} ''; serviceConfig = { Type = "oneshot"; From 25639cec427415a0e26d9ffcdc028edea1f11817 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:40 +0100 Subject: [PATCH 18/19] netns: fix error msg when starting netns Previously, the failing initial `netns delete` resulted in a "Cannot remove namespace file ..." error visible in the journal and status output. --- modules/netns-isolation.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 752d8e9..5c5fbb3 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -151,6 +151,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} @@ -179,7 +180,6 @@ in { serviceConfig = { Type = "oneshot"; RemainAfterExit = "yes"; - ExecStartPre = "-${ip} netns delete ${netnsName}"; }; }; }; From b4b607dfa56eaa25a7af5eca55b3149322335db6 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 29 Oct 2020 21:20:41 +0100 Subject: [PATCH 19/19] netns: simplify firewall setup --- modules/netns-isolation.nix | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 5c5fbb3..9f49cef 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -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}"; @@ -165,15 +166,13 @@ 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} '';