Merge fort-nix/nix-bitcoin#374: Update to NixOS 21.05

a2454975a5 doas: fix recursive calls to doas (Erik Arvstedt)
7c876664b1 netns test: update matching of 'capsh' output (Erik Arvstedt)
308a11f22b tests: avoid postgresql timeout failures on CI nodes (Erik Arvstedt)
01804e6dfb tests: improve test script formatting (Erik Arvstedt)
1be924529d tests: adapt to new linter (Erik Arvstedt)
c1c663d0a9 tests: fix formatting (Erik Arvstedt)
c4c2b03e19 extra-container: 0.6 -> 0.7 (Erik Arvstedt)
161baa7e68 joinmarket-ob-watcher: allow required 'mbind' system call (Erik Arvstedt)
ca64a4a64f clightning-plugins.prometheus: use current nixpkgs version of prometheus-client (Erik Arvstedt)
3aab1fc267 spark-wallet: update to new node-env (Erik Arvstedt)
a0e5894f1f backups: remove illegal option definition (Erik Arvstedt)
35fe939cf8 security: update /proc restriction mechanism (Erik Arvstedt)
178a0dcf8f services: use new 'tor' options (Erik Arvstedt)
e44f78ebb8 services: set isSystemUser for service users (Erik Arvstedt)
0ef66c920b treewide: use services.getty option (Erik Arvstedt)
a25ceecca5 update to NixOS 21.05 (Erik Arvstedt)
b758150c9e pinned: expose nixpkgsStable, nixpkgsUnstable (Erik Arvstedt)

Pull request description:

ACKs for top commit:
  jonasnick:
    ACK a2454975a5

Tree-SHA512: a8a25c25d835662ec63c3a042eb237d29b857b2030d9023a8b4ead94e03a4f9dffe2d6616e2a286800e40288985e5db3a55056d6b45d8984161b9a19aba28a60
This commit is contained in:
Jonas Nick 2021-08-14 15:01:37 +00:00
commit 08c8f70ebe
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
34 changed files with 126 additions and 139 deletions

View File

@ -240,7 +240,7 @@
# compatible, in order to avoid breaking some software such as database # compatible, in order to avoid breaking some software such as database
# servers. You should change this only after NixOS release notes say you # servers. You should change this only after NixOS release notes say you
# should. # 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. # The nix-bitcoin release version that your config is compatible with.
# When upgrading to a backwards-incompatible release, nix-bitcoin will display an # When upgrading to a backwards-incompatible release, nix-bitcoin will display an

View File

