From b758150c9e53330134efef535bcbc5b85e9910a6 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:48:55 +0200 Subject: [PATCH 01/17] pinned: expose nixpkgsStable, nixpkgsUnstable This allows accessing the pinned nixpkgs. E.g., this is useful for comparing package versions between stable and unstable. --- pkgs/pinned.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/pinned.nix b/pkgs/pinned.nix index b158a74..0851d03 100644 --- a/pkgs/pinned.nix +++ b/pkgs/pinned.nix @@ -21,6 +21,8 @@ in lightning-loop lightning-pool; + inherit nixpkgsStable nixpkgsUnstable; + stable = nixBitcoinPkgsStable; unstable = nixBitcoinPkgsUnstable; } From a25ceecca5b65fcd57dabc66ca2c858c19512d47 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:48:56 +0200 Subject: [PATCH 02/17] update to NixOS 21.05 --- examples/configuration.nix | 2 +- modules/presets/hardened.nix | 2 +- pkgs/nixpkgs-pinned.nix | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/configuration.nix b/examples/configuration.nix index f6f293b..bf5cd84 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -240,7 +240,7 @@ # compatible, in order to avoid breaking some software such as database # servers. You should change this only after NixOS release notes say you # should. - system.stateVersion = "18.09"; # Did you read the comment? + system.stateVersion = "21.05"; # Did you read the comment? # The nix-bitcoin release version that your config is compatible with. # When upgrading to a backwards-incompatible release, nix-bitcoin will display an diff --git a/modules/presets/hardened.nix b/modules/presets/hardened.nix index 16833a6..f7521d8 100644 --- a/modules/presets/hardened.nix +++ b/modules/presets/hardened.nix @@ -9,6 +9,6 @@ # Needed for sandboxed builds and services security.allowUserNamespaces = true; - # The "scudo" allocator is broken on NixOS 20.09 + # The "scudo" allocator is broken on NixOS >= 20.09 environment.memoryAllocator.provider = "libc"; } diff --git a/pkgs/nixpkgs-pinned.nix b/pkgs/nixpkgs-pinned.nix index 7f9c717..61c0130 100644 --- a/pkgs/nixpkgs-pinned.nix +++ b/pkgs/nixpkgs-pinned.nix @@ -8,8 +8,9 @@ in { # To update, run ../helper/fetch-channel REV nixpkgs = fetch { - rev = "359e6542e1d41eb18df55c82bdb08bf738fae2cf"; - sha256 = "05v28njaas9l26ibc6vy6imvy7grbkli32bmv0n32x6x9cf68gf9"; + # nixos-21.05 (2021-08-03) + rev = "d4590d21006387dcb190c516724cb1e41c0f8fdf"; + sha256 = "17q39hlx1x87xf2rdygyimj8whdbx33nzszf4rxkc6b85wz0l38n"; }; nixpkgs-unstable = fetch { rev = "16105403bdd843540cbef9c63fc0f16c1c6eaa70"; From 0ef66c920b91ab850ce3163a275c74c453f1662d Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:48:58 +0200 Subject: [PATCH 03/17] treewide: use services.getty option services.mingetty is equivalent but deprecated. --- examples/qemu-vm/vm-config.nix | 2 +- test/lib/make-test.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/qemu-vm/vm-config.nix b/examples/qemu-vm/vm-config.nix index ca91e55..df3d40c 100644 --- a/examples/qemu-vm/vm-config.nix +++ b/examples/qemu-vm/vm-config.nix @@ -4,7 +4,7 @@ config = { virtualisation.graphics = false; - services.mingetty.autologinUser = "root"; + services.getty.autologinUser = "root"; users.users.root = { openssh.authorizedKeys.keyFiles = [ ./id-vm.pub ]; }; diff --git a/test/lib/make-test.nix b/test/lib/make-test.nix index 7666755..ab1fec3 100644 --- a/test/lib/make-test.nix +++ b/test/lib/make-test.nix @@ -69,7 +69,7 @@ name: testConfig: "${toString pkgs.path}/nixos/modules/virtualisation/qemu-vm.nix" ]; virtualisation.graphics = false; - services.mingetty.autologinUser = "root"; + services.getty.autologinUser = "root"; # Provide a shortcut for instant poweroff from within the machine environment.systemPackages = with pkgs; [ From e44f78ebb81af1dd97e212bd24799e59bf46006b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:48:59 +0200 Subject: [PATCH 04/17] services: set isSystemUser for service users 'isSystemUser' has to be explicitly set in NixOS 21.05. Previously, it was the implicit default. --- modules/bitcoind.nix | 5 ++++- modules/btcpayserver.nix | 2 ++ modules/charge-lnd.nix | 2 +- modules/clightning.nix | 1 + modules/electrs.nix | 1 + modules/joinmarket.nix | 1 + modules/liquid.nix | 1 + modules/lnd.nix | 1 + modules/recurring-donations.nix | 1 + modules/spark-wallet.nix | 1 + 10 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index 9a2a79b..e78ca37 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -388,7 +388,10 @@ in { } // nbLib.allowLocalIPAddresses; }; - users.users.${cfg.user}.group = cfg.group; + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + }; users.groups.${cfg.group} = {}; users.groups.bitcoinrpc-public = {}; nix-bitcoin.operator.groups = [ cfg.group ]; diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index 1429f9a..4b89add 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -230,6 +230,7 @@ in { }; in self; users.users.${cfg.nbxplorer.user} = { + isSystemUser = true; group = cfg.nbxplorer.group; extraGroups = [ "bitcoinrpc-public" ] ++ optional cfg.btcpayserver.lbtc cfg.liquidd.group; @@ -237,6 +238,7 @@ in { }; users.groups.${cfg.nbxplorer.group} = {}; users.users.${cfg.btcpayserver.user} = { + isSystemUser = true; group = cfg.btcpayserver.group; extraGroups = [ cfg.nbxplorer.group ] ++ optional (cfg.btcpayserver.lightningBackend == "clightning") cfg.clightning.user; diff --git a/modules/charge-lnd.nix b/modules/charge-lnd.nix index c926cee..b6cf8aa 100644 --- a/modules/charge-lnd.nix +++ b/modules/charge-lnd.nix @@ -133,8 +133,8 @@ in }; users.users.${user} = { - group = group; isSystemUser = true; + group = group; }; users.groups.${group} = {}; }; diff --git a/modules/clightning.nix b/modules/clightning.nix index 1fbff34..0cadfc3 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -140,6 +140,7 @@ in { }; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; extraGroups = [ "bitcoinrpc-public" ]; }; diff --git a/modules/electrs.nix b/modules/electrs.nix index 53a20ad..880cc1a 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -106,6 +106,7 @@ in { }; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; extraGroups = [ "bitcoinrpc-public" ] ++ optionals cfg.high-memory [ bitcoind.user ]; }; diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index bf5bac4..3dcb291 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -270,6 +270,7 @@ in { }; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; home = cfg.dataDir; # Allow access to the tor control socket, needed for payjoin onion service creation diff --git a/modules/liquid.nix b/modules/liquid.nix index 215ef5f..7e375a7 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -240,6 +240,7 @@ in { }; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; extraGroups = [ "bitcoinrpc-public" ]; }; diff --git a/modules/lnd.nix b/modules/lnd.nix index cf5fa9c..33bbce0 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -263,6 +263,7 @@ in { }; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; extraGroups = [ "bitcoinrpc-public" ]; home = cfg.dataDir; # lnd creates .lnd dir in HOME diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 68d48ab..377af6d 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -97,6 +97,7 @@ in { }; users.users.recurring-donations = { + isSystemUser = true; group = "recurring-donations"; extraGroups = [ config.services.clightning.group ]; }; diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index f9947ec..c305337 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -65,6 +65,7 @@ in { services.clightning.enable = true; users.users.${cfg.user} = { + isSystemUser = true; group = cfg.group; extraGroups = [ config.services.clightning.group ]; }; From 178a0dcf8f0849a774ff4f08cac282151aa5fb33 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:00 +0200 Subject: [PATCH 05/17] services: use new 'tor' options --- modules/bitcoind.nix | 2 +- modules/btcpayserver.nix | 2 +- modules/clightning.nix | 2 +- modules/joinmarket-ob-watcher.nix | 16 +++++++++------- modules/joinmarket.nix | 21 ++++++++++++--------- modules/lightning-loop.nix | 2 +- modules/lightning-pool.nix | 2 +- modules/liquid.nix | 2 +- modules/lnd-rest-onion-service.nix | 5 +++-- modules/lnd.nix | 2 +- modules/modules.nix | 8 +++++++- modules/netns-isolation.nix | 9 +++++++-- modules/nodeinfo.nix | 6 +++--- modules/onion-services.nix | 8 ++++---- modules/presets/secure-node.nix | 2 +- modules/recurring-donations.nix | 2 +- modules/spark-wallet.nix | 2 +- pkgs/lib.nix | 2 +- 18 files changed, 56 insertions(+), 39 deletions(-) diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index e78ca37..3ca5c2a 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -193,7 +193,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = "Connect through SOCKS5 proxy"; }; listen = mkOption { diff --git a/modules/btcpayserver.nix b/modules/btcpayserver.nix index 4b89add..1e1f45f 100644 --- a/modules/btcpayserver.nix +++ b/modules/btcpayserver.nix @@ -184,7 +184,7 @@ in { network=${config.services.bitcoind.network} bind=${cfg.btcpayserver.address} port=${toString cfg.btcpayserver.port} - socksendpoint=${cfg.tor.client.socksListenAddress} + socksendpoint=${config.nix-bitcoin.torClientAddressWithPort} btcexplorerurl=${nbExplorerUrl} btcexplorercookiefile=${nbExplorerCookie} postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb diff --git a/modules/clightning.nix b/modules/clightning.nix index 0cadfc3..f415a30 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -34,7 +34,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = '' Socks proxy for connecting to Tor nodes (or for all connections if option always-use-proxy is set). ''; diff --git a/modules/joinmarket-ob-watcher.nix b/modules/joinmarket-ob-watcher.nix index c668dde..f8b6760 100644 --- a/modules/joinmarket-ob-watcher.nix +++ b/modules/joinmarket-ob-watcher.nix @@ -5,7 +5,13 @@ let cfg = config.services.joinmarket-ob-watcher; nbLib = config.nix-bitcoin.lib; nbPkgs = config.nix-bitcoin.pkgs; - torAddress = builtins.head (builtins.split ":" config.services.tor.client.socksListenAddress); + + socks5Settings = with config.services.tor.client.socksListenAddress; '' + socks5 = true + socks5_host = ${addr} + socks5_port = ${toString port} + ''; + configFile = builtins.toFile "config" '' [BLOCKCHAIN] blockchain_source = no-blockchain @@ -15,18 +21,14 @@ let channel = joinmarket-pit port = 6697 usessl = true - socks5 = true - socks5_host = ${torAddress} - socks5_port = 9050 + ${socks5Settings} [MESSAGING:server2] host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion channel = joinmarket-pit port = 6667 usessl = false - socks5 = true - socks5_host = ${torAddress} - socks5_port = 9050 + ${socks5Settings} ''; in { options.services.joinmarket-ob-watcher = { diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index 3dcb291..2d27094 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -10,7 +10,14 @@ let runAsUser = config.nix-bitcoin.runAsUserCmd; inherit (config.services) bitcoind; - torAddress = builtins.head (builtins.split ":" config.services.tor.client.socksListenAddress); + + torAddress = config.services.tor.client.socksListenAddress; + socks5Settings = '' + socks5 = true + socks5_host = ${torAddress.addr} + socks5_port = ${toString torAddress.port} + ''; + # Based on https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/configure.py yg = cfg.yieldgenerator; configFile = builtins.toFile "config" '' @@ -34,18 +41,14 @@ let channel = joinmarket-pit port = 6697 usessl = true - socks5 = true - socks5_host = ${torAddress} - socks5_port = 9050 + ${socks5Settings} [MESSAGING:server2] host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion channel = joinmarket-pit port = 6667 usessl = false - socks5 = true - socks5_host = ${torAddress} - socks5_port = 9050 + ${socks5Settings} [LOGGING] console_log_level = INFO @@ -72,8 +75,8 @@ let disable_output_substitution = 0 max_additional_fee_contribution = default min_fee_rate = 1.1 - onion_socks5_host = ${torAddress} - onion_socks5_port = 9050 + onion_socks5_host = ${torAddress.addr} + onion_socks5_port = ${toString torAddress.port} tor_control_host = unix:/run/tor/control hidden_service_ssl = false diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix index 9d40b9c..18cce78 100644 --- a/modules/lightning-loop.nix +++ b/modules/lightning-loop.nix @@ -60,7 +60,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = "host:port of SOCKS5 proxy for connnecting to the loop server."; }; extraConfig = mkOption { diff --git a/modules/lightning-pool.nix b/modules/lightning-pool.nix index 7736d0a..74ba9d5 100644 --- a/modules/lightning-pool.nix +++ b/modules/lightning-pool.nix @@ -54,7 +54,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = "host:port of SOCKS5 proxy for connnecting to the pool auction server."; }; extraConfig = mkOption { diff --git a/modules/liquid.nix b/modules/liquid.nix index 7e375a7..28f2e4f 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -144,7 +144,7 @@ in { }; proxy = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = "Connect through SOCKS5 proxy"; }; listen = mkOption { diff --git a/modules/lnd-rest-onion-service.nix b/modules/lnd-rest-onion-service.nix index 31415f3..1d873cc 100644 --- a/modules/lnd-rest-onion-service.nix +++ b/modules/lnd-rest-onion-service.nix @@ -40,8 +40,9 @@ in { config = mkIf cfg.enable { services.tor = { enable = true; - hiddenServices.lnd-rest = nbLib.mkHiddenService { - toHost = lnd.restAddress; + relay.onionServices.lnd-rest = nbLib.mkOnionService { + target.addr = lnd.restAddress; + target.port = lnd.restPort; port = lnd.restPort; }; }; diff --git a/modules/lnd.nix b/modules/lnd.nix index 33bbce0..d5c29e1 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -83,7 +83,7 @@ in { }; tor-socks = mkOption { type = types.nullOr types.str; - default = if cfg.enforceTor then config.services.tor.client.socksListenAddress else null; + default = if cfg.enforceTor then config.nix-bitcoin.torClientAddressWithPort else null; description = "Socks proxy for connecting to Tor nodes"; }; macaroons = mkOption { diff --git a/modules/modules.nix b/modules/modules.nix index 2ebe733..fbfe272 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -49,13 +49,19 @@ with lib; default = import ../pkgs/lib.nix lib pkgs; }; + torClientAddressWithPort = mkOption { + readOnly = true; + default = with config.services.tor.client.socksListenAddress; + "${addr}:${toString port}"; + }; + # Torify binary that works with custom Tor SOCKS addresses # Related issue: https://github.com/NixOS/nixpkgs/issues/94236 torify = mkOption { readOnly = true; default = pkgs.writeScriptBin "torify" '' ${pkgs.tor}/bin/torify \ - --address ${head (splitString ":" config.services.tor.client.socksListenAddress)} \ + --address ${config.services.tor.client.socksListenAddress.addr} \ "$@" ''; }; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 36ba446..83db4cc 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -97,8 +97,13 @@ in { # Base infrastructure { networking.dhcpcd.denyInterfaces = [ "nb-br" "nb-veth*" ]; - services.tor.client.socksListenAddress = "${bridgeIp}:9050"; - networking.firewall.interfaces.nb-br.allowedTCPPorts = [ 9050 ]; + services.tor.client.socksListenAddress = { + addr = bridgeIp; + # Default NixOS values. These must be repeated when redefining this option. + port = 9050; + IsolateDestAddr = true; + }; + networking.firewall.interfaces.nb-br.allowedTCPPorts = [ config.services.tor.client.socksListenAddress.port ]; boot.kernel.sysctl."net.ipv4.ip_forward" = true; security.wrappers.netns-exec = { diff --git a/modules/nodeinfo.nix b/modules/nodeinfo.nix index ab27bbd..782446b 100644 --- a/modules/nodeinfo.nix +++ b/modules/nodeinfo.nix @@ -95,12 +95,12 @@ let ''; mkIfOnionPort = name: fn: - if hiddenServices ? ${name} then - fn (toString (builtins.elemAt hiddenServices.${name}.map 0).port) + if onionServices ? ${name} then + fn (toString (builtins.elemAt onionServices.${name}.map 0).port) else ""; - inherit (config.services.tor) hiddenServices; + inherit (config.services.tor.relay) onionServices; in { options = { nix-bitcoin.nodeinfo = { diff --git a/modules/onion-services.nix b/modules/onion-services.nix index a250c0b..a6d39a0 100644 --- a/modules/onion-services.nix +++ b/modules/onion-services.nix @@ -57,14 +57,14 @@ in { # Define hidden services services.tor = { enable = true; - hiddenServices = genAttrs activeServices (name: + relay.onionServices = genAttrs activeServices (name: let service = config.services.${name}; inherit (cfg.${name}) externalPort; - in nbLib.mkHiddenService { + in nbLib.mkOnionService { port = if externalPort != null then externalPort else service.port; - toPort = service.port; - toHost = if service.address == "0.0.0.0" then "127.0.0.1" else service.address; + target.port = service.port; + target.addr = if service.address == "0.0.0.0" then "127.0.0.1" else service.address; } ); }; diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 70a850e..25a6d47 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -29,7 +29,7 @@ in { ]; # sshd - services.tor.hiddenServices.sshd = nbLib.mkHiddenService { port = 22; }; + services.tor.relay.onionServices.sshd = nbLib.mkOnionService { port = 22; }; nix-bitcoin.onionAddresses.access.${operatorName} = [ "sshd" ]; services.bitcoind = { diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 377af6d..63a5333 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -11,7 +11,7 @@ let NAME=$1 AMOUNT=$2 echo Attempting to pay $AMOUNT sat to $NAME - INVOICE=$(curl --socks5-hostname ${config.services.tor.client.socksListenAddress} -d "satoshi_amount=$AMOUNT&payment_method=ln&id=$NAME&type=profile" -X POST https://api.tallyco.in/v1/payment/request/ | jq -r '.lightning_pay_request') 2> /dev/null + INVOICE=$(curl --socks5-hostname ${config.nix-bitcoin.torClientAddressWithPort} -d "satoshi_amount=$AMOUNT&payment_method=ln&id=$NAME&type=profile" -X POST https://api.tallyco.in/v1/payment/request/ | jq -r '.lightning_pay_request') 2> /dev/null if [ -z "$INVOICE" ] || [ "$INVOICE" = "null" ]; then echo "ERROR: did not get invoice from tallycoin" return diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index c305337..ca307ce 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -8,7 +8,7 @@ let # Use wasabi rate provider because the default (bitstamp) doesn't accept # connections through Tor - torRateProvider = "--rate-provider wasabi --proxy socks5h://${config.services.tor.client.socksListenAddress}"; + torRateProvider = "--rate-provider wasabi --proxy socks5h://${config.nix-bitcoin.torClientAddressWithPort}"; startScript = '' ${optionalString (cfg.getPublicAddressCmd != "") '' publicURL="--public-url http://$(${cfg.getPublicAddressCmd})" diff --git a/pkgs/lib.nix b/pkgs/lib.nix index a024ef9..1d53289 100644 --- a/pkgs/lib.nix +++ b/pkgs/lib.nix @@ -80,7 +80,7 @@ let self = { default = "exec"; }; - mkHiddenService = map: { + mkOnionService = map: { map = [ map ]; version = 3; }; From 35fe939cf8698da725af77a3b21078f42041c8a2 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:01 +0200 Subject: [PATCH 06/17] security: update /proc restriction mechanism NixOS option `security.hideProcessInformation` for globally restricting access to /proc has been removed. Use per-service restrictions via 'ProtectProc' instead. Rename `nix-bitcoin.security.hideProcessInformation` to `nix-bitcoin.security.dbusHideProcessInformation` because this option now only implements the dbus restriction. --- modules/presets/secure-node.nix | 2 +- modules/security.nix | 31 ++++++++++++++++++++----------- pkgs/lib.nix | 5 ++--- test/tests.py | 4 ---- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 25a6d47..4036282 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -18,7 +18,7 @@ in { networking.firewall.enable = true; - nix-bitcoin.security.hideProcessInformation = true; + nix-bitcoin.security.dbusHideProcessInformation = true; # Use doas instead of sudo security.doas.enable = true; diff --git a/modules/security.nix b/modules/security.nix index cd5ad4e..0ea57b7 100644 --- a/modules/security.nix +++ b/modules/security.nix @@ -1,20 +1,29 @@ -{ config, lib, pkgs, options, ... }: +{ config, lib, pkgs, ... }: +with lib; { options = { - nix-bitcoin.security.hideProcessInformation = options.security.hideProcessInformation; + nix-bitcoin.security.dbusHideProcessInformation = mkOption { + type = types.bool; + default = false; + description = '' + Only allow users with group 'proc' to retrieve systemd unit information like + cgroup paths (i.e. (sub)process command lines) via D-Bus. + + This mitigates a systemd security issue where (sub)process command lines can + be retrieved by services even when their access to /proc is restricted + (via ProtectProc). + + This option works by restricting the D-Bus method 'GetUnitProcesses', which + is also used internally by `systemctl status`. + ''; + }; }; - config = lib.mkIf config.nix-bitcoin.security.hideProcessInformation { - # Only show the current user's processes in /proc. - # Users with group 'proc' can still access all processes. - security.hideProcessInformation = true; + config = mkIf config.nix-bitcoin.security.dbusHideProcessInformation { + users.groups.proc = {}; + nix-bitcoin.operator.groups = [ "proc" ]; # Enable operator access to systemd-status - # This mitigates a systemd security issue leaking (sub)process - # command lines. - # Only allow users with group 'proc' to retrieve systemd unit information like - # cgroup paths (i.e. (sub)process command lines) via D-Bus. - # This D-Bus call is used by `systemctl status`. services.dbus.packages = lib.mkAfter [ # Apply at the end to override the default policy (pkgs.writeTextDir "etc/dbus-1/system.d/dbus.conf" '' diff --git a/pkgs/lib.nix b/pkgs/lib.nix index 1d53289..660820b 100644 --- a/pkgs/lib.nix +++ b/pkgs/lib.nix @@ -17,9 +17,8 @@ let self = { ProtectKernelModules = "true"; ProtectKernelLogs = "true"; ProtectClock = "true"; - # Test and enable these when systemd v247 is available - # ProtectProc = "invisible"; - # ProcSubset = "pid"; + ProtectProc = "invisible"; + ProcSubset = "pid"; ProtectControlGroups = "true"; RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; RestrictNamespaces = "true"; diff --git a/test/tests.py b/test/tests.py index 724e2d4..1208848 100644 --- a/test/tests.py +++ b/test/tests.py @@ -85,9 +85,6 @@ def _(): succeed('[[ $(stat -c "%U:%G %a" /secrets/dummy) = "root:root 440" ]]') if "secure-node" in enabled_tests: - # Access to '/proc' should be restricted - machine.succeed("grep -Fq hidepid=2 /proc/mounts") - machine.wait_for_unit("bitcoind") # `systemctl status` run by unprivileged users shouldn't leak cgroup info assert_matches( @@ -97,7 +94,6 @@ def _(): # The 'operator' with group 'proc' has full access assert_full_match("runuser -u operator -- systemctl status bitcoind 2>&1 >/dev/null", "") - @test("bitcoind") def _(): assert_running("bitcoind") From a0e5894f1f2f6514cfda5c8fa434efccada4254b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:02 +0200 Subject: [PATCH 07/17] backups: remove illegal option definition --- modules/backups.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/backups.nix b/modules/backups.nix index 6209d64..946f526 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -82,7 +82,6 @@ in { services.postgresqlBackup = { enable = true; databases = [ "btcpaydb" ]; - startAt = []; }; systemd.services.duplicity = rec { wants = [ "postgresqlBackup-btcpaydb.service" ]; From 3aab1fc26736d6c711aae642fd2175296d14f5ac Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:03 +0200 Subject: [PATCH 08/17] spark-wallet: update to new node-env --- pkgs/spark-wallet/composition.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/spark-wallet/composition.nix b/pkgs/spark-wallet/composition.nix index 3bc2c21..b98b4f8 100644 --- a/pkgs/spark-wallet/composition.nix +++ b/pkgs/spark-wallet/composition.nix @@ -6,7 +6,8 @@ let nodeEnv = import "${toString pkgs.path}/pkgs/development/node-packages/node-env.nix" { - inherit (pkgs) stdenv python2 utillinux runCommand writeTextFile; + inherit pkgs; + inherit (pkgs) lib stdenv python2 runCommand writeTextFile; inherit nodejs; libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; }; From ca64a4a64f9fba2cddde9fb40f7040e65f5045d7 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:04 +0200 Subject: [PATCH 09/17] clightning-plugins.prometheus: use current nixpkgs version of prometheus-client --- pkgs/clightning-plugins/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/clightning-plugins/default.nix b/pkgs/clightning-plugins/default.nix index d1dadc3..50c8e0e 100644 --- a/pkgs/clightning-plugins/default.nix +++ b/pkgs/clightning-plugins/default.nix @@ -17,7 +17,7 @@ let monitor = {}; prometheus = { extraPkgs = [ prometheus_client ]; - patchRequirements = "--replace prometheus-client==0.6.0 prometheus-client==0.8.0"; + patchRequirements = "--replace prometheus-client==0.6.0 prometheus-client==0.9.0"; }; rebalance = {}; summary = { From 161baa7e68dac7ce5129d30de9ecd9fc85c3219b Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:05 +0200 Subject: [PATCH 10/17] joinmarket-ob-watcher: allow required 'mbind' system call --- modules/joinmarket-ob-watcher.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/joinmarket-ob-watcher.nix b/modules/joinmarket-ob-watcher.nix index f8b6760..82499ef 100644 --- a/modules/joinmarket-ob-watcher.nix +++ b/modules/joinmarket-ob-watcher.nix @@ -80,6 +80,7 @@ in { ${nbPkgs.joinmarket}/bin/ob-watcher --datadir=${cfg.dataDir} \ --host=${cfg.address} --port=${toString cfg.port} ''; + SystemCallFilter = nbLib.defaultHardening.SystemCallFilter ++ [ "mbind" ] ; Restart = "on-failure"; RestartSec = "10s"; } // nbLib.allowTor; From c4c2b03e190c7e672c56fe7d9c43c73038992cb6 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:06 +0200 Subject: [PATCH 11/17] extra-container: 0.6 -> 0.7 Version 0.7 adds support for NixOS 21.05. --- pkgs/extra-container/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/extra-container/default.nix b/pkgs/extra-container/default.nix index 98691c2..07b495f 100644 --- a/pkgs/extra-container/default.nix +++ b/pkgs/extra-container/default.nix @@ -4,11 +4,11 @@ stdenv.mkDerivation rec { pname = "extra-container"; - version = "0.6"; + version = "0.7"; src = builtins.fetchTarball { url = "https://github.com/erikarvstedt/extra-container/archive/${version}.tar.gz"; - sha256 = "0hm4xfjbqjrrq7n1pkbs33lpw9k5q3ms3psprqhfsxkkwzy78zlm"; + sha256 = "1hcbi611vm0kn8rl7q974wcjkihpddan6m3p7hx8l8jnv18ydng8"; }; buildCommand = '' From c1c663d0a90270618bcd36bb092d24773a63e8d6 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:07 +0200 Subject: [PATCH 12/17] tests: fix formatting --- test/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.py b/test/tests.py index 1208848..4f8d2fa 100644 --- a/test/tests.py +++ b/test/tests.py @@ -239,7 +239,7 @@ def _(): @test("joinmarket-yieldgenerator") def _(): machine.wait_until_succeeds( - log_has_string("joinmarket-yieldgenerator", "Critical error updating blockheight.",) + log_has_string("joinmarket-yieldgenerator", "Critical error updating blockheight.") ) From 1be924529d24c86d8b2b7f0d00e116ee6f3b093e Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:08 +0200 Subject: [PATCH 13/17] tests: adapt to new linter The Python test driver now uses 'pyflakes'. Remove hacks that were needed for the 'black' linter. --- test/lib/make-test-vm.nix | 30 +++++------------------------- test/tests.py | 9 +++++---- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/test/lib/make-test-vm.nix b/test/lib/make-test-vm.nix index 8742ac2..7ddce50 100644 --- a/test/lib/make-test-vm.nix +++ b/test/lib/make-test-vm.nix @@ -10,36 +10,16 @@ args: let test = pythonTesting.makeTest args; - fixedDriver = test.driver.overrideAttrs (old: let - # Allow the test script to have longer lines by fixing the call to the 'black' - # code formatter. - # The default width of 88 chars is too restrictive for our script. - parts = builtins.split ''/nix/store/[^ ]+/black '' old.buildCommand; - preMatch = builtins.elemAt parts 0; - postMatch = builtins.elemAt parts 2; - in { - # See `mkDriver` in nixpkgs/nixos/lib/testing-python.nix for the original definition of `buildCommand` - buildCommand = '' - ${preMatch}${pkgs.python3Packages.black}/bin/black --line-length 100 ${postMatch} - ''; - # Keep reference to the `testDriver` derivation, required by `buildCommand` - testDriverReference = old.buildCommand; - }); - - # 1. Use fixed driver - # 2. Save test logging output - # 3. Add link to driver so that a gcroot to a test prevents the driver from + # 1. Save test logging output + # 2. Add link to driver so that a gcroot to a test prevents the driver from # being garbage-collected fixedTest = test.overrideAttrs (_: { # See `runTests` in nixpkgs/nixos/lib/testing-python.nix for the original definition of `buildCommand` buildCommand = '' mkdir $out - LOGFILE=$out/output.xml tests='exec(os.environ["testScript"])' ${fixedDriver}/bin/nixos-test-driver - ln -s ${fixedDriver} $out/driver + LOGFILE=$out/output.xml tests='exec(os.environ["testScript"])' ${test.driver}/bin/nixos-test-driver + ln -s ${test.driver} $out/driver ''; - }) // { - driver = fixedDriver; - inherit (test) nodes; - }; + }); in fixedTest diff --git a/test/tests.py b/test/tests.py index 4f8d2fa..a3bb85c 100644 --- a/test/tests.py +++ b/test/tests.py @@ -1,6 +1,7 @@ from collections import OrderedDict import json +logger = machine.logger def succeed(*cmds): """Returns the concatenated output of all cmds""" @@ -39,7 +40,7 @@ def wait_for_open_port(address, port): status, _ = machine.execute(f"nc -z {address} {port}") return status == 0 - with log.nested(f"Waiting for TCP port {address}:{port}"): + with logger.nested(f"Waiting for TCP port {address}:{port}"): retry(is_port_open) @@ -66,7 +67,7 @@ def run_tests(): raise RuntimeError(f"The following tests are enabled but not defined: {enabled}") machine.connect() # Visually separate boot output from the test output for test in to_run: - with log.nested(f"test: {test}"): + with logger.nested(f"test: {test}"): tests[test]() @@ -150,9 +151,9 @@ def _(): f"Output of 'lightning-cli plugin list':\n{plugin_list}" ) else: - log.log("Active clightning plugins:") + logger.log("Active clightning plugins:") for p in test_data["clightning-plugins"]: - log.log(os.path.basename(p)) + logger.log(os.path.basename(p)) @test("lnd") From 01804e6dfb1ec3a18ee281390866262ea351be46 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:09 +0200 Subject: [PATCH 14/17] tests: improve test script formatting Remove annyoing spacing constraints enforced by the previous 'black' linter. --- test/tests.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/test/tests.py b/test/tests.py index a3bb85c..75efbb2 100644 --- a/test/tests.py +++ b/test/tests.py @@ -7,34 +7,28 @@ def succeed(*cmds): """Returns the concatenated output of all cmds""" return machine.succeed(*cmds) - def assert_matches(cmd, regexp): out = succeed(cmd) if not re.search(regexp, out): raise Exception(f"Pattern '{regexp}' not found in '{out}'") - def assert_full_match(cmd, regexp): out = succeed(cmd) if not re.fullmatch(regexp, out): raise Exception(f"Pattern '{regexp}' doesn't match '{out}'") - def log_has_string(unit, str): return f"journalctl -b --output=cat -u {unit} --grep='{str}'" - def assert_no_failure(unit): """Unit should not have failed since the system is running""" machine.fail(log_has_string(unit, "Failed with result")) - def assert_running(unit): with machine.nested(f"waiting for unit: {unit}"): machine.wait_for_unit(unit) assert_no_failure(unit) - def wait_for_open_port(address, port): def is_port_open(_): status, _ = machine.execute(f"nc -z {address} {port}") @@ -48,14 +42,11 @@ def wait_for_open_port(address, port): tests = OrderedDict() - def test(name): def x(fn): tests[name] = fn - return x - def run_tests(): enabled = enabled_tests.copy() to_run = [] @@ -70,7 +61,6 @@ def run_tests(): with logger.nested(f"test: {test}"): tests[test]() - def run_test(test): tests[test]() @@ -78,7 +68,6 @@ def run_test(test): ### Tests # All tests are executed in the order they are defined here - @test("security") def _(): assert_running("setup-secrets") @@ -112,7 +101,6 @@ def _(): log_has_string("bitcoind", "RPC User public not allowed to call method stop") ) - @test("electrs") def _(): assert_running("electrs") @@ -120,14 +108,12 @@ def _(): # Check RPC connection to bitcoind machine.wait_until_succeeds(log_has_string("electrs", "NetworkInfo")) - # Impure: Stops electrs # Stop electrs from spamming the test log with 'WARN - wait until IBD is over' messages @test("stop-electrs") def _(): succeed("systemctl stop electrs") - @test("liquidd") def _(): assert_running("liquidd") @@ -135,7 +121,6 @@ def _(): assert_matches("runuser -u operator -- elements-cli getnetworkinfo | jq", '"version"') succeed("runuser -u operator -- liquidswap-cli --help") - @test("clightning") def _(): assert_running("clightning") @@ -155,19 +140,16 @@ def _(): for p in test_data["clightning-plugins"]: logger.log(os.path.basename(p)) - @test("lnd") def _(): assert_running("lnd") assert_matches("runuser -u operator -- lncli getinfo | jq", '"version"') assert_no_failure("lnd") - @test("lnd-rest-onion-service") def _(): assert_matches("runuser -u operator -- lndconnect-rest-onion -j", ".onion") - @test("lightning-loop") def _(): assert_running("lightning-loop") @@ -181,7 +163,6 @@ def _(): ) ) - @test("lightning-pool") def _(): assert_running("lightning-pool") @@ -195,14 +176,12 @@ def _(): ) ) - @test("charge-lnd") def _(): # charge-lnd is a oneshot service that is started by a timer under regular operation succeed("systemctl start charge-lnd") assert_no_failure("charge-lnd") - @test("btcpayserver") def _(): assert_running("nbxplorer") @@ -220,7 +199,6 @@ def _(): '"version"', ) - @test("spark-wallet") def _(): assert_running("spark-wallet") @@ -228,7 +206,6 @@ def _(): spark_auth = re.search("login=(.*)", succeed("cat /secrets/spark-wallet-login"))[1] assert_matches(f"curl -s {spark_auth}@{ip('spark-wallet')}:9737", "Spark") - @test("joinmarket") def _(): assert_running("joinmarket") @@ -236,20 +213,17 @@ def _(): log_has_string("joinmarket", "JMDaemonServerProtocolFactory starting on 27183") ) - @test("joinmarket-yieldgenerator") def _(): machine.wait_until_succeeds( log_has_string("joinmarket-yieldgenerator", "Critical error updating blockheight.") ) - @test("joinmarket-ob-watcher") def _(): assert_running("joinmarket-ob-watcher") machine.wait_until_succeeds(log_has_string("joinmarket-ob-watcher", "Starting ob-watcher")) - @test("nodeinfo") def _(): status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null") @@ -259,12 +233,10 @@ def _(): info = json.loads(json_info) assert info["bitcoind"]["local_address"] - @test("secure-node") def _(): assert_running("onion-addresses") - # Run this test before the following tests that shut down services # (and their corresponding network namespaces). @test("netns-isolation") @@ -347,7 +319,6 @@ def _(): assert_file_exists("secrets/lnd-wallet-password") - # Impure: restarts services @test("banlist-and-restart") def _(): @@ -368,7 +339,6 @@ def _(): ) assert_no_failure("bitcoind-import-banlist") - @test("regtest") def _(): def enabled(unit): @@ -407,14 +377,9 @@ def _(): ) succeed("runuser -u operator -- pool orders list") - if "netns-isolation" in enabled_tests: - def ip(name): return test_data["netns"][name]["address"] - - else: - def ip(_): return "127.0.0.1" From 308a11f22b0f544457afd9e04d18bf79ccc033ee Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:10 +0200 Subject: [PATCH 15/17] tests: avoid postgresql timeout failures on CI nodes --- test/tests.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/tests.nix b/test/tests.nix index 86c4216..f68374e 100644 --- a/test/tests.nix +++ b/test/tests.nix @@ -106,6 +106,9 @@ let systemd.services.setup-secrets.preStart = mkIfTest "security" '' install -D -o nobody -g nogroup -m777 <(:) /secrets/dummy ''; + + # Avoid timeout failures on slow CI nodes + systemd.services.postgresql.serviceConfig.TimeoutStartSec = "3min"; } (mkIf config.test.features.clightningPlugins { services.clightning.plugins = { From 7c876664b1cc40ab4046c37cbd726744b2b3d8df Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 5 Aug 2021 00:49:11 +0200 Subject: [PATCH 16/17] netns test: update matching of 'capsh' output The output now contains multiple lines. --- test/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests.py b/test/tests.py index 75efbb2..88856e9 100644 --- a/test/tests.py +++ b/test/tests.py @@ -271,9 +271,9 @@ def _(): if "joinmarket" in enabled_tests: # netns-exec should drop capabilities - assert_full_match( + assert_matches( "runuser -u operator -- netns-exec nb-joinmarket capsh --print | grep Current", - "Current: =\n", + re.compile("^Current: =$", re.MULTILINE), ) if "clightning" in enabled_tests: From a2454975a58ce3d3773f4ec5b2c0cd50738cbcac Mon Sep 17 00:00:00 2001 From: Erik Arvstedt Date: Thu, 12 Aug 2021 14:35:24 +0200 Subject: [PATCH 17/17] doas: fix recursive calls to doas Doas was broken for recursive calls like `doas -u operator lncli` where `lncli` internally calls doas. --- modules/modules.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/modules.nix b/modules/modules.nix index fbfe272..c32bdf0 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -70,7 +70,8 @@ with lib; runAsUserCmd = mkOption { readOnly = true; default = if config.security.doas.enable - then "doas -u" + # TODO: Use absolute path until https://github.com/NixOS/nixpkgs/pull/133622 is available. + then "/run/wrappers/bin/doas -u" else "sudo -u"; }; };