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: ACKb4b607dfa5
nixbitcoin: ACKb4b607dfa5
Tree-SHA512: b290831d9a3fa4de56b0f19cf84a1998e830aa844532d7cba8cd8227c785a23bfa1514123a974652e8e61060e1297b6bfbcff9640580206a04c5292309b1daef
This commit is contained in:
commit
dbad828851
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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.
|
||||
'';
|
||||
|
@ -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;
|
||||
|
@ -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@@
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
@ -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)}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -10,10 +10,6 @@
|
||||
#include <sys/capability.h>
|
||||
|
||||
static char *allowed_netns[] = {
|
||||
"nb-lnd",
|
||||
"nb-lightning-loop",
|
||||
"nb-bitcoind",
|
||||
"nb-liquidd",
|
||||
"nb-joinmarket"
|
||||
};
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user