@ -4,7 +4,7 @@
config = { config = {
virtualisation.graphics = false; virtualisation.graphics = false;
services.mingetty.autologinUser = "root"; services.getty.autologinUser = "root";
users.users.root = { users.users.root = {
openssh.authorizedKeys.keyFiles = [ ./id-vm.pub ]; openssh.authorizedKeys.keyFiles = [ ./id-vm.pub ];
}; };

View File

@ -82,7 +82,6 @@ in {
services.postgresqlBackup = { services.postgresqlBackup = {
enable = true; enable = true;
databases = [ "btcpaydb" ]; databases = [ "btcpaydb" ];
startAt = [];
}; };
systemd.services.duplicity = rec { systemd.services.duplicity = rec {
wants = [ "postgresqlBackup-btcpaydb.service" ]; wants = [ "postgresqlBackup-btcpaydb.service" ];

View File

@ -193,7 +193,7 @@ in {
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; 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"; description = "Connect through SOCKS5 proxy";
}; };
listen = mkOption { listen = mkOption {
@ -388,7 +388,10 @@ in {
} // nbLib.allowLocalIPAddresses; } // nbLib.allowLocalIPAddresses;
}; };
users.users.${cfg.user}.group = cfg.group; users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
};
users.groups.${cfg.group} = {}; users.groups.${cfg.group} = {};
users.groups.bitcoinrpc-public = {}; users.groups.bitcoinrpc-public = {};
nix-bitcoin.operator.groups = [ cfg.group ]; nix-bitcoin.operator.groups = [ cfg.group ];

View File

@ -184,7 +184,7 @@ in {
network=${config.services.bitcoind.network} network=${config.services.bitcoind.network}
bind=${cfg.btcpayserver.address} bind=${cfg.btcpayserver.address}
port=${toString cfg.btcpayserver.port} port=${toString cfg.btcpayserver.port}
socksendpoint=${cfg.tor.client.socksListenAddress} socksendpoint=${config.nix-bitcoin.torClientAddressWithPort}
btcexplorerurl=${nbExplorerUrl} btcexplorerurl=${nbExplorerUrl}
btcexplorercookiefile=${nbExplorerCookie} btcexplorercookiefile=${nbExplorerCookie}
postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb postgres=User ID=${cfg.btcpayserver.user};Host=/run/postgresql;Database=btcpaydb
@ -230,6 +230,7 @@ in {
}; in self; }; in self;
users.users.${cfg.nbxplorer.user} = { users.users.${cfg.nbxplorer.user} = {
isSystemUser = true;
group = cfg.nbxplorer.group; group = cfg.nbxplorer.group;
extraGroups = [ "bitcoinrpc-public" ] extraGroups = [ "bitcoinrpc-public" ]
++ optional cfg.btcpayserver.lbtc cfg.liquidd.group; ++ optional cfg.btcpayserver.lbtc cfg.liquidd.group;
@ -237,6 +238,7 @@ in {
}; };
users.groups.${cfg.nbxplorer.group} = {}; users.groups.${cfg.nbxplorer.group} = {};
users.users.${cfg.btcpayserver.user} = { users.users.${cfg.btcpayserver.user} = {
isSystemUser = true;
group = cfg.btcpayserver.group; group = cfg.btcpayserver.group;
extraGroups = [ cfg.nbxplorer.group ] extraGroups = [ cfg.nbxplorer.group ]
++ optional (cfg.btcpayserver.lightningBackend == "clightning") cfg.clightning.user; ++ optional (cfg.btcpayserver.lightningBackend == "clightning") cfg.clightning.user;

View File

@ -133,8 +133,8 @@ in
}; };
users.users.${user} = { users.users.${user} = {
group = group;
isSystemUser = true; isSystemUser = true;
group = group;
}; };
users.groups.${group} = {}; users.groups.${group} = {};
}; };

View File

@ -34,7 +34,7 @@ in {
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; 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 = '' 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).
''; '';
@ -140,6 +140,7 @@ in {
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ]; extraGroups = [ "bitcoinrpc-public" ];
}; };

View File

@ -106,6 +106,7 @@ in {
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ] ++ optionals cfg.high-memory [ bitcoind.user ]; extraGroups = [ "bitcoinrpc-public" ] ++ optionals cfg.high-memory [ bitcoind.user ];
}; };

View File

