Skip to content

Commit a9c163c

Browse files
committed
Merge #221: nbxplorer/btcpayserver: add module
c9c844d btcpayserver: add tests (nixbitcoin) f93c3c8 backups: add nbxplorer and btcpayserver datadir to includelist (nixbitcoin) 605b37c nodeinfo: add btcpayserver onion (nixbitcoin) 15b574f nbxplorer/btcpayserver: add module (nixbitcoin) 46d681a lnd: generate custom macaroons (nixbitcoin) 6f032e3 lnd: fix mnemonic file access vulnerability (Erik Arvstedt) b97584f netns: allow return traffic to outgoing connections (nixbitcoin) 9929532 temp: mirror erikarvstedt btcpayserver (Calvin Kim) Pull request description: ACKs for top commit: erikarvstedt: ACK c9c844d Tree-SHA512: 0020964db37f5c5db3343ddef88f2e7e8d8ad48760ece73125fd9d2feaed0a3789ba3fd3eff98c225a675b49447b1728cd2c9eb4fa495c961e8376b28d32bad9
2 parents adae7da + c9c844d commit a9c163c

24 files changed

+3600
-9
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ env:
2727
- PKG=elementsd STABLE=0
2828
- PKG=electrs STABLE=1
2929
- PKG=electrs STABLE=0
30+
- PKG=nbxplorer STABLE=1
31+
- PKG=nbxplorer STABLE=0
32+
- PKG=btcpayserver STABLE=1
33+
- PKG=btcpayserver STABLE=0
3034
- PKG=liquid-swap STABLE=1
3135
- PKG=lightning-loop STABLE=0
3236
- PKG=nixops19_09 STABLE=1

examples/configuration.nix

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@
7676
# sync faster. Only available if hardware wallets are disabled.
7777
# services.electrs.high-memory = true;
7878

79+
### BTCPayServer
80+
# Enable this module to use BTCPayServer, a self-hosted, open-source
81+
# cryptocurrency payment processor.
82+
# Privacy Warning: BTCPayServer currently looks up price rates without
83+
# proxying them through Tor. This means an outside observer can correlate
84+
# your BTCPayServer usage, like invoice creation times, with your IP address.
85+
# services.btcpayserver.enable = true;
86+
# Enable this option to connect BTCPayServer to clightning.
87+
# services.btcpayserver.lightningBackend = "clightning";
88+
# Enable this option to connect BTCPayServert to lnd.
89+
# services.btcpayserver.lightningBackend = "lnd";
90+
# Afterwards you need to go into Store > General Settings > Lightning Nodes
91+
# and click to use "the internal lightning node of this BTCPay Server".
92+
7993
### LIQUIDD
8094
# Enable this module to use Liquid, a sidechain for an inter-exchange
8195
# settlement network linking together cryptocurrency exchanges and

modules/backups.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ let
1616
${config.services.liquidd.dataDir}
1717
${optionalString cfg.with-bulk-data "${config.services.electrs.dataDir}"}
1818
${config.services.lightning-charge.dataDir}
19+
${config.services.nbxplorer.dataDir}
20+
${config.services.btcpayserver.dataDir}
1921
/var/lib/tor
2022
# Extra files
2123
${cfg.extraFiles}

modules/btcpayserver.nix

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
{ config, lib, pkgs, ... }:
2+
3+
with lib;
4+
5+
let
6+
cfg = config.services;
7+
inherit (config) nix-bitcoin-services;
8+
in {
9+
options.services = {
10+
nbxplorer = {
11+
package = mkOption {
12+
type = types.package;
13+
default = pkgs.nix-bitcoin.nbxplorer;
14+
defaultText = "pkgs.nix-bitcoin.nbxplorer";
15+
description = "The package providing nbxplorer binaries.";
16+
};
17+
dataDir = mkOption {
18+
type = types.path;
19+
default = "/var/lib/nbxplorer";
20+
description = "The data directory for nbxplorer.";
21+
};
22+
user = mkOption {
23+
type = types.str;
24+
default = "nbxplorer";
25+
description = "The user as which to run nbxplorer.";
26+
};
27+
group = mkOption {
28+
type = types.str;
29+
default = cfg.nbxplorer.user;
30+
description = "The group as which to run nbxplorer.";
31+
};
32+
bind = mkOption {
33+
type = types.str;
34+
default = "127.0.0.1";
35+
description = "The address on which to bind.";
36+
};
37+
enable = mkOption {
38+
# This option is only used by netns-isolation
39+
internal = true;
40+
default = cfg.btcpayserver.enable;
41+
};
42+
enforceTor = nix-bitcoin-services.enforceTor;
43+
};
44+
45+
btcpayserver = {
46+
enable = mkEnableOption "btcpayserver";
47+
package = mkOption {
48+
type = types.package;
49+
default = pkgs.nix-bitcoin.btcpayserver;
50+
defaultText = "pkgs.nix-bitcoin.btcpayserver";
51+
description = "The package providing btcpayserver binaries.";
52+
};
53+
dataDir = mkOption {
54+
type = types.path;
55+
default = "/var/lib/btcpayserver";
56+
description = "The data directory for btcpayserver.";
57+
};
58+
user = mkOption {
59+
type = types.str;
60+
default = "btcpayserver";
61+
description = "The user as which to run btcpayserver.";
62+
};
63+
group = mkOption {
64+
type = types.str;
65+
default = cfg.btcpayserver.user;
66+
description = "The group as which to run btcpayserver.";
67+
};
68+
bind = mkOption {
69+
type = types.str;
70+
default = "127.0.0.1";
71+
description = "The address on which to bind.";
72+
};
73+
lightningBackend = mkOption {
74+
type = types.nullOr (types.enum [ "clightning" "lnd" ]);
75+
default = null;
76+
description = "The lightning node implementation to use.";
77+
};
78+
enforceTor = nix-bitcoin-services.enforceTor;
79+
};
80+
};
81+
82+
config = mkIf cfg.btcpayserver.enable {
83+
assertions = let
84+
backend = cfg.btcpayserver.lightningBackend;
85+
in [
86+
{ assertion = (backend != null) -> cfg.${backend}.enable;
87+
message = "btcpayserver requires ${backend}.";
88+
}
89+
];
90+
91+
systemd.tmpfiles.rules = [
92+
"d '${cfg.nbxplorer.dataDir}' 0770 ${cfg.nbxplorer.user} ${cfg.nbxplorer.group} - -"
93+
"d '${cfg.btcpayserver.dataDir}' 0770 ${cfg.btcpayserver.user} ${cfg.btcpayserver.group} - -"
94+
];
95+
96+
systemd.services.nbxplorer = let
97+
configFile = builtins.toFile "config" ''
98+
network=mainnet
99+
btcrpcuser=${cfg.bitcoind.rpc.users.btcpayserver.name}
100+
btcrpcurl=http://${builtins.elemAt config.services.bitcoind.rpcbind 0}:8332
101+
btcnodeendpoint=${config.services.bitcoind.bind}:8333
102+
bind=${cfg.nbxplorer.bind}
103+
'';
104+
in {
105+
description = "Run nbxplorer";
106+
wantedBy = [ "multi-user.target" ];
107+
requires = [ "bitcoind.service" ];
108+
after = [ "bitcoind.service" ];
109+
preStart = ''
110+
install -m 600 ${configFile} ${cfg.nbxplorer.dataDir}/settings.config
111+
echo "btcrpcpassword=$(cat ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-btcpayserver)" \
112+
>> '${cfg.nbxplorer.dataDir}/settings.config'
113+
'';
114+
serviceConfig = nix-bitcoin-services.defaultHardening // {
115+
ExecStart = ''
116+
${cfg.nbxplorer.package}/bin/nbxplorer --conf=${cfg.nbxplorer.dataDir}/settings.config \
117+
--datadir=${cfg.nbxplorer.dataDir}
118+
'';
119+
User = cfg.nbxplorer.user;
120+
Restart = "on-failure";
121+
RestartSec = "10s";
122+
ReadWritePaths = cfg.nbxplorer.dataDir;
123+
MemoryDenyWriteExecute = "false";
124+
} // (if cfg.nbxplorer.enforceTor
125+
then nix-bitcoin-services.allowTor
126+
else nix-bitcoin-services.allowAnyIP
127+
);
128+
};
129+
130+
systemd.services.btcpayserver = let
131+
configFile = builtins.toFile "config" (''
132+
network=mainnet
133+
socksendpoint=${cfg.tor.client.socksListenAddress}
134+
btcexplorerurl=http://${cfg.nbxplorer.bind}:24444/
135+
btcexplorercookiefile=${cfg.nbxplorer.dataDir}/Main/.cookie
136+
bind=${cfg.btcpayserver.bind}
137+
'' + optionalString (cfg.btcpayserver.lightningBackend == "clightning") ''
138+
btclightning=type=clightning;server=unix:///${cfg.clightning.dataDir}/bitcoin/lightning-rpc
139+
'');
140+
lndConfig =
141+
"btclightning=type=lnd-rest;" +
142+
"server=https://${toString cfg.lnd.listen}:${toString cfg.lnd.restPort}/;" +
143+
"macaroonfilepath=/run/lnd/btcpayserver.macaroon;" +
144+
"certthumbprint=";
145+
in let self = {
146+
wantedBy = [ "multi-user.target" ];
147+
requires = [ "nbxplorer.service" ]
148+
++ optional (cfg.btcpayserver.lightningBackend != null) "${cfg.btcpayserver.lightningBackend}.service";
149+
after = self.requires;
150+
preStart = ''
151+
install -m 600 ${configFile} ${cfg.btcpayserver.dataDir}/settings.config
152+
${optionalString (cfg.btcpayserver.lightningBackend == "lnd") ''
153+
{
154+
echo -n "${lndConfig}";
155+
${pkgs.openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${config.nix-bitcoin.secretsDir}/lnd-cert \
156+
| sed -e 's/.*=//;s/://g';
157+
} >> ${cfg.btcpayserver.dataDir}/settings.config
158+
''}
159+
'';
160+
serviceConfig = nix-bitcoin-services.defaultHardening // {
161+
ExecStart = ''
162+
${cfg.btcpayserver.package}/bin/btcpayserver --conf=${cfg.btcpayserver.dataDir}/settings.config \
163+
--datadir=${cfg.btcpayserver.dataDir}
164+
'';
165+
User = cfg.btcpayserver.user;
166+
Restart = "on-failure";
167+
RestartSec = "10s";
168+
ReadWritePaths = cfg.btcpayserver.dataDir;
169+
MemoryDenyWriteExecute = "false";
170+
} // (if cfg.btcpayserver.enforceTor
171+
then nix-bitcoin-services.allowTor
172+
else nix-bitcoin-services.allowAnyIP
173+
);
174+
}; in self;
175+
176+
services.lnd.macaroons.btcpayserver = mkIf (cfg.btcpayserver.lightningBackend == "lnd") {
177+
inherit (cfg.btcpayserver) user;
178+
permissions = ''{"entity":"info","action":"read"},{"entity":"onchain","action":"read"},{"entity":"offchain","action":"read"},{"entity":"address","action":"read"},{"entity":"message","action":"read"},{"entity":"peers","action":"read"},{"entity":"signer","action":"read"},{"entity":"invoices","action":"read"},{"entity":"invoices","action":"write"},{"entity":"address","action":"write"}'';
179+
};
180+
181+
users.users.${cfg.nbxplorer.user} = {
182+
description = "nbxplorer user";
183+
group = cfg.nbxplorer.group;
184+
extraGroups = [ "bitcoinrpc" ];
185+
home = cfg.nbxplorer.dataDir;
186+
};
187+
users.groups.${cfg.nbxplorer.group} = {};
188+
users.users.${cfg.btcpayserver.user} = {
189+
description = "btcpayserver user";
190+
group = cfg.btcpayserver.group;
191+
extraGroups = [ "nbxplorer" ]
192+
++ optional (cfg.btcpayserver.lightningBackend == "clightning") cfg.clightning.user;
193+
home = cfg.btcpayserver.dataDir;
194+
};
195+
users.groups.${cfg.btcpayserver.group} = {};
196+
197+
services.bitcoind.rpc.users.btcpayserver = {
198+
passwordHMACFromFile = true;
199+
rpcwhitelist = cfg.bitcoind.rpc.users.public.rpcwhitelist ++ [
200+
"setban"
201+
"generatetoaddress"
202+
"getpeerinfo"
203+
];
204+
};
205+
nix-bitcoin.secrets.bitcoin-rpcpassword-btcpayserver = {
206+
user = "bitcoin";
207+
group = "nbxplorer";
208+
};
209+
nix-bitcoin.secrets.bitcoin-HMAC-btcpayserver.user = "bitcoin";
210+
};
211+
}

modules/lnd.nix

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ let
77
inherit (config) nix-bitcoin-services;
88
onion-chef-service = (if cfg.announce-tor then [ "onion-chef.service" ] else []);
99
secretsDir = config.nix-bitcoin.secretsDir;
10+
mainnetDir = "${cfg.dataDir}/chain/bitcoin/mainnet";
1011
configFile = pkgs.writeText "lnd.conf" ''
1112
datadir=${cfg.dataDir}
1213
logdir=${cfg.dataDir}/logs
@@ -97,6 +98,27 @@ in {
9798
default = false;
9899
description = "Announce LND Tor Hidden Service";
99100
};
101+
macaroons = mkOption {
102+
default = {};
103+
type = with types; attrsOf (submodule {
104+
options = {
105+
user = mkOption {
106+
type = types.str;
107+
description = "User who owns the macaroon.";
108+
};
109+
permissions = mkOption {
110+
type = types.str;
111+
example = ''
112+
{"entity":"info","action":"read"},{"entity":"onchain","action":"read"}
113+
'';
114+
description = "List of granted macaroon permissions.";
115+
};
116+
};
117+
});
118+
description = ''
119+
Extra macaroon definitions.
120+
'';
121+
};
100122
extraConfig = mkOption {
101123
type = types.lines;
102124
default = "";
@@ -155,6 +177,8 @@ in {
155177
${optionalString cfg.announce-tor "echo externalip=$(cat /var/lib/onion-chef/lnd/lnd) >> '${cfg.dataDir}/lnd.conf'"}
156178
'';
157179
serviceConfig = nix-bitcoin-services.defaultHardening // {
180+
RuntimeDirectory = "lnd"; # Only used to store custom macaroons
181+
RuntimeDirectoryMode = "711";
158182
ExecStart = "${cfg.package}/bin/lnd --configfile=${cfg.dataDir}/lnd.conf";
159183
User = "lnd";
160184
Restart = "on-failure";
@@ -174,17 +198,14 @@ in {
174198
mnemonic=${secretsDir}/lnd-seed-mnemonic
175199
if [[ ! -f $mnemonic ]]; then
176200
echo Create lnd seed
177-
201+
umask u=r,go=
178202
${pkgs.curl}/bin/curl -s \
179203
--cacert ${secretsDir}/lnd-cert \
180204
-X GET https://127.0.0.1:${restPort}/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > "$mnemonic"
181205
fi
182206
chown lnd: "$mnemonic"
183-
chmod 400 "$mnemonic"
184207
''}"
185-
"${let
186-
mainnetDir = "${cfg.dataDir}/chain/bitcoin/mainnet";
187-
in nix-bitcoin-services.script ''
208+
"${nix-bitcoin-services.script ''
188209
if [[ ! -f ${mainnetDir}/wallet.db ]]; then
189210
echo Create lnd wallet
190211
@@ -214,6 +235,23 @@ in {
214235
while ! { exec 3>/dev/tcp/127.0.0.1/${toString cfg.rpcPort}; } &>/dev/null; do
215236
sleep 0.1
216237
done
238+
239+
''}"
240+
# Run fully privileged for chown
241+
"+${nix-bitcoin-services.script ''
242+
umask ug=r,o=
243+
${lib.concatMapStrings (macaroon: ''
244+
echo "Create custom macaroon ${macaroon}"
245+
macaroonPath="$RUNTIME_DIRECTORY/${macaroon}.macaroon"
246+
${pkgs.curl}/bin/curl -s \
247+
-H "Grpc-Metadata-macaroon: $(${pkgs.xxd}/bin/xxd -ps -u -c 99999 '${mainnetDir}/admin.macaroon')" \
248+
--cacert ${secretsDir}/lnd-cert \
249+
-X POST \
250+
-d '{"permissions":[${cfg.macaroons.${macaroon}.permissions}]}' \
251+
https://127.0.0.1:${restPort}/v1/macaroon |\
252+
${pkgs.jq}/bin/jq -c '.macaroon' | ${pkgs.xxd}/bin/xxd -p -r > "$macaroonPath"
253+
chown ${cfg.macaroons.${macaroon}.user}: "$macaroonPath"
254+
'') (attrNames cfg.macaroons)}
217255
''}"
218256
];
219257
} // (if cfg.enforceTor
@@ -232,6 +270,7 @@ in {
232270
lnd-wallet-password.user = "lnd";
233271
lnd-key.user = "lnd";
234272
lnd-cert.user = "lnd";
273+
lnd-cert.permissions = "0444"; # world readable
235274
};
236275
};
237276
}

modules/modules.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
./netns-isolation.nix
1919
./security.nix
2020
./backups.nix
21+
./btcpayserver.nix
2122
];
2223

2324
disabledModules = [ "services/networking/bitcoind.nix" ];

modules/netns-isolation.nix

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ in {
160160
${ipNetns} route add default via ${bridgeIp}
161161
${netnsIptables} -w -P INPUT DROP
162162
${netnsIptables} -w -A INPUT -s 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT
163+
# allow return traffic to outgoing connections initiated by the service itself
164+
${netnsIptables} -w -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
163165
'' + (optionalString (config.services.${n}.enforceTor or false)) ''
164166
${netnsIptables} -w -P OUTPUT DROP
165167
${netnsIptables} -w -A OUTPUT -d 127.0.0.1,${bridgeIp},${v.address} -j ACCEPT
@@ -230,6 +232,16 @@ in {
230232
id = 22;
231233
connections = [ "lnd" ];
232234
};
235+
nbxplorer = {
236+
id = 23;
237+
connections = [ "bitcoind" ];
238+
};
239+
btcpayserver = {
240+
id = 24;
241+
connections = [ "nbxplorer" ]
242+
++ optional (config.services.btcpayserver.lightningBackend == "lnd") "lnd";
243+
# communicates with clightning over rpc socket
244+
};
233245
};
234246

235247
services.bitcoind = {
@@ -299,6 +311,9 @@ in {
299311
};
300312

301313
services.lightning-loop.cliExec = mkCliExec "lightning-loop";
314+
315+
services.nbxplorer.bind = netns.nbxplorer.address;
316+
services.btcpayserver.bind = netns.btcpayserver.address;
302317
}
303318
]);
304319
}

0 commit comments

Comments
 (0)