Merge #255: Improve netns-isolation and Tor config

b4b607dfa5 netns: simplify firewall setup (Erik Arvstedt)
25639cec42 netns: fix error msg when starting netns (Erik Arvstedt)
67068afd6b netns: fix error when stopping netns (Erik Arvstedt)
4ff88efc50 netns: add address binding test (Erik Arvstedt)
8da01fe8a6 lightning-loop: allow RPC access from main netns (Erik Arvstedt)
d76b080b74 lightning-loop: add RPC and REST server options (Erik Arvstedt)
9ddf7864a4 lightning-loop regtest: fix incorrectly succeeding test (Erik Arvstedt)
e66636ef0e liquidd: use type str for rpcbind (Erik Arvstedt)
de23fdd377 lnd: use type str for rpclisten, restlisten (Erik Arvstedt)
8b053326cc bitcoind: use type str for rpcbind (Erik Arvstedt)
6903e8afcc netns-liquidd: allow RPC access from main netns (Erik Arvstedt)
82f4901880 netns-lnd: allow RPC access from main netns (Erik Arvstedt)
58d24e735d netns-bitcoind: allow RPC access from main netns (Erik Arvstedt)
0e2ff948d3 test: add scenario 'netnsRegtest' (Erik Arvstedt)
e0675cb256 move enforceTor logic to service modules (Erik Arvstedt)
0cc8caa737 lnd: only set tor.active on enforceTor (Erik Arvstedt)
9a931483b9 netns test: remove strict dependency on clightning, electrs (Erik Arvstedt)
bae1b7f413 netns test: improve ping test (Erik Arvstedt)
5e0e16529c netns: fix default addressblock value type (Erik Arvstedt)

Pull request description:

ACKs for top commit:
  jonasnick:
    ACK b4b607dfa5
  nixbitcoin:
    ACK b4b607dfa5

Tree-SHA512: b290831d9a3fa4de56b0f19cf84a1998e830aa844532d7cba8cd8227c785a23bfa1514123a974652e8e61060e1297b6bfbcff9640580206a04c5292309b1daef
This commit is contained in:
Jonas Nick 2020-11-02 16:11:25 +00:00
commit dbad828851
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
15 changed files with 143 additions and 165 deletions

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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.
'';

View File

@ -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;

View File

@ -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@@

View File

@ -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;
};

View File

@ -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;
};
};

View File

@ -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)}

View File

@ -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;

View File

@ -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;
};

View File

@ -10,10 +10,6 @@
#include <sys/capability.h>
static char *allowed_netns[] = {
"nb-lnd",
"nb-lightning-loop",
"nb-bitcoind",
"nb-liquidd",
"nb-joinmarket"
};

View File

@ -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() {

View File

@ -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;

View File

@ -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: