services: add `tor.*` options

Split `enforceTor` into `tor.proxy` and `tor.enforce`.
By enabling `tor.proxy` without `tor.enforce`, a service can accept
incoming clearnet connections.
E.g., this allows setting up a Tor-proxied bitcoind node that accepts
RPC connections from LAN.
This commit is contained in:
Erik Arvstedt 2021-11-28 21:24:49 +01:00
parent ff24e73ad7
commit 9bda7305fd
No known key found for this signature in database
GPG Key ID: 33312B944DD97846
17 changed files with 109 additions and 65 deletions

View File

@ -157,7 +157,7 @@ let
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = "Connect through SOCKS5 proxy"; description = "Connect through SOCKS5 proxy";
}; };
i2p = mkOption { i2p = mkOption {
@ -262,7 +262,7 @@ let
''; '';
description = "Binary to connect with the bitcoind instance."; description = "Binary to connect with the bitcoind instance.";
}; };
enforceTor = nbLib.enforceTor; tor = nbLib.tor;
}; };
}; };
@ -407,7 +407,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
UMask = mkIf cfg.dataDirReadableByGroup "0027"; UMask = mkIf cfg.dataDirReadableByGroup "0027";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.enforceTor } // nbLib.allowedIPAddresses cfg.tor.enforce
// optionalAttrs zmqServerEnabled nbLib.allowNetlink; // optionalAttrs zmqServerEnabled nbLib.allowNetlink;
}; };

View File

@ -54,7 +54,7 @@ let
default = cfg.btcpayserver.user; default = cfg.btcpayserver.user;
description = "The group as which to run btcpayserver."; description = "The group as which to run btcpayserver.";
}; };
enforceTor = nbLib.enforceTor; tor.enforce = nbLib.tor.enforce;
}; };
nbxplorer = { nbxplorer = {
@ -96,7 +96,7 @@ let
default = cfg.nbxplorer.user; default = cfg.nbxplorer.user;
description = "The group as which to run nbxplorer."; description = "The group as which to run nbxplorer.";
}; };
enforceTor = nbLib.enforceTor; tor.enforce = nbLib.tor.enforce;
}; };
}; };
@ -185,7 +185,7 @@ in {
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.nbxplorer.dataDir; ReadWritePaths = cfg.nbxplorer.dataDir;
MemoryDenyWriteExecute = "false"; MemoryDenyWriteExecute = "false";
} // nbLib.allowedIPAddresses cfg.nbxplorer.enforceTor; } // nbLib.allowedIPAddresses cfg.nbxplorer.tor.enforce;
}; };
systemd.services.btcpayserver = let systemd.services.btcpayserver = let
@ -238,7 +238,7 @@ in {
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.btcpayserver.dataDir; ReadWritePaths = cfg.btcpayserver.dataDir;
MemoryDenyWriteExecute = "false"; MemoryDenyWriteExecute = "false";
} // nbLib.allowedIPAddresses cfg.btcpayserver.enforceTor; } // nbLib.allowedIPAddresses cfg.btcpayserver.tor.enforce;
}; in self; }; in self;
users.users.${cfg.nbxplorer.user} = { users.users.${cfg.nbxplorer.user} = {

View File

@ -28,6 +28,6 @@ let cfg = config.services.clightning.plugins.clboss; in
''; '';
systemd.services.clightning.path = [ systemd.services.clightning.path = [
pkgs.dnsutils pkgs.dnsutils
] ++ optional config.services.clightning.enforceTor (hiPrio config.nix-bitcoin.torify); ] ++ optional config.services.clightning.tor.proxy (hiPrio config.nix-bitcoin.torify);
}; };
} }

View File

@ -16,14 +16,14 @@ let
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = '' description = ''
Socks proxy for connecting to Tor nodes (or for all connections if option always-use-proxy is set). Socks proxy for connecting to Tor nodes (or for all connections if option always-use-proxy is set).
''; '';
}; };
always-use-proxy = mkOption { always-use-proxy = mkOption {
type = types.bool; type = types.bool;
default = cfg.enforceTor; default = cfg.tor.proxy;
description = '' description = ''
Always use the proxy, even to connect to normal IP addresses. Always use the proxy, even to connect to normal IP addresses.
You can still connect to Unix domain sockets manually. You can still connect to Unix domain sockets manually.
@ -79,7 +79,7 @@ let
If left empty, no address is announced. If left empty, no address is announced.
''; '';
}; };
inherit (nbLib) enforceTor; tor = nbLib.tor;
}; };
cfg = config.services.clightning; cfg = config.services.clightning;
@ -156,7 +156,7 @@ in {
# #
# Disable seccomp filtering because clightning depends on this syscall. # Disable seccomp filtering because clightning depends on this syscall.
SystemCallFilter = []; SystemCallFilter = [];
} // nbLib.allowedIPAddresses cfg.enforceTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
# Wait until the rpc socket appears # Wait until the rpc socket appears
postStart = '' postStart = ''
while [[ ! -e ${cfg.networkDir}/lightning-rpc ]]; do while [[ ! -e ${cfg.networkDir}/lightning-rpc ]]; do

View File

@ -39,7 +39,7 @@ let
default = cfg.user; default = cfg.user;
description = "The group as which to run electrs."; description = "The group as which to run electrs.";
}; };
enforceTor = nbLib.enforceTor; tor.enforce = nbLib.tor.enforce;
}; };
cfg = config.services.electrs; cfg = config.services.electrs;
@ -94,7 +94,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.enforceTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {

View File

@ -29,11 +29,9 @@ let
default = cfg.user; default = cfg.user;
description = "The group as which to run JoinMarket."; description = "The group as which to run JoinMarket.";
}; };
# This option is only used by netns-isolation # This option is only used by netns-isolation.
enforceTor = mkOption { # Tor is always enabled.
readOnly = true; tor.enforce = nbLib.tor.enforce;
default = true;
};
}; };
cfg = config.services.joinmarket-ob-watcher; cfg = config.services.joinmarket-ob-watcher;
@ -100,7 +98,7 @@ in {
SystemCallFilter = nbLib.defaultHardening.SystemCallFilter ++ [ "mbind" ] ; SystemCallFilter = nbLib.defaultHardening.SystemCallFilter ++ [ "mbind" ] ;
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
} // nbLib.allowTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {

View File

@ -50,11 +50,9 @@ let
readOnly = true; readOnly = true;
default = ircServers; default = ircServers;
}; };
# This option is only used by netns-isolation # This option is only used by netns-isolation.
enforceTor = mkOption { # Tor is always enabled.
readOnly = true; tor.enforce = nbLib.tor.enforce;
default = true;
};
inherit (nbLib) cliExec; inherit (nbLib) cliExec;
yieldgenerator = { yieldgenerator = {
@ -328,7 +326,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {

View File

@ -36,7 +36,7 @@ let
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = "host:port of SOCKS5 proxy for connnecting to the loop server."; description = "host:port of SOCKS5 proxy for connnecting to the loop server.";
}; };
extraConfig = mkOption { extraConfig = mkOption {
@ -56,7 +56,7 @@ let
''; '';
description = "Binary to connect with the lightning-loop instance."; description = "Binary to connect with the lightning-loop instance.";
}; };
enforceTor = nbLib.enforceTor; tor = nbLib.tor;
}; };
cfg = config.services.lightning-loop; cfg = config.services.lightning-loop;
@ -105,7 +105,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.enforceTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
nix-bitcoin.secrets = { nix-bitcoin.secrets = {

View File

@ -36,7 +36,7 @@ let
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = "host:port of SOCKS5 proxy for connnecting to the pool auction server."; description = "host:port of SOCKS5 proxy for connnecting to the pool auction server.";
}; };
extraConfig = mkOption { extraConfig = mkOption {
@ -56,7 +56,7 @@ let
''; '';
description = "Binary to connect with the lightning-pool instance."; description = "Binary to connect with the lightning-pool instance.";
}; };
enforceTor = nbLib.enforceTor; tor = nbLib.tor;
}; };
cfg = config.services.lightning-pool; cfg = config.services.lightning-pool;
@ -102,7 +102,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // (nbLib.allowedIPAddresses cfg.enforceTor) } // (nbLib.allowedIPAddresses cfg.tor.enforce)
// nbLib.allowNetlink; # required by gRPC-Go // nbLib.allowNetlink; # required by gRPC-Go
}; };
}; };

View File

@ -98,7 +98,7 @@ let
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = "Connect through SOCKS5 proxy"; description = "Connect through SOCKS5 proxy";
}; };
dbCache = mkOption { dbCache = mkOption {
@ -156,7 +156,7 @@ let
''; '';
description = "Binary for managing liquid swaps."; description = "Binary for managing liquid swaps.";
}; };
enforceTor = nbLib.enforceTor; tor = nbLib.tor;
}; };
}; };
@ -271,7 +271,7 @@ in {
ExecStart = "${nbPkgs.elementsd}/bin/elementsd -datadir='${cfg.dataDir}'"; ExecStart = "${nbPkgs.elementsd}/bin/elementsd -datadir='${cfg.dataDir}'";
Restart = "on-failure"; Restart = "on-failure";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.enforceTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {

View File

@ -46,7 +46,7 @@ let
}; };
tor-socks = mkOption { tor-socks = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; default = if cfg.tor.proxy then config.nix-bitcoin.torClientAddressWithPort else null;
description = "Socks proxy for connecting to Tor nodes"; description = "Socks proxy for connecting to Tor nodes";
}; };
macaroons = mkOption { macaroons = mkOption {
@ -117,7 +117,7 @@ let
default = "${secretsDir}/lnd-cert"; default = "${secretsDir}/lnd-cert";
description = "LND TLS certificate path."; description = "LND TLS certificate path.";
}; };
inherit (nbLib) enforceTor; tor = nbLib.tor;
}; };
cfg = config.services.lnd; cfg = config.services.lnd;
@ -143,7 +143,7 @@ let
bitcoin.active=1 bitcoin.active=1
bitcoin.node=bitcoind bitcoin.node=bitcoind
${optionalString (cfg.enforceTor) "tor.active=true"} ${optionalString (cfg.tor.proxy) "tor.active=true"}
${optionalString (cfg.tor-socks != null) "tor.socks=${cfg.tor-socks}"} ${optionalString (cfg.tor-socks != null) "tor.socks=${cfg.tor-socks}"}
bitcoind.rpchost=${bitcoindRpcAddress}:${toString bitcoind.rpc.port} bitcoind.rpchost=${bitcoindRpcAddress}:${toString bitcoind.rpc.port}
@ -277,7 +277,7 @@ in {
'') (attrNames cfg.macaroons)} '') (attrNames cfg.macaroons)}
'') '')
]; ];
} // nbLib.allowedIPAddresses cfg.enforceTor; } // nbLib.allowedIPAddresses cfg.tor.enforce;
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {

View File

@ -179,7 +179,7 @@ in {
${iptables} -w -A INPUT -s 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT ${iptables} -w -A INPUT -s 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT
# allow return traffic to outgoing connections initiated by the service itself # allow return traffic to outgoing connections initiated by the service itself
${iptables} -w -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT ${iptables} -w -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
'' + optionalString (config.services.${n}.enforceTor or false) '' '' + optionalString (config.services.${n}.tor.enforce or false) ''
${iptables} -w -P OUTPUT DROP ${iptables} -w -P OUTPUT DROP
${iptables} -w -A OUTPUT -d 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT ${iptables} -w -A OUTPUT -d 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT
'' + optionalString (v.availableNetns != []) '' '' + optionalString (v.availableNetns != []) ''

View File

@ -7,6 +7,16 @@ let
mkRemovedOptionModule [ "services" service "announce-tor" ] '' mkRemovedOptionModule [ "services" service "announce-tor" ] ''
Use option `nix-bitcoin.onionServices.${service}.public` instead. Use option `nix-bitcoin.onionServices.${service}.public` instead.
''; '';
mkSplitEnforceTorOption = service:
(mkRemovedOptionModule [ "services" service "enforceTor" ] ''
The option has been split into options `tor.proxy` and `tor.enforce`.
Set `tor.proxy = true` to proxy outgoing connections with Tor.
Set `tor.enforce = true` to only allow connections (incoming and outgoing) through Tor.
'');
mkRenamedEnforceTorOption = service:
(mkRenamedOptionModule [ "services" service "enforceTor" ] [ "services" service "tor" "enforce" ]);
in { in {
imports = [ imports = [
(mkRenamedOptionModule [ "services" "bitcoind" "bind" ] [ "services" "bitcoind" "address" ]) (mkRenamedOptionModule [ "services" "bitcoind" "bind" ] [ "services" "bitcoind" "address" ])
@ -33,5 +43,20 @@ in {
bitcoin peer connections for syncing blocks. This performs well on low and high bitcoin peer connections for syncing blocks. This performs well on low and high
memory systems. memory systems.
'') '')
]; ] ++
# 0.0.59
(map mkSplitEnforceTorOption [
"clightning"
"lightning-loop"
"lightning-pool"
"liquid"
"lnd"
"spark-wallet"
"bitcoind"
]) ++
(map mkRenamedEnforceTorOption [
"btcpayserver"
"rtl"
"electrs"
]);
} }

View File

@ -1,26 +1,42 @@
{ lib, config, ... }: { lib, config, ... }:
let let
defaultTrue = lib.mkDefault true; defaultTrue = lib.mkDefault true;
defaultEnableTorProxy = {
tor.proxy = defaultTrue;
tor.enforce = defaultTrue;
};
defaultEnforceTor = {
tor.enforce = defaultTrue;
};
in { in {
services.tor = { services.tor = {
enable = true; enable = true;
client.enable = true; client.enable = true;
}; };
# Use Tor for all outgoing connections
services = { services = {
bitcoind.enforceTor = true; # Use Tor as a proxy for outgoing connections
clightning.enforceTor = true; # and restrict all connections to Tor
lnd.enforceTor = true; #
lightning-loop.enforceTor = true; bitcoind = defaultEnableTorProxy;
liquidd.enforceTor = true; clightning = defaultEnableTorProxy;
electrs.enforceTor = true; lnd = defaultEnableTorProxy;
lightning-loop = defaultEnableTorProxy;
liquidd = defaultEnableTorProxy;
# disable Tor enforcement until btcpayserver can fetch rates over Tor # disable Tor enforcement until btcpayserver can fetch rates over Tor
# btcpayserver.enforceTor = true; # btcpayserver = defaultEnableTorProxy;
nbxplorer.enforceTor = true; spark-wallet = defaultEnableTorProxy;
spark-wallet.enforceTor = true; lightning-pool = defaultEnableTorProxy;
lightning-pool.enforceTor = true;
rtl.enforceTor = true; # These services don't make outgoing connections
# (or use Tor by default in case of joinmarket)
# but we restrict them to Tor just to be safe.
#
electrs = defaultEnforceTor;
nbxplorer = defaultEnforceTor;
rtl = defaultEnforceTor;
joinmarket = defaultEnforceTor;
joinmarket-ob-watcher = defaultEnforceTor;
}; };
# Add onion services for incoming connections # Add onion services for incoming connections

View File

@ -89,7 +89,7 @@ let
description = "Swagger API documentation server port."; description = "Swagger API documentation server port.";
}; };
}; };
inherit (nbLib) enforceTor; tor.enforce = nbLib.tor.enforce;
}; };
cfg = config.services.rtl; cfg = config.services.rtl;
@ -214,7 +214,7 @@ in {
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
ReadWritePaths = cfg.dataDir; ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.enforceTor } // nbLib.allowedIPAddresses cfg.tor.enforce
// nbLib.nodejs; // nbLib.nodejs;
}; };

