diff --git a/README.md b/README.md index e98cd28..e8817b5 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Security --- * **Simplicity:** Only services you select in `configuration.nix` and their dependencies are installed, packages and dependencies are [pinned](pkgs/nixpkgs-pinned.nix), most packages are built from the [NixOS stable channel](https://github.com/NixOS/nixpkgs/tree/nixos-20.09), with a few exceptions that are built from the nixpkgs unstable channel, builds happen in a [sandboxed environment](https://nixos.org/manual/nix/stable/#conf-sandbox), code is continuously reviewed and refined. * **Integrity:** Nix package manager, NixOS and packages can be built from source to reduce reliance on binary caches, nix-bitcoin merge commits are signed, all commits are approved by multiple nix-bitcoin developers, upstream packages are cryptographically verified where possible, we use this software ourselves. -* **Principle of Least Privilege:** Services operate with least privileges; they each have their own user and are restricted further with [systemd options](modules/nix-bitcoin-services.nix), [RPC whitelisting](modules/bitcoind-rpc-public-whitelist.nix), and [netns-isolation](modules/netns-isolation.nix). There's a non-root user *operator* to interact with the various services. +* **Principle of Least Privilege:** Services operate with least privileges; they each have their own user and are restricted further with [systemd options](pkgs/lib.nix), [RPC whitelisting](modules/bitcoind-rpc-public-whitelist.nix), and [netns-isolation](modules/netns-isolation.nix). There's a non-root user *operator* to interact with the various services. * **Defense-in-depth:** nix-bitcoin is built with a [hardened kernel](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix) by default, services are confined through discretionary access control, Linux namespaces, [dbus firewall](modules/security.nix) and seccomp-bpf with continuous improvements. Note that if the machine you're deploying *from* is insecure, there is nothing nix-bitcoin can do to protect itself. diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 7518606..af84cc8 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.bitcoind; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; configFile = pkgs.writeText "bitcoin.conf" '' @@ -291,7 +291,7 @@ in { ''; description = "Binary to connect with the bitcoind instance."; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; }; @@ -348,7 +348,7 @@ in { install -o '${cfg.user}' -g '${cfg.group}' -m 640 <(echo "$cfg") $confFile fi ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { Type = "notify"; NotifyAccess = "all"; User = "${cfg.user}"; @@ -359,9 +359,9 @@ in { UMask = mkIf cfg.dataDirReadableByGroup "0027"; ReadWritePaths = "${cfg.dataDir}"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP) - // optionalAttrs (cfg.zmqpubrawblock != null || cfg.zmqpubrawtx != null) nix-bitcoin-services.allowAnyProtocol; + then nbLib.allowTor + else nbLib.allowAnyIP) + // optionalAttrs (cfg.zmqpubrawblock != null || cfg.zmqpubrawtx != null) nbLib.allowAnyProtocol; }; # Use this to update the banlist: @@ -382,11 +382,11 @@ in { fi done ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { User = "${cfg.user}"; Group = "${cfg.group}"; ReadWritePaths = "${cfg.dataDir}"; - } // nix-bitcoin-services.allowTor; + } // nbLib.allowTor; }; users.users.${cfg.user} = { diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index 3600930..b663132 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; in { options.services = { @@ -44,7 +44,7 @@ in { internal = true; default = cfg.btcpayserver.enable; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; btcpayserver = { @@ -90,7 +90,7 @@ in { example = "btcpayserver"; description = "The prefix for root-relative btcpayserver URLs."; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; }; @@ -132,7 +132,7 @@ in { echo "btcrpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-btcpayserver)" \ >> '${cfg.nbxplorer.dataDir}/settings.config' ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { ExecStart = '' ${cfg.nbxplorer.package}/bin/nbxplorer --conf=${cfg.nbxplorer.dataDir}/settings.config \ --datadir=${cfg.nbxplorer.dataDir} @@ -143,8 +143,8 @@ in { ReadWritePaths = cfg.nbxplorer.dataDir; MemoryDenyWriteExecute = "false"; } // (if cfg.nbxplorer.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP + then nbLib.allowTor + else nbLib.allowAnyIP ); }; @@ -181,7 +181,7 @@ in { } >> ${cfg.btcpayserver.dataDir}/settings.config ''} ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { ExecStart = '' ${cfg.btcpayserver.package}/bin/btcpayserver --conf=${cfg.btcpayserver.dataDir}/settings.config \ --datadir=${cfg.btcpayserver.dataDir} @@ -192,8 +192,8 @@ in { ReadWritePaths = cfg.btcpayserver.dataDir; MemoryDenyWriteExecute = "false"; } // (if cfg.btcpayserver.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP + then nbLib.allowTor + else nbLib.allowAnyIP ); }; in self; diff --git a/modules/clightning.nix b/modules/clightning.nix index 544782b..d8838ec 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.clightning; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; network = config.services.bitcoind.makeNetworkName "bitcoin" "regtest"; configFile = pkgs.writeText "config" '' @@ -91,7 +91,7 @@ in { If left empty, no address is announced. ''; }; - inherit (nix-bitcoin-services) enforceTor; + inherit (nbLib) enforceTor; }; config = mkIf cfg.enable { @@ -135,15 +135,15 @@ in { } >> '${cfg.dataDir}/config' ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { ExecStart = "${nbPkgs.clightning}/bin/lightningd --lightning-dir=${cfg.dataDir}"; User = "${cfg.user}"; Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP + then nbLib.allowTor + else nbLib.allowAnyIP ); # Wait until the rpc socket appears postStart = '' diff --git a/modules/electrs.nix b/modules/electrs.nix index 5d03c10..a2c8102 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -3,7 +3,7 @@ with lib; let cfg = config.services.electrs; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; bitcoind = config.services.bitcoind; in { @@ -51,7 +51,7 @@ in { default = ""; description = "Extra command line arguments passed to electrs."; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; config = mkIf cfg.enable { @@ -76,7 +76,7 @@ in { echo "cookie = \"${bitcoind.rpc.users.public.name}:$(cat ${secretsDir}/bitcoin-rpcpassword-public)\"" \ > electrs.toml ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { RuntimeDirectory = "electrs"; RuntimeDirectoryMode = "700"; WorkingDirectory = "/run/electrs"; @@ -104,8 +104,8 @@ in { RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir} ${if cfg.high-memory then "${bitcoind.dataDir}" else ""}"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP + then nbLib.allowTor + else nbLib.allowAnyIP ); }; diff --git a/modules/joinmarket-ob-watcher.nix b/modules/joinmarket-ob-watcher.nix index e65bd7d..e8b2a30 100644 --- a/modules/joinmarket-ob-watcher.nix +++ b/modules/joinmarket-ob-watcher.nix @@ -3,7 +3,7 @@ with lib; let cfg = config.services.joinmarket-ob-watcher; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; torAddress = builtins.head (builtins.split ":" config.services.tor.client.socksListenAddress); configFile = builtins.toFile "config" '' @@ -76,7 +76,7 @@ in { preStart = '' ln -snf ${configFile} ${cfg.dataDir}/joinmarket.cfg ''; - serviceConfig = nix-bitcoin-services.defaultHardening // rec { + serviceConfig = nbLib.defaultHardening // rec { StateDirectory = "joinmarket-ob-watcher"; StateDirectoryMode = "0770"; WorkingDirectory = "${cfg.dataDir}"; # The service creates dir 'logs' in the working dir @@ -87,7 +87,7 @@ in { User = cfg.user; Restart = "on-failure"; RestartSec = "10s"; - } // nix-bitcoin-services.allowTor; + } // nbLib.allowTor; }; users.users.${cfg.user} = { diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index d1c2367..ccdc8cb 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.joinmarket; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; secretsDir = config.nix-bitcoin.secretsDir; @@ -137,7 +137,7 @@ in { readOnly = true; default = true; }; - inherit (nix-bitcoin-services) cliExec; + inherit (nbLib) cliExec; }; config = mkIf cfg.enable (mkMerge [{ @@ -177,15 +177,15 @@ in { requires = [ "bitcoind.service" ]; after = [ "bitcoind.service" ]; path = [ pkgs.sudo ]; - serviceConfig = nix-bitcoin-services.defaultHardening // { - ExecStartPre = nix-bitcoin-services.privileged '' + serviceConfig = nbLib.defaultHardening // { + ExecStartPre = nbLib.privileged '' install -o '${cfg.user}' -g '${cfg.group}' -m 640 ${configFile} ${cfg.dataDir}/joinmarket.cfg sed -i \ "s|@@RPC_PASSWORD@@|rpc_password = $(cat ${secretsDir}/bitcoin-rpcpassword-privileged)|" \ '${cfg.dataDir}/joinmarket.cfg' ''; # Generating wallets (jmclient/wallet.py) is only supported for mainnet or testnet - ExecStartPost = mkIf (bitcoind.network == "mainnet") (nix-bitcoin-services.privileged '' + ExecStartPost = mkIf (bitcoind.network == "mainnet") (nbLib.privileged '' walletname=wallet.jmdat pw=$(cat "${secretsDir}"/jm-wallet-password) mnemonic=${secretsDir}/jm-wallet-seed @@ -207,7 +207,7 @@ in { Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; - } // nix-bitcoin-services.allowTor; + } // nbLib.allowTor; }; nix-bitcoin.secrets.jm-wallet-password.user = cfg.user; @@ -239,14 +239,14 @@ in { pw=$(cat "${secretsDir}"/jm-wallet-password) echo "echo -n $pw | ${start}" > $RUNTIME_DIRECTORY/start ''; - serviceConfig = nix-bitcoin-services.defaultHardening // rec { + serviceConfig = nbLib.defaultHardening // rec { RuntimeDirectory = "joinmarket-yieldgenerator"; # Only used to create start script RuntimeDirectoryMode = "700"; WorkingDirectory = "${cfg.dataDir}"; # The service creates dir 'logs' in the working dir ExecStart = "${pkgs.bash}/bin/bash /run/${RuntimeDirectory}/start"; User = "${cfg.user}"; ReadWritePaths = "${cfg.dataDir}"; - } // nix-bitcoin-services.allowTor; + } // nbLib.allowTor; }; }) ]); diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index 37d9448..fd87b19 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.lightning-loop; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; network = config.services.bitcoind.network; rpclisten = "${cfg.rpcAddress}:${toString cfg.rpcPort}"; @@ -80,7 +80,7 @@ in { ''; description = "Binary to connect with the lightning-loop instance."; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; config = mkIf cfg.enable { @@ -96,15 +96,15 @@ in { wantedBy = [ "multi-user.target" ]; requires = [ "lnd.service" ]; after = [ "lnd.service" ]; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { ExecStart = "${cfg.package}/bin/loopd --configfile=${configFile}"; User = "lnd"; Restart = "on-failure"; RestartSec = "10s"; ReadWritePaths = "${cfg.dataDir}"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP); + then nbLib.allowTor + else nbLib.allowAnyIP); }; nix-bitcoin.secrets = { diff --git a/modules/liquid.nix b/modules/liquid.nix index 6e83321..26b9e5a 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.liquidd; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; secretsDir = config.nix-bitcoin.secretsDir; pidFile = "${cfg.dataDir}/liquidd.pid"; @@ -203,7 +203,7 @@ in { ''; description = "Binary for managing liquid swaps."; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; }; @@ -232,7 +232,7 @@ in { echo "rpcpassword=$(cat ${secretsDir}/liquid-rpcpassword)" >> '${cfg.dataDir}/elements.conf' echo "mainchainrpcpassword=$(cat ${secretsDir}/bitcoin-rpcpassword-public)" >> '${cfg.dataDir}/elements.conf' ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { Type = "simple"; User = "${cfg.user}"; Group = "${cfg.group}"; @@ -241,8 +241,8 @@ in { Restart = "on-failure"; ReadWritePaths = "${cfg.dataDir}"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP + then nbLib.allowTor + else nbLib.allowAnyIP ); }; diff --git a/modules/lnd.nix b/modules/lnd.nix index e8fb9c7..4899dac 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.lnd; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; bitcoind = config.services.bitcoind; @@ -144,7 +144,7 @@ in { If left empty, no address is announced. ''; }; - inherit (nix-bitcoin-services) enforceTor; + inherit (nbLib) enforceTor; }; config = mkIf cfg.enable { @@ -186,7 +186,7 @@ in { ''} } >> '${cfg.dataDir}/lnd.conf' ''; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { RuntimeDirectory = "lnd"; # Only used to store custom macaroons RuntimeDirectoryMode = "711"; ExecStart = "${cfg.package}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf"; @@ -198,7 +198,7 @@ in { restUrl = "https://${cfg.restAddress}:${toString cfg.restPort}/v1"; in [ # Run fully privileged for secrets dir write access - "+${nix-bitcoin-services.script '' + "+${nbLib.script '' attempts=250 while ! { exec 3>/dev/tcp/${cfg.restAddress}/${toString cfg.restPort} && exec 3>&-; } &>/dev/null; do ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } @@ -215,7 +215,7 @@ in { fi chown lnd: "$mnemonic" ''}" - "${nix-bitcoin-services.script '' + "${nbLib.script '' if [[ ! -f ${networkDir}/wallet.db ]]; then echo Create lnd wallet @@ -248,7 +248,7 @@ in { ''}" # Run fully privileged for chown - "+${nix-bitcoin-services.script '' + "+${nbLib.script '' umask ug=r,o= ${lib.concatMapStrings (macaroon: '' echo "Create custom macaroon ${macaroon}" @@ -265,9 +265,9 @@ in { ''}" ]; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP - ) // nix-bitcoin-services.allowAnyProtocol; # For ZMQ + then nbLib.allowTor + else nbLib.allowAnyIP + ) // nbLib.allowAnyProtocol; # For ZMQ }; users.users.lnd = { diff --git a/modules/modules.nix b/modules/modules.nix index 6ca7e18..3372e30 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -35,17 +35,17 @@ with lib; disabledModules = [ "services/networking/bitcoind.nix" ]; options = { - nix-bitcoin-services = mkOption { - readOnly = true; - default = import ./nix-bitcoin-services.nix lib pkgs; - }; - nix-bitcoin = { pkgs = mkOption { type = types.attrs; default = (import ../pkgs { inherit pkgs; }).modulesPkgs; }; + lib = mkOption { + readOnly = true; + default = import ../pkgs/lib.nix lib pkgs; + }; + # Torify binary that works with custom Tor SOCKS addresses # Related issue: https://github.com/NixOS/nixpkgs/issues/94236 torify = mkOption { diff --git a/modules/onion-addresses.nix b/modules/onion-addresses.nix index df9c9dc..64cda59 100644 --- a/modules/onion-addresses.nix +++ b/modules/onion-addresses.nix @@ -10,7 +10,7 @@ with lib; let cfg = config.nix-bitcoin.onionAddresses; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; in { options.nix-bitcoin.onionAddresses = { access = mkOption { @@ -47,7 +47,7 @@ in { wantedBy = [ "tor.service" ]; bindsTo = [ "tor.service" ]; after = [ "tor.service" ]; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { Type = "oneshot"; RemainAfterExit = true; StateDirectory = "onion-addresses"; diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 93ae7b5..84ebea9 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.recurring-donations; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; recurring-donations-script = pkgs.writeScript "recurring-donations.sh" '' LNCLI="${config.nix-bitcoin.pkgs.clightning}/bin/lightning-cli --lightning-dir=${config.services.clightning.dataDir}" pay_tallycoin() { @@ -75,7 +75,7 @@ in { Random delay to add to scheduled time for donation. Default is one day. ''; }; - enforceTor = nix-bitcoin-services.enforceTor; + enforceTor = nbLib.enforceTor; }; config = mkIf cfg.enable { @@ -93,13 +93,13 @@ in { requires = [ "clightning.service" ]; after = [ "clightning.service" ]; path = with pkgs; [ nix-bitcoin.clightning curl sudo jq ]; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { ExecStart = "${pkgs.bash}/bin/bash ${recurring-donations-script}"; User = "recurring-donations"; Type = "oneshot"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP); + then nbLib.allowTor + else nbLib.allowAnyIP); }; systemd.timers.recurring-donations = { requires = [ "clightning.service" ]; diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index a130dd2..3a46f89 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.spark-wallet; - inherit (config) nix-bitcoin-services; + nbLib = config.nix-bitcoin.lib; # Use wasabi rate provider because the default (bitstamp) doesn't accept # connections through Tor @@ -54,7 +54,7 @@ in { encodes an URL for accessing the web interface. ''; }; - inherit (nix-bitcoin-services) enforceTor; + inherit (nbLib) enforceTor; }; config = mkIf cfg.enable { @@ -73,14 +73,14 @@ in { requires = [ "clightning.service" ]; after = [ "clightning.service" ]; script = startScript; - serviceConfig = nix-bitcoin-services.defaultHardening // { + serviceConfig = nbLib.defaultHardening // { User = "spark-wallet"; Restart = "on-failure"; RestartSec = "10s"; } // (if cfg.enforceTor - then nix-bitcoin-services.allowTor - else nix-bitcoin-services.allowAnyIP) - // nix-bitcoin-services.nodejs; + then nbLib.allowTor + else nbLib.allowAnyIP) + // nbLib.nodejs; }; nix-bitcoin.secrets.spark-wallet-login.user = "spark-wallet"; }; diff --git a/modules/nix-bitcoin-services.nix b/pkgs/lib.nix similarity index 92% rename from modules/nix-bitcoin-services.nix rename to pkgs/lib.nix index 24d0099..75409bf 100644 --- a/modules/nix-bitcoin-services.nix +++ b/pkgs/lib.nix @@ -1,9 +1,9 @@ -# See `man systemd.exec` and `man systemd.resource-control` for an explanation -# of the various systemd options available through this module. - lib: pkgs: with lib; + +# See `man systemd.exec` and `man systemd.resource-control` for an explanation +# of the systemd-related options available through this module. let self = { # These settings roughly follow systemd's "strict" security profile defaultHardening = { @@ -46,8 +46,8 @@ let self = { type = types.bool; default = false; description = '' - "Whether to force Tor on a service by only allowing connections from and - to 127.0.0.1;"; + Whether to force Tor on a service by only allowing connections from and + to 127.0.0.1; ''; };