@ -5,7 +5,13 @@ let
cfg = config.services.joinmarket-ob-watcher; cfg = config.services.joinmarket-ob-watcher;
nbLib = config.nix-bitcoin.lib; nbLib = config.nix-bitcoin.lib;
nbPkgs = config.nix-bitcoin.pkgs; 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" '' configFile = builtins.toFile "config" ''
[BLOCKCHAIN] [BLOCKCHAIN]
blockchain_source = no-blockchain blockchain_source = no-blockchain
@ -15,18 +21,14 @@ let
channel = joinmarket-pit channel = joinmarket-pit
port = 6697 port = 6697
usessl = true usessl = true
socks5 = true ${socks5Settings}
socks5_host = ${torAddress}
socks5_port = 9050
[MESSAGING:server2] [MESSAGING:server2]
host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion
channel = joinmarket-pit channel = joinmarket-pit
port = 6667 port = 6667
usessl = false usessl = false
socks5 = true ${socks5Settings}
socks5_host = ${torAddress}
socks5_port = 9050
''; '';
in { in {
options.services.joinmarket-ob-watcher = { options.services.joinmarket-ob-watcher = {
@ -78,6 +80,7 @@ in {
${nbPkgs.joinmarket}/bin/ob-watcher --datadir=${cfg.dataDir} \ ${nbPkgs.joinmarket}/bin/ob-watcher --datadir=${cfg.dataDir} \
--host=${cfg.address} --port=${toString cfg.port} --host=${cfg.address} --port=${toString cfg.port}
''; '';
SystemCallFilter = nbLib.defaultHardening.SystemCallFilter ++ [ "mbind" ] ;
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "10s"; RestartSec = "10s";
} // nbLib.allowTor; } // nbLib.allowTor;

View File

@ -10,7 +10,14 @@ let
runAsUser = config.nix-bitcoin.runAsUserCmd; runAsUser = config.nix-bitcoin.runAsUserCmd;
inherit (config.services) bitcoind; 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 # Based on https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/configure.py
yg = cfg.yieldgenerator; yg = cfg.yieldgenerator;
configFile = builtins.toFile "config" '' configFile = builtins.toFile "config" ''
@ -34,18 +41,14 @@ let
channel = joinmarket-pit channel = joinmarket-pit
port = 6697 port = 6697
usessl = true usessl = true
socks5 = true ${socks5Settings}
socks5_host = ${torAddress}
socks5_port = 9050
[MESSAGING:server2] [MESSAGING:server2]
host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion
channel = joinmarket-pit channel = joinmarket-pit
port = 6667 port = 6667
usessl = false usessl = false
socks5 = true ${socks5Settings}
socks5_host = ${torAddress}
socks5_port = 9050
[LOGGING] [LOGGING]
console_log_level = INFO console_log_level = INFO
@ -72,8 +75,8 @@ let
disable_output_substitution = 0 disable_output_substitution = 0
max_additional_fee_contribution = default max_additional_fee_contribution = default
min_fee_rate = 1.1 min_fee_rate = 1.1
onion_socks5_host = ${torAddress} onion_socks5_host = ${torAddress.addr}
onion_socks5_port = 9050 onion_socks5_port = ${toString torAddress.port}
tor_control_host = unix:/run/tor/control tor_control_host = unix:/run/tor/control
hidden_service_ssl = false hidden_service_ssl = false
@ -270,6 +273,7 @@ in {
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
home = cfg.dataDir; home = cfg.dataDir;
# Allow access to the tor control socket, needed for payjoin onion service creation # Allow access to the tor control socket, needed for payjoin onion service creation

View File

@ -60,7 +60,7 @@ in {
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; 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."; description = "host:port of SOCKS5 proxy for connnecting to the loop server.";
}; };
extraConfig = mkOption { extraConfig = mkOption {

View File

@ -54,7 +54,7 @@ in {
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; 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."; description = "host:port of SOCKS5 proxy for connnecting to the pool auction server.";
}; };
extraConfig = mkOption { extraConfig = mkOption {

View File

@ -144,7 +144,7 @@ in {
}; };
proxy = mkOption { proxy = mkOption {
type = types.nullOr types.str; 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"; description = "Connect through SOCKS5 proxy";
}; };
listen = mkOption { listen = mkOption {
@ -240,6 +240,7 @@ in {
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ]; extraGroups = [ "bitcoinrpc-public" ];
}; };

View File

@ -40,8 +40,9 @@ in {
config = mkIf cfg.enable { config = mkIf cfg.enable {
services.tor = { services.tor = {
enable = true; enable = true;
hiddenServices.lnd-rest = nbLib.mkHiddenService { relay.onionServices.lnd-rest = nbLib.mkOnionService {
toHost = lnd.restAddress; target.addr = lnd.restAddress;
target.port = lnd.restPort;
port = lnd.restPort; port = lnd.restPort;
}; };
}; };

View File

@ -83,7 +83,7 @@ in {
}; };
tor-socks = mkOption { tor-socks = mkOption {
type = types.nullOr types.str; 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"; description = "Socks proxy for connecting to Tor nodes";
}; };
macaroons = mkOption { macaroons = mkOption {
@ -263,6 +263,7 @@ in {
}; };
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ]; extraGroups = [ "bitcoinrpc-public" ];
home = cfg.dataDir; # lnd creates .lnd dir in HOME home = cfg.dataDir; # lnd creates .lnd dir in HOME

View File

@ -49,13 +49,19 @@ with lib;
default = import ../pkgs/lib.nix lib pkgs; 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 # Torify binary that works with custom Tor SOCKS addresses
# Related issue: https://github.com/NixOS/nixpkgs/issues/94236 # Related issue: https://github.com/NixOS/nixpkgs/issues/94236
torify = mkOption { torify = mkOption {
readOnly = true; readOnly = true;
default = pkgs.writeScriptBin "torify" '' default = pkgs.writeScriptBin "torify" ''
${pkgs.tor}/bin/torify \ ${pkgs.tor}/bin/torify \
--address ${head (splitString ":" config.services.tor.client.socksListenAddress)} \ --address ${config.services.tor.client.socksListenAddress.addr} \
"$@" "$@"
''; '';
}; };
@ -64,7 +70,8 @@ with lib;
runAsUserCmd = mkOption { runAsUserCmd = mkOption {
readOnly = true; readOnly = true;
default = if config.security.doas.enable 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"; else "sudo -u";
}; };
}; };

View File

@ -97,8 +97,13 @@ in {
# Base infrastructure # Base infrastructure
{ {
networking.dhcpcd.denyInterfaces = [ "nb-br" "nb-veth*" ]; networking.dhcpcd.denyInterfaces = [ "nb-br" "nb-veth*" ];
services.tor.client.socksListenAddress = "${bridgeIp}:9050"; services.tor.client.socksListenAddress = {
networking.firewall.interfaces.nb-br.allowedTCPPorts = [ 9050 ]; 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; boot.kernel.sysctl."net.ipv4.ip_forward" = true;
security.wrappers.netns-exec = { security.wrappers.netns-exec = {

View File

@ -95,12 +95,12 @@ let
''; '';
mkIfOnionPort = name: fn: mkIfOnionPort = name: fn:
if hiddenServices ? ${name} then if onionServices ? ${name} then
fn (toString (builtins.elemAt hiddenServices.${name}.map 0).port) fn (toString (builtins.elemAt onionServices.${name}.map 0).port)
else else
""; "";
inherit (config.services.tor) hiddenServices; inherit (config.services.tor.relay) onionServices;
in { in {
options = { options = {
nix-bitcoin.nodeinfo = { nix-bitcoin.nodeinfo = {

View File

@ -57,14 +57,14 @@ in {
# Define hidden services # Define hidden services
services.tor = { services.tor = {
enable = true; enable = true;
hiddenServices = genAttrs activeServices (name: relay.onionServices = genAttrs activeServices (name:
let let
service = config.services.${name}; service = config.services.${name};
inherit (cfg.${name}) externalPort; inherit (cfg.${name}) externalPort;
in nbLib.mkHiddenService { in nbLib.mkOnionService {
port = if externalPort != null then externalPort else service.port; port = if externalPort != null then externalPort else service.port;
toPort = service.port; target.port = service.port;
toHost = if service.address == "0.0.0.0" then "127.0.0.1" else service.address; target.addr = if service.address == "0.0.0.0" then "127.0.0.1" else service.address;
} }
); );
}; };

View File

@ -9,6 +9,6 @@
# Needed for sandboxed builds and services # Needed for sandboxed builds and services
security.allowUserNamespaces = true; 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"; environment.memoryAllocator.provider = "libc";
} }

View File

@ -18,7 +18,7 @@ in {
networking.firewall.enable = true; networking.firewall.enable = true;
nix-bitcoin.security.hideProcessInformation = true; nix-bitcoin.security.dbusHideProcessInformation = true;
# Use doas instead of sudo # Use doas instead of sudo
security.doas.enable = true; security.doas.enable = true;
@ -29,7 +29,7 @@ in {
]; ];
# sshd # sshd
services.tor.hiddenServices.sshd = nbLib.mkHiddenService { port = 22; }; services.tor.relay.onionServices.sshd = nbLib.mkOnionService { port = 22; };
nix-bitcoin.onionAddresses.access.${operatorName} = [ "sshd" ]; nix-bitcoin.onionAddresses.access.${operatorName} = [ "sshd" ];
services.bitcoind = { services.bitcoind = {

View File

@ -11,7 +11,7 @@ let
NAME=$1 NAME=$1
AMOUNT=$2 AMOUNT=$2
echo Attempting to pay $AMOUNT sat to $NAME 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 if [ -z "$INVOICE" ] || [ "$INVOICE" = "null" ]; then
echo "ERROR: did not get invoice from tallycoin" echo "ERROR: did not get invoice from tallycoin"
return return
@ -97,6 +97,7 @@ in {
}; };
users.users.recurring-donations = { users.users.recurring-donations = {
isSystemUser = true;
group = "recurring-donations"; group = "recurring-donations";
extraGroups = [ config.services.clightning.group ]; extraGroups = [ config.services.clightning.group ];
}; };

View File

@ -1,20 +1,29 @@
{ config, lib, pkgs, options, ... }: { config, lib, pkgs, ... }:
with lib;
{ {
options = { 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 { config = mkIf config.nix-bitcoin.security.dbusHideProcessInformation {
# Only show the current user's processes in /proc. users.groups.proc = {};
# Users with group 'proc' can still access all processes. nix-bitcoin.operator.groups = [ "proc" ]; # Enable operator access to systemd-status
security.hideProcessInformation = true;
# 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 services.dbus.packages = lib.mkAfter [ # Apply at the end to override the default policy
(pkgs.writeTextDir "etc/dbus-1/system.d/dbus.conf" '' (pkgs.writeTextDir "etc/dbus-1/system.d/dbus.conf" ''
<busconfig> <busconfig>

View File

@ -8,7 +8,7 @@ let
# Use wasabi rate provider because the default (bitstamp) doesn't accept # Use wasabi rate provider because the default (bitstamp) doesn't accept
# connections through Tor # 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 = '' startScript = ''
${optionalString (cfg.getPublicAddressCmd != "") '' ${optionalString (cfg.getPublicAddressCmd != "") ''
publicURL="--public-url http://$(${cfg.getPublicAddressCmd})" publicURL="--public-url http://$(${cfg.getPublicAddressCmd})"
@ -65,6 +65,7 @@ in {
services.clightning.enable = true; services.clightning.enable = true;
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = [ config.services.clightning.group ]; extraGroups = [ config.services.clightning.group ];
}; };

View File

@ -17,7 +17,7 @@ let
monitor = {}; monitor = {};
prometheus = { prometheus = {
extraPkgs = [ prometheus_client ]; 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 = {}; rebalance = {};
summary = { summary = {

View File

@ -4,11 +4,11 @@
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
pname = "extra-container"; pname = "extra-container";
version = "0.6"; version = "0.7";
src = builtins.fetchTarball { src = builtins.fetchTarball {
url = "https://github.com/erikarvstedt/extra-container/archive/${version}.tar.gz"; url = "https://github.com/erikarvstedt/extra-container/archive/${version}.tar.gz";
sha256 = "0hm4xfjbqjrrq7n1pkbs33lpw9k5q3ms3psprqhfsxkkwzy78zlm"; sha256 = "1hcbi611vm0kn8rl7q974wcjkihpddan6m3p7hx8l8jnv18ydng8";
}; };
buildCommand = '' buildCommand = ''

View File

@ -17,9 +17,8 @@ let self = {
ProtectKernelModules = "true"; ProtectKernelModules = "true";
ProtectKernelLogs = "true"; ProtectKernelLogs = "true";
ProtectClock = "true"; ProtectClock = "true";
# Test and enable these when systemd v247 is available ProtectProc = "invisible";
# ProtectProc = "invisible"; ProcSubset = "pid";
# ProcSubset = "pid";
ProtectControlGroups = "true"; ProtectControlGroups = "true";
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
RestrictNamespaces = "true"; RestrictNamespaces = "true";
@ -80,7 +79,7 @@ let self = {
default = "exec"; default = "exec";
}; };
mkHiddenService = map: { mkOnionService = map: {
map = [ map ]; map = [ map ];
version = 3; version = 3;
}; };

View File

@ -8,8 +8,9 @@ in
{ {
# To update, run ../helper/fetch-channel REV # To update, run ../helper/fetch-channel REV
nixpkgs = fetch { nixpkgs = fetch {
rev = "359e6542e1d41eb18df55c82bdb08bf738fae2cf"; # nixos-21.05 (2021-08-03)
sha256 = "05v28njaas9l26ibc6vy6imvy7grbkli32bmv0n32x6x9cf68gf9"; rev = "d4590d21006387dcb190c516724cb1e41c0f8fdf";
sha256 = "17q39hlx1x87xf2rdygyimj8whdbx33nzszf4rxkc6b85wz0l38n";
}; };
nixpkgs-unstable = fetch { nixpkgs-unstable = fetch {
rev = "16105403bdd843540cbef9c63fc0f16c1c6eaa70"; rev = "16105403bdd843540cbef9c63fc0f16c1c6eaa70";

View File

@ -21,6 +21,8 @@ in
lightning-loop lightning-loop
lightning-pool; lightning-pool;
inherit nixpkgsStable nixpkgsUnstable;
stable = nixBitcoinPkgsStable; stable = nixBitcoinPkgsStable;
unstable = nixBitcoinPkgsUnstable; unstable = nixBitcoinPkgsUnstable;
} }

View File

@ -6,7 +6,8 @@
let let
nodeEnv = import "${toString pkgs.path}/pkgs/development/node-packages/node-env.nix" { 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; inherit nodejs;
libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null;
}; };

View File

@ -10,36 +10,16 @@ args:
let let
test = pythonTesting.makeTest args; test = pythonTesting.makeTest args;
fixedDriver = test.driver.overrideAttrs (old: let # 1. Save test logging output
# Allow the test script to have longer lines by fixing the call to the 'black' # 2. Add link to driver so that a gcroot to a test prevents the driver from
# 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
# being garbage-collected # being garbage-collected
fixedTest = test.overrideAttrs (_: { fixedTest = test.overrideAttrs (_: {
# See `runTests` in nixpkgs/nixos/lib/testing-python.nix for the original definition of `buildCommand` # See `runTests` in nixpkgs/nixos/lib/testing-python.nix for the original definition of `buildCommand`
buildCommand = '' buildCommand = ''
mkdir $out mkdir $out
LOGFILE=$out/output.xml tests='exec(os.environ["testScript"])' ${fixedDriver}/bin/nixos-test-driver LOGFILE=$out/output.xml tests='exec(os.environ["testScript"])' ${test.driver}/bin/nixos-test-driver
ln -s ${fixedDriver} $out/driver ln -s ${test.driver} $out/driver
''; '';
}) // { });
driver = fixedDriver;
inherit (test) nodes;
};
in in
fixedTest fixedTest

View File

@ -69,7 +69,7 @@ name: testConfig:
"${toString pkgs.path}/nixos/modules/virtualisation/qemu-vm.nix" "${toString pkgs.path}/nixos/modules/virtualisation/qemu-vm.nix"
]; ];
virtualisation.graphics = false; virtualisation.graphics = false;
services.mingetty.autologinUser = "root"; services.getty.autologinUser = "root";
# Provide a shortcut for instant poweroff from within the machine # Provide a shortcut for instant poweroff from within the machine
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [

View File

@ -106,6 +106,9 @@ let
systemd.services.setup-secrets.preStart = mkIfTest "security" '' systemd.services.setup-secrets.preStart = mkIfTest "security" ''
install -D -o nobody -g nogroup -m777 <(:) /secrets/dummy 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 { (mkIf config.test.features.clightningPlugins {
services.clightning.plugins = { services.clightning.plugins = {

View File

@ -1,45 +1,40 @@
from collections import OrderedDict from collections import OrderedDict
import json import json
logger = machine.logger
def succeed(*cmds): def succeed(*cmds):
"""Returns the concatenated output of all cmds""" """Returns the concatenated output of all cmds"""
return machine.succeed(*cmds) return machine.succeed(*cmds)
def assert_matches(cmd, regexp): def assert_matches(cmd, regexp):
out = succeed(cmd) out = succeed(cmd)
if not re.search(regexp, out): if not re.search(regexp, out):
raise Exception(f"Pattern '{regexp}' not found in '{out}'") raise Exception(f"Pattern '{regexp}' not found in '{out}'")
def assert_full_match(cmd, regexp): def assert_full_match(cmd, regexp):
out = succeed(cmd) out = succeed(cmd)
if not re.fullmatch(regexp, out): if not re.fullmatch(regexp, out):
raise Exception(f"Pattern '{regexp}' doesn't match '{out}'") raise Exception(f"Pattern '{regexp}' doesn't match '{out}'")
def log_has_string(unit, str): def log_has_string(unit, str):
return f"journalctl -b --output=cat -u {unit} --grep='{str}'" return f"journalctl -b --output=cat -u {unit} --grep='{str}'"
def assert_no_failure(unit): def assert_no_failure(unit):
"""Unit should not have failed since the system is running""" """Unit should not have failed since the system is running"""
machine.fail(log_has_string(unit, "Failed with result")) machine.fail(log_has_string(unit, "Failed with result"))
def assert_running(unit): def assert_running(unit):
with machine.nested(f"waiting for unit: {unit}"): with machine.nested(f"waiting for unit: {unit}"):
machine.wait_for_unit(unit) machine.wait_for_unit(unit)
assert_no_failure(unit) assert_no_failure(unit)
def wait_for_open_port(address, port): def wait_for_open_port(address, port):
def is_port_open(_): def is_port_open(_):
status, _ = machine.execute(f"nc -z {address} {port}") status, _ = machine.execute(f"nc -z {address} {port}")
return status == 0 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) retry(is_port_open)
@ -47,14 +42,11 @@ def wait_for_open_port(address, port):
tests = OrderedDict() tests = OrderedDict()
def test(name): def test(name):
def x(fn): def x(fn):
tests[name] = fn tests[name] = fn
return x return x
def run_tests(): def run_tests():
enabled = enabled_tests.copy() enabled = enabled_tests.copy()
to_run = [] to_run = []
@ -66,10 +58,9 @@ def run_tests():
raise RuntimeError(f"The following tests are enabled but not defined: {enabled}") raise RuntimeError(f"The following tests are enabled but not defined: {enabled}")
machine.connect() # Visually separate boot output from the test output machine.connect() # Visually separate boot output from the test output
for test in to_run: for test in to_run:
with log.nested(f"test: {test}"): with logger.nested(f"test: {test}"):
tests[test]() tests[test]()
def run_test(test): def run_test(test):
tests[test]() tests[test]()
@ -77,7 +68,6 @@ def run_test(test):
### Tests ### Tests
# All tests are executed in the order they are defined here # All tests are executed in the order they are defined here
@test("security") @test("security")
def _(): def _():
assert_running("setup-secrets") assert_running("setup-secrets")
@ -85,9 +75,6 @@ def _():
succeed('[[ $(stat -c "%U:%G %a" /secrets/dummy) = "root:root 440" ]]') succeed('[[ $(stat -c "%U:%G %a" /secrets/dummy) = "root:root 440" ]]')
if "secure-node" in enabled_tests: 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") machine.wait_for_unit("bitcoind")
# `systemctl status` run by unprivileged users shouldn't leak cgroup info # `systemctl status` run by unprivileged users shouldn't leak cgroup info
assert_matches( assert_matches(
@ -97,7 +84,6 @@ def _():
# The 'operator' with group 'proc' has full access # The 'operator' with group 'proc' has full access
assert_full_match("runuser -u operator -- systemctl status bitcoind 2>&1 >/dev/null", "") assert_full_match("runuser -u operator -- systemctl status bitcoind 2>&1 >/dev/null", "")
@test("bitcoind") @test("bitcoind")
def _(): def _():
assert_running("bitcoind") assert_running("bitcoind")
@ -115,7 +101,6 @@ def _():
log_has_string("bitcoind", "RPC User public not allowed to call method stop") log_has_string("bitcoind", "RPC User public not allowed to call method stop")
) )
@test("electrs") @test("electrs")
def _(): def _():
assert_running("electrs") assert_running("electrs")
@ -123,14 +108,12 @@ def _():
# Check RPC connection to bitcoind # Check RPC connection to bitcoind
machine.wait_until_succeeds(log_has_string("electrs", "NetworkInfo")) machine.wait_until_succeeds(log_has_string("electrs", "NetworkInfo"))
# Impure: Stops electrs # Impure: Stops electrs
# Stop electrs from spamming the test log with 'WARN - wait until IBD is over' messages # Stop electrs from spamming the test log with 'WARN - wait until IBD is over' messages
@test("stop-electrs") @test("stop-electrs")
def _(): def _():
succeed("systemctl stop electrs") succeed("systemctl stop electrs")
@test("liquidd") @test("liquidd")
def _(): def _():
assert_running("liquidd") assert_running("liquidd")
@ -138,7 +121,6 @@ def _():
assert_matches("runuser -u operator -- elements-cli getnetworkinfo | jq", '"version"') assert_matches("runuser -u operator -- elements-cli getnetworkinfo | jq", '"version"')
succeed("runuser -u operator -- liquidswap-cli --help") succeed("runuser -u operator -- liquidswap-cli --help")
@test("clightning") @test("clightning")
def _(): def _():
assert_running("clightning") assert_running("clightning")
@ -154,10 +136,9 @@ def _():
f"Output of 'lightning-cli plugin list':\n{plugin_list}" f"Output of 'lightning-cli plugin list':\n{plugin_list}"
) )
else: else:
log.log("Active clightning plugins:") logger.log("Active clightning plugins:")
for p in test_data["clightning-plugins"]: for p in test_data["clightning-plugins"]:
log.log(os.path.basename(p)) logger.log(os.path.basename(p))
@test("lnd") @test("lnd")
def _(): def _():
@ -165,12 +146,10 @@ def _():
assert_matches("runuser -u operator -- lncli getinfo | jq", '"version"') assert_matches("runuser -u operator -- lncli getinfo | jq", '"version"')
assert_no_failure("lnd") assert_no_failure("lnd")
@test("lnd-rest-onion-service") @test("lnd-rest-onion-service")
def _(): def _():
assert_matches("runuser -u operator -- lndconnect-rest-onion -j", ".onion") assert_matches("runuser -u operator -- lndconnect-rest-onion -j", ".onion")
@test("lightning-loop") @test("lightning-loop")
def _(): def _():
assert_running("lightning-loop") assert_running("lightning-loop")
@ -184,7 +163,6 @@ def _():
) )
) )
@test("lightning-pool") @test("lightning-pool")
def _(): def _():
assert_running("lightning-pool") assert_running("lightning-pool")
@ -198,14 +176,12 @@ def _():
) )
) )
@test("charge-lnd") @test("charge-lnd")
def _(): def _():
# charge-lnd is a oneshot service that is started by a timer under regular operation # charge-lnd is a oneshot service that is started by a timer under regular operation
succeed("systemctl start charge-lnd") succeed("systemctl start charge-lnd")
assert_no_failure("charge-lnd") assert_no_failure("charge-lnd")
@test("btcpayserver") @test("btcpayserver")
def _(): def _():
assert_running("nbxplorer") assert_running("nbxplorer")
@ -223,7 +199,6 @@ def _():
'"version"', '"version"',
) )
@test("spark-wallet") @test("spark-wallet")
def _(): def _():
assert_running("spark-wallet") assert_running("spark-wallet")
@ -231,7 +206,6 @@ def _():
spark_auth = re.search("login=(.*)", succeed("cat /secrets/spark-wallet-login"))[1] spark_auth = re.search("login=(.*)", succeed("cat /secrets/spark-wallet-login"))[1]
assert_matches(f"curl -s {spark_auth}@{ip('spark-wallet')}:9737", "Spark") assert_matches(f"curl -s {spark_auth}@{ip('spark-wallet')}:9737", "Spark")
@test("joinmarket") @test("joinmarket")
def _(): def _():
assert_running("joinmarket") assert_running("joinmarket")
@ -239,20 +213,17 @@ def _():
log_has_string("joinmarket", "JMDaemonServerProtocolFactory starting on 27183") log_has_string("joinmarket", "JMDaemonServerProtocolFactory starting on 27183")
) )
@test("joinmarket-yieldgenerator") @test("joinmarket-yieldgenerator")
def _(): def _():
machine.wait_until_succeeds( machine.wait_until_succeeds(
log_has_string("joinmarket-yieldgenerator", "Critical error updating blockheight.",) log_has_string("joinmarket-yieldgenerator", "Critical error updating blockheight.")
) )
@test("joinmarket-ob-watcher") @test("joinmarket-ob-watcher")
def _(): def _():
assert_running("joinmarket-ob-watcher") assert_running("joinmarket-ob-watcher")
machine.wait_until_succeeds(log_has_string("joinmarket-ob-watcher", "Starting ob-watcher")) machine.wait_until_succeeds(log_has_string("joinmarket-ob-watcher", "Starting ob-watcher"))
@test("nodeinfo") @test("nodeinfo")
def _(): def _():
status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null") status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null")
@ -262,12 +233,10 @@ def _():
info = json.loads(json_info) info = json.loads(json_info)
assert info["bitcoind"]["local_address"] assert info["bitcoind"]["local_address"]
@test("secure-node") @test("secure-node")
def _(): def _():
assert_running("onion-addresses") assert_running("onion-addresses")
# Run this test before the following tests that shut down services # Run this test before the following tests that shut down services
# (and their corresponding network namespaces). # (and their corresponding network namespaces).
@test("netns-isolation") @test("netns-isolation")
@ -302,9 +271,9 @@ def _():
if "joinmarket" in enabled_tests: if "joinmarket" in enabled_tests:
# netns-exec should drop capabilities # netns-exec should drop capabilities
assert_full_match( assert_matches(
"runuser -u operator -- netns-exec nb-joinmarket capsh --print | grep Current", "runuser -u operator -- netns-exec nb-joinmarket capsh --print | grep Current",
"Current: =\n", re.compile("^Current: =$", re.MULTILINE),
) )
if "clightning" in enabled_tests: if "clightning" in enabled_tests:
@ -350,7 +319,6 @@ def _():
assert_file_exists("secrets/lnd-wallet-password") assert_file_exists("secrets/lnd-wallet-password")
# Impure: restarts services # Impure: restarts services
@test("banlist-and-restart") @test("banlist-and-restart")
def _(): def _():
@ -371,7 +339,6 @@ def _():
) )
assert_no_failure("bitcoind-import-banlist") assert_no_failure("bitcoind-import-banlist")
@test("regtest") @test("regtest")
def _(): def _():
def enabled(unit): def enabled(unit):
@ -410,14 +377,9 @@ def _():
) )
succeed("runuser -u operator -- pool orders list") succeed("runuser -u operator -- pool orders list")
if "netns-isolation" in enabled_tests: if "netns-isolation" in enabled_tests:
def ip(name): def ip(name):
return test_data["netns"][name]["address"] return test_data["netns"][name]["address"]
else: else:
def ip(_): def ip(_):
return "127.0.0.1" return "127.0.0.1"