View File

@ -38,7 +38,7 @@ let
default = cfg.user; default = cfg.user;
description = "The group as which to run spark-wallet."; description = "The group as which to run spark-wallet.";
}; };
inherit (nbLib) enforceTor; tor = nbLib.tor;
}; };
cfg = config.services.spark-wallet; cfg = config.services.spark-wallet;
@ -57,7 +57,7 @@ let
--ln-path '${clightning.networkDir}' \ --ln-path '${clightning.networkDir}' \
--host ${cfg.address} --port ${toString cfg.port} \ --host ${cfg.address} --port ${toString cfg.port} \
--config '${config.nix-bitcoin.secretsDir}/spark-wallet-login' \ --config '${config.nix-bitcoin.secretsDir}/spark-wallet-login' \
${optionalString cfg.enforceTor torRateProvider} \ ${optionalString cfg.tor.proxy torRateProvider} \
$publicURL \ $publicURL \
--pairing-qr --print-key ${cfg.extraArgs} --pairing-qr --print-key ${cfg.extraArgs}
''; '';
@ -76,7 +76,7 @@ in {
User = cfg.user; User = cfg.user;
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
} // nbLib.allowedIPAddresses cfg.enforceTor } // nbLib.allowedIPAddresses cfg.tor.enforce
// nbLib.nodejs; // nbLib.nodejs;
}; };

View File

@ -55,13 +55,20 @@ let self = {
then self.allowLocalIPAddresses then self.allowLocalIPAddresses
else self.allowAllIPAddresses; else self.allowAllIPAddresses;
enforceTor = mkOption { tor = {
type = types.bool; proxy = mkOption {
default = false; type = types.bool;
description = '' default = false;
Whether to force Tor on a service by only allowing connections from and description = "Whether to proxy outgoing connections with Tor.";
to 127.0.0.1; };
''; enforce = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enforce Tor on a service by only allowing connections
from and to localhost and link-local addresses.
'';
};
}; };
script = name: src: pkgs.writers.writeBash name '' script = name: src: pkgs.writers.writeBash name ''