Merge fort-nix/nix-bitcoin#575: Improve lndconnect, support WireGuard

cea69b73d2 nodeinfo: enable required option `nix-bitcoin.operator` (Erik Arvstedt)
27d95fda85 nodeinfo/lnd: add `onion_rest_address` (Erik Arvstedt)
54a21874ae nodeinfo/lnd: add `rest_address` (Erik Arvstedt)
a4bfefd562 add `presets/wireguard.nix` (Erik Arvstedt)
477e1709fb lndconnect: update to Zeus 0.7.1 (Erik Arvstedt)
f996ef37d9 lnd, clightning-rest: remove `lndconnectOnion`, add generic option `lndconnect` (Erik Arvstedt)
b4bc621b8c rename `lndconnect-onion.nix` -> `lndconnect.nix` (Erik Arvstedt)
907cfe4f4c docs/services: improve title, fix numbering (Erik Arvstedt)

Pull request description:

ACKs for top commit:
  jonasnick:
    ACK cea69b73d2

Tree-SHA512: 747d95b49f5c1b63dfaa2c6bc302fb102e3788c36e279cc28266ea230e8daae54973d8bdb51f2a81e7e84eb86b6b1e504fbe8af85c2318525c54d901678b3f55
This commit is contained in:
Jonas Nick 2023-03-13 12:55:40 +00:00
commit 282c45b746
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
16 changed files with 798 additions and 172 deletions

View File

@ -85,7 +85,9 @@ NixOS modules ([src](modules/modules.nix))
* [Lightning Loop](https://github.com/lightninglabs/loop) * [Lightning Loop](https://github.com/lightninglabs/loop)
* [Lightning Pool](https://github.com/lightninglabs/pool) * [Lightning Pool](https://github.com/lightninglabs/pool)
* [charge-lnd](https://github.com/accumulator/charge-lnd): policy-based channel fee manager * [charge-lnd](https://github.com/accumulator/charge-lnd): policy-based channel fee manager
* [lndconnect](https://github.com/LN-Zap/lndconnect): connect your wallet to lnd or clightning via a REST onion service * [lndconnect](https://github.com/LN-Zap/lndconnect): connect your wallet to lnd or
clightning [via WireGuard](./docs/services.md#use-zeus-mobile-lightning-wallet-via-wireguard) or
[Tor](./docs/services.md#use-zeus-mobile-lightning-wallet-via-tor)
* [Ride The Lightning](https://github.com/Ride-The-Lightning/RTL): web interface for `lnd` and `clightning` * [Ride The Lightning](https://github.com/Ride-The-Lightning/RTL): web interface for `lnd` and `clightning`
* [spark-wallet](https://github.com/shesek/spark-wallet) * [spark-wallet](https://github.com/shesek/spark-wallet)
* [electrs](https://github.com/romanz/electrs): Electrum server * [electrs](https://github.com/romanz/electrs): Electrum server

View File

@ -45,4 +45,34 @@ with lib;
nix-bitcoin.nodeinfo.enable = true; nix-bitcoin.nodeinfo.enable = true;
# test.container.enableWAN = true; # test.container.enableWAN = true;
}; };
wireguard-lndconnect-online = { config, pkgs, lib, ... }: {
imports = [
../modules/presets/wireguard.nix
scenarios.regtestBase
];
# 51820 (default wg port) + 1
networking.wireguard.interfaces.wg-nb.listenPort = 51821;
test.container.enableWAN = true;
# test.container.exposeLocalhost = true;
services.clightning.extraConfig = "disable-dns";
services.lnd = {
enable = true;
lndconnect = {
enable = true;
onion = true;
};
};
services.clightning-rest = {
enable = true;
lndconnect = {
enable = true;
onion = true;
};
};
nix-bitcoin.nodeinfo.enable = true;
};
} }

View File

@ -0,0 +1,64 @@
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Test Tor and WireGuard connections on a mobile device
# 1. Run container
run-tests.sh -s wireguard-lndconnect-online container
# 2. Test connecting via Tor
# Print QR codes for lnd, clightning-rest connections via Tor
c lndconnect
c lndconnect-clightning
# Add these to Zeus >= 0.7.1.
# To explicitly check if the connection is successful, press the node logo in the top
# left corner, and then "Node Info".
# Debug
c lndconnect --url
c lndconnect-clightning --url
# 3. Test connecting via WireGuard
# 3.1 Forward WireGuard port from the container host to the container
iptables -t nat -A PREROUTING -p udp --dport 51821 -j DNAT --to-destination 10.225.255.2
# 3.2. Optional: When your container host has an external firewall,
# forward the WireGuard port to the container host:
# - Port: 51821
# - Protocol: UDP
# - Destination: IPv4 of the container host
# 3.2 Print QR code and setup wireguard on the mobile device
c nix-bitcoin-wg-connect
c nix-bitcoin-wg-connect --text
# Print QR codes for lnd, clightning-rest connections via WireGuard
c lndconnect-wg
c lndconnect-clightning-wg
# Add these to Zeus >= 0.7.1.
# To explicitly check if the connection is successful, press the node logo in the top
# left corner, and then "Node Info".
# Debug
c lndconnect-wg --url
c lndconnect-clightning-wg --url
# 3.3.remove external firewall port forward, remove local port forward:
iptables -t nat -D PREROUTING -p udp --dport 51821 -j DNAT --to-destination 10.225.255.2
# Now exit the container shell
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Debug lndconnect
run-tests.sh -s wireguard-lndconnect-online container
c nodeinfo
c lndconnect --url
c lndconnect-wg --url
c lndconnect-clightning --url
c lndconnect-clightning-wg --url
c lndconnect
c lndconnect-wg
c lndconnect-clightning
c lndconnect-clightning-wg

View File

@ -142,60 +142,154 @@ You can find the `<onion-address>` with command `nodeinfo`.
The default password location is `$secretsDir/rtl-password`. The default password location is `$secretsDir/rtl-password`.
See: [Secrets dir](./configuration.md#secrets-dir) See: [Secrets dir](./configuration.md#secrets-dir)
# Use LND or clightning with Zeus (mobile wallet) via Tor # Use Zeus (mobile lightning wallet) via Tor
1. Install [Zeus](https://zeusln.app) 1. Install [Zeus](https://zeusln.app) (version ≥ 0.7.1)
2. Edit your `configuration.nix` 2. Edit your `configuration.nix`
##### For lnd ##### For lnd
Add the following config: Add the following config:
``` ```nix
services.lnd.lndconnectOnion.enable = true; services.lnd.lndconnect = {
enable = true;
onion = true;
};
``` ```
##### For clightning ##### For clightning
Add the following config: Add the following config:
``` ```nix
services.clightning-rest = { services.clightning-rest = {
enable = true; enable = true;
lndconnectOnion.enable = true; lndconnect = {
enable = true;
onion = true;
};
}; };
``` ```
3. Deploy your configuration 3. Deploy your configuration
3. Run the following command on your node (as user `operator`) to create a QR code 4. Run the following command on your node (as user `operator`) to create a QR code
with address and authentication information: with address and authentication information:
##### For lnd ##### For lnd
``` ```
lndconnect-onion lndconnect
``` ```
##### For clightning ##### For clightning
``` ```
lndconnect-onion-clightning lndconnect-clightning
``` ```
4. Configure Zeus 5. Configure Zeus
- Add a new node - Add a new node and scan the QR code
- Select `Scan lndconnect config` (at the bottom) and scan the QR code
- For clightning: Set `Node interface` to `c-lightning-REST`
- Click `Save node config` - Click `Save node config`
- Start sending and stacking sats privately - Start sending and stacking sats privately
### Additional lndconnect features ### Additional lndconnect features
Create plain text URLs or QR code images: - Create a plain text URL:
``` ```bash
lndconnect-onion --url lndconnect --url
lndconnect-onion --image ```
- Set a custom host. By default, `lndconnect` detects the system's external IP and uses it as the host.
```bash
lndconnect --host myhost
```
# Use Zeus (mobile lightning wallet) via WireGuard
Connecting Zeus directly to your node is much faster than using Tor, but a bit more complex to setup.
There are two ways to establish a secure, direct connection:
- Connecting via TLS. This requires installing your lightning app's
TLS Certificate on your mobile device.
- Connecting via WireGuard. This approach is simpler and more versatile, and is
described in this guide.
1. Install [Zeus](https://zeusln.app) (version ≥ 0.7.1) and
[WireGuard](https://www.wireguard.com/install/) on your mobile device.
2. Add the following to your `configuration.nix`:
```nix
imports = [
# Use this line when using the default deployment method
<nix-bitcoin/modules/presets/wireguard.nix>
# Use this line when using Flakes
(nix-bitcoin + /modules/presets/wireguard.nix)
]
# For lnd
services.lnd.lndconnect.enable = true;
# For clightning
services.clightning-rest = {
enable = true;
lndconnect.enable = true;
};
```
3. Deploy your configuration.
4. If your node is behind an external firewall or NAT, add the following port forwarding
rule to the external device:
- Port: 51820 (the default value of option `networking.wireguard.interfaces.wg-nb.listenPort`)
- Protocol: UDP
- Destination: IP of your node
5. Setup WireGuard on your mobile device.
Run the following command on your node (as user `operator`) to create a QR code
for WireGuard:
```bash
nix-bitcoin-wg-connect
# For debugging: Show the WireGuard config as text
nix-bitcoin-wg-connect --text
```
The above commands automatically detect your node's external IP.\
To set a custom IP or hostname, run the following:
```
nix-bitcoin-wg-connect 93.184.216.34
nix-bitcoin-wg-connect mynode.org
```
Configure WireGuard:
- Press the `+` button in the bottom right corner
- Scan the QR code
- Add the tunnel
6. Setup Zeus
Run the following command on your node (as user `operator`) to create a QR code for Zeus:
##### For lnd
```
lndconnect-wg
```
##### For clightning
```
lndconnect-clightning-wg
```
Configure Zeus:
- Add a new node and scan the QR code
- Click `Save node config`
- On the certificate warning screen, click `I understand, save node config`.\
Certificates are not needed when connecting via WireGuard.
- Start sending and stacking sats privately
### Additional lndconnect features
Create a plain text URL:
```bash
lndconnect-wg --url
`````` ``````
Create a QR code for a custom hostname:
```
lndconnect-onion --host=mynode.org
```
# Connect to spark-wallet # Connect to spark-wallet
### Requirements ### Requirements

View File

@ -56,13 +56,18 @@
# #
# == REST server # == REST server
# Set this to create a clightning REST onion service. # Set this to create a clightning REST onion service.
# This also adds binary `lndconnect-onion-clightning` to the system environment. # This also adds binary `lndconnect-clightning` to the system environment.
# This binary creates QR codes or URLs for connecting applications to clightning # This binary creates QR codes or URLs for connecting applications to clightning
# via the REST onion service (see ../docs/services.md). # via the REST onion service.
# You can also connect via WireGuard instead of Tor.
# See ../docs/services.md for details.
# #
# services.clightning-rest = { # services.clightning-rest = {
# enable = true; # enable = true;
# lndconnectOnion.enable = true; # lndconnect = {
# enable = true;
# onion = true;
# };
# }; # };
### LND ### LND
@ -78,11 +83,17 @@
# The onion service is automatically announced to peers. # The onion service is automatically announced to peers.
# nix-bitcoin.onionServices.lnd.public = true; # nix-bitcoin.onionServices.lnd.public = true;
# #
# Set this to create an lnd REST onion service. # Set this to create a lnd REST onion service.
# This also adds binary `lndconnect-onion` to the system environment. # This also adds binary `lndconnect` to the system environment.
# This binary generates QR codes or URLs for connecting applications to lnd via the # This binary generates QR codes or URLs for connecting applications to lnd via the
# REST onion service (see ../docs/services.md). # REST onion service.
# services.lnd.lndconnectOnion.enable = true; # You can also connect via WireGuard instead of Tor.
# See ../docs/services.md for details.
#
# services.lnd.lndconnect = {
# enable = true;
# onion = true;
# };
# #
## WARNING ## WARNING
# If you use lnd, you should manually backup your wallet mnemonic # If you use lnd, you should manually backup your wallet mnemonic

View File

@ -1,126 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
options = {
services.lnd.lndconnectOnion.enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Create an onion service for the lnd REST server.
Add a `lndconnect-onion` binary to the system environment.
See: https://github.com/LN-Zap/lndconnect
Usage:
```bash
# Print QR code
lndconnect-onion
# Print URL
lndconnect-onion --url
```
'';
};
services.clightning-rest.lndconnectOnion.enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Create an onion service for clightning-rest.
Add a `lndconnect-onion-clightning` binary to the system environment.
See: https://github.com/LN-Zap/lndconnect
Usage:
```bash
# Print QR code
lndconnect-onion-clightning
# Print URL
lndconnect-onion-clightning --url
```
'';
};
};
nbLib = config.nix-bitcoin.lib;
runAsUser = config.nix-bitcoin.runAsUserCmd;
inherit (config.services)
lnd
clightning
clightning-rest;
mkLndconnect = {
name,
shebang ? "#!${pkgs.stdenv.shell} -e",
onionService,
port,
certPath,
macaroonPath
}:
# TODO-EXTERNAL:
# lndconnect requires a --configfile argument, although it's unused
# https://github.com/LN-Zap/lndconnect/issues/25
pkgs.writeScriptBin name ''
${shebang}
exec ${config.nix-bitcoin.pkgs.lndconnect}/bin/lndconnect \
--host=$(cat ${config.nix-bitcoin.onionAddresses.dataDir}/${onionService}) \
--port=${toString port} \
--tlscertpath='${certPath}' \
--adminmacaroonpath='${macaroonPath}' \
--configfile=/dev/null "$@"
'';
operatorName = config.nix-bitcoin.operator.name;
in {
inherit options;
config = mkMerge [
(mkIf (lnd.enable && lnd.lndconnectOnion.enable) {
services.tor = {
enable = true;
relay.onionServices.lnd-rest = nbLib.mkOnionService {
target.addr = nbLib.address lnd.restAddress;
target.port = lnd.restPort;
port = lnd.restPort;
};
};
nix-bitcoin.onionAddresses.access.${lnd.user} = [ "lnd-rest" ];
environment.systemPackages = [(
mkLndconnect {
name = "lndconnect-onion";
# Run as lnd user because the macaroon and cert are not group-readable
shebang = "#!/usr/bin/env -S ${runAsUser} ${lnd.user} ${pkgs.bash}/bin/bash";
onionService = "${lnd.user}/lnd-rest";
port = lnd.restPort;
certPath = lnd.certPath;
macaroonPath = "${lnd.networkDir}/admin.macaroon";
}
)];
})
(mkIf (clightning-rest.enable && clightning-rest.lndconnectOnion.enable) {
services.tor = {
enable = true;
relay.onionServices.clightning-rest = nbLib.mkOnionService {
target.addr = nbLib.address clightning-rest.address;
target.port = clightning-rest.port;
port = clightning-rest.port;
};
};
# This also allows nodeinfo to show the clightning-rest onion address
nix-bitcoin.onionAddresses.access.${operatorName} = [ "clightning-rest" ];
environment.systemPackages = [(
mkLndconnect {
name = "lndconnect-onion-clightning";
onionService = "${operatorName}/clightning-rest";
port = clightning-rest.port;
certPath = "${clightning-rest.dataDir}/certs/certificate.pem";
macaroonPath = "${clightning-rest.dataDir}/certs/access.macaroon";
}
)];
})
];
}

205
modules/lndconnect.nix Normal file
View File

@ -0,0 +1,205 @@
{ config, lib, pkgs, ... }:
with lib;
let
options = {
services.lnd.lndconnect = {
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Add a `lndconnect` binary to the system environment which prints
connection info for lnd clients.
See: https://github.com/LN-Zap/lndconnect
Usage:
```bash
# Print QR code
lndconnect
# Print URL
lndconnect --url
```
'';
};
onion = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Create an onion service for the lnd REST server,
which is used by lndconnect.
'';
};
};
services.clightning-rest.lndconnect = {
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Add a `lndconnect-clightning` binary to the system environment which prints
connection info for clightning clients.
See: https://github.com/LN-Zap/lndconnect
Usage:
```bash
# Print QR code
lndconnect-clightning
# Print URL
lndconnect-clightning --url
```
'';
};
onion = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Create an onion service for the clightning REST server,
which is used by lndconnect.
'';
};
};
nix-bitcoin.mkLndconnect = mkOption {
readOnly = true;
default = mkLndconnect;
description = mdDoc ''
A function to create a lndconnect binary.
See the source for further details.
'';
};
};
nbLib = config.nix-bitcoin.lib;
runAsUser = config.nix-bitcoin.runAsUserCmd;
inherit (config.services)
lnd
clightning-rest;
mkLndconnect = {
name,
shebang ? "#!${pkgs.stdenv.shell} -e",
isClightning ? false,
port,
macaroonPath,
enableOnion,
onionService ? null,
certPath ? null
}:
# TODO-EXTERNAL:
# lndconnect requires a --configfile argument, although it's unused
# https://github.com/LN-Zap/lndconnect/issues/25
pkgs.hiPrio (pkgs.writeScriptBin name ''
${shebang}
url=$(
${getExe config.nix-bitcoin.pkgs.lndconnect} --url \
${optionalString enableOnion "--host=$(cat ${config.nix-bitcoin.onionAddresses.dataDir}/${onionService})"} \
--port=${toString port} \
${if enableOnion || certPath == null then "--nocert" else "--tlscertpath='${certPath}'"} \
--adminmacaroonpath='${macaroonPath}' \
--configfile=/dev/null "$@"
)
${optionalString isClightning
# - Change URL procotcol to c-lightning-rest
# - Encode macaroon as hex (in uppercase) instead of base 64.
# Because `macaroon` is always the last URL fragment, the
# sed replacement below works correctly.
''
macaroonHex=$(${getExe pkgs.xxd} -p -u -c 99999 '${macaroonPath}')
url=$(
echo "$url" | ${getExe pkgs.gnused} "
s|^lndconnect|c-lightning-rest|
s|macaroon=.*|macaroon=$macaroonHex|
";
)
''
}
# If --url is in args
if [[ " $* " =~ " --url " ]]; then
echo "$url"
else
# This UTF-8 encoding yields a smaller, more convenient output format
# compared to the native lndconnect output
echo -n "$url" | ${getExe pkgs.qrencode} -t UTF8 -o -
fi
'');
operatorName = config.nix-bitcoin.operator.name;
in {
inherit options;
config = mkMerge [
(mkIf (lnd.enable && lnd.lndconnect.enable)
(mkMerge [
{
environment.systemPackages = [(
mkLndconnect {
name = "lndconnect";
# Run as lnd user because the macaroon and cert are not group-readable
shebang = "#!/usr/bin/env -S ${runAsUser} ${lnd.user} ${pkgs.bash}/bin/bash";
enableOnion = lnd.lndconnect.onion;
onionService = "${lnd.user}/lnd-rest";
port = lnd.restPort;
certPath = lnd.certPath;
macaroonPath = "${lnd.networkDir}/admin.macaroon";
}
)];
services.lnd.restAddress = mkIf (!lnd.lndconnect.onion) "0.0.0.0";
}
(mkIf lnd.lndconnect.onion {
services.tor = {
enable = true;
relay.onionServices.lnd-rest = nbLib.mkOnionService {
target.addr = nbLib.address lnd.restAddress;
target.port = lnd.restPort;
port = lnd.restPort;
};
};
nix-bitcoin.onionAddresses.access = {
${lnd.user} = [ "lnd-rest" ];
${operatorName} = [ "lnd-rest" ];
};
})
]))
(mkIf (clightning-rest.enable && clightning-rest.lndconnect.enable)
(mkMerge [
{
environment.systemPackages = [(
mkLndconnect {
name = "lndconnect-clightning";
isClightning = true;
enableOnion = clightning-rest.lndconnect.onion;
onionService = "${operatorName}/clightning-rest";
port = clightning-rest.port;
certPath = "${clightning-rest.dataDir}/certs/certificate.pem";
macaroonPath = "${clightning-rest.dataDir}/certs/access.macaroon";
}
)];
# clightning-rest always binds to all interfaces
}
(mkIf clightning-rest.lndconnect.onion {
services.tor = {
enable = true;
relay.onionServices.clightning-rest = nbLib.mkOnionService {
target.addr = nbLib.address clightning-rest.address;
target.port = clightning-rest.port;
port = clightning-rest.port;
};
};
# This also allows nodeinfo to show the clightning-rest onion address
nix-bitcoin.onionAddresses.access.${operatorName} = [ "clightning-rest" ];
})
])
)
];
}

View File

@ -19,7 +19,7 @@
./lightning-loop.nix ./lightning-loop.nix
./lightning-pool.nix ./lightning-pool.nix
./charge-lnd.nix ./charge-lnd.nix
./lndconnect-onion.nix # Requires onion-addresses.nix ./lndconnect.nix # Requires onion-addresses.nix
./rtl.nix ./rtl.nix
./electrs.nix ./electrs.nix
./fulcrum.nix ./fulcrum.nix

View File

@ -63,7 +63,7 @@ let
infos = OrderedDict() infos = OrderedDict()
operator = "${config.nix-bitcoin.operator.name}" operator = "${config.nix-bitcoin.operator.name}"
def set_onion_address(info, name, port): def get_onion_address(name, port):
path = f"/var/lib/onion-addresses/{operator}/{name}" path = f"/var/lib/onion-addresses/{operator}/{name}"
try: try:
with open(path, "r") as f: with open(path, "r") as f:
@ -71,7 +71,7 @@ let
except OSError: except OSError:
print(f"error reading file {path}", file=sys.stderr) print(f"error reading file {path}", file=sys.stderr)
return return
info["onion_address"] = f"{onion_address}:{port}" return f"{onion_address}:{port}"
def add_service(service, make_info, systemd_service = None): def add_service(service, make_info, systemd_service = None):
systemd_service = systemd_service or service systemd_service = systemd_service or service
@ -106,7 +106,7 @@ let
add_service("${name}", """ add_service("${name}", """
info["local_address"] = "${nbLib.addressWithPort cfg.address cfg.port}" info["local_address"] = "${nbLib.addressWithPort cfg.address cfg.port}"
'' + mkIfOnionPort name (onionPort: '' '' + mkIfOnionPort name (onionPort: ''
set_onion_address(info, "${name}", ${onionPort}) info["onion_address"] = get_onion_address("${name}", ${onionPort})
'') + extraCode + '' '') + extraCode + ''
""", "${systemdServiceName}") """, "${systemdServiceName}")
@ -123,8 +123,10 @@ let
in { in {
inherit options; inherit options;
config = { config = mkIf cfg.enable {
environment.systemPackages = optional cfg.enable script; environment.systemPackages = [ script ];
nix-bitcoin.operator.enable = true;
nix-bitcoin.nodeinfo.services = with nodeinfoLib; { nix-bitcoin.nodeinfo.services = with nodeinfoLib; {
bitcoind = mkInfo ""; bitcoind = mkInfo "";
@ -133,9 +135,13 @@ in {
if 'onion_address' in info: if 'onion_address' in info:
info["id"] = f"{info['nodeid']}@{info['onion_address']}" info["id"] = f"{info['nodeid']}@{info['onion_address']}"
''; '';
lnd = mkInfo '' lnd = name: cfg: mkInfo (''
info["rest_address"] = "${nbLib.addressWithPort cfg.restAddress cfg.restPort}"
'' + mkIfOnionPort "lnd-rest" (onionPort: ''
info["onion_rest_address"] = get_onion_address("lnd-rest", ${onionPort})
'') + ''
info["nodeid"] = shell("lncli getinfo | jq -r '.identity_pubkey'") info["nodeid"] = shell("lncli getinfo | jq -r '.identity_pubkey'")
''; '') name cfg;
clightning-rest = mkInfo ""; clightning-rest = mkInfo "";
electrs = mkInfo ""; electrs = mkInfo "";
fulcrum = mkInfo ""; fulcrum = mkInfo "";
@ -146,7 +152,7 @@ in {
rtl = mkInfo ""; rtl = mkInfo "";
# Only add sshd when it has an onion service # Only add sshd when it has an onion service
sshd = name: cfg: mkIfOnionPort "sshd" (onionPort: '' sshd = name: cfg: mkIfOnionPort "sshd" (onionPort: ''
add_service("sshd", """set_onion_address(info, "sshd", ${onionPort})""") add_service("sshd", """info["onion_address"] = get_onion_address("sshd", ${onionPort})""")
''); '');
}; };
}; };

View File

@ -33,7 +33,6 @@ in {
(mkRenamedOptionModule [ "services" "liquidd" "rpcbind" ] [ "services" "liquidd" "rpc" "address" ]) (mkRenamedOptionModule [ "services" "liquidd" "rpcbind" ] [ "services" "liquidd" "rpc" "address" ])
# 0.0.70 # 0.0.70
(mkRenamedOptionModule [ "services" "rtl" "cl-rest" ] [ "services" "clightning-rest" ]) (mkRenamedOptionModule [ "services" "rtl" "cl-rest" ] [ "services" "clightning-rest" ])
(mkRenamedOptionModule [ "services" "lnd" "restOnionService" "enable" ] [ "services" "lnd" "lndconnectOnion" "enable" ])
(mkRenamedOptionModule [ "nix-bitcoin" "setup-secrets" ] [ "nix-bitcoin" "setupSecrets" ]) (mkRenamedOptionModule [ "nix-bitcoin" "setup-secrets" ] [ "nix-bitcoin" "setupSecrets" ])
@ -46,6 +45,28 @@ in {
bitcoin peer connections for syncing blocks. This performs well on low and high bitcoin peer connections for syncing blocks. This performs well on low and high
memory systems. memory systems.
'') '')
# 0.0.86
(mkRemovedOptionModule [ "services" "lnd" "restOnionService" "enable" ] ''
Set the following options instead:
services.lnd.lndconnect = {
enable = true;
onion = true;
}
'')
(mkRemovedOptionModule [ "services" "lnd" "lndconnect-onion" ] ''
Set the following options instead:
services.lnd.lndconnect = {
enable = true;
onion = true;
}
'')
(mkRemovedOptionModule [ "services" "clightning-rest" "lndconnect-onion" ] ''
Set the following options instead:
services.clightning-rest.lndconnect = {
enable = true;
onion = true;
}
'')
] ++ ] ++
# 0.0.59 # 0.0.59
(map mkSplitEnforceTorOption [ (map mkSplitEnforceTorOption [

View File

@ -0,0 +1,214 @@
{ config, pkgs, lib, ... }:
# Create a WireGuard server with a single peer.
# Private/public keys are created via the secrets system.
# Add helper binaries `nix-bitcoin-wg-connect` and optionally `lndconnect-wg`, `lndconnect-clightning-wg`.
# See ../../docs/services.md ("Use Zeus (mobile lightning wallet) via WireGuard")
# for usage instructions.
# This is a rather opinionated implementation that lacks the flexibility offered by
# other nix-bitcoin modules, so ship this as a `preset`.
# Some users will prefer to use `lndconnect` with their existing WireGuard or Tailscale setup.
with lib;
let
options.nix-bitcoin.wireguard = {
subnet = mkOption {
type = types.str;
default = "10.10.0";
description = mdDoc "The /24 subnet of the wireguard network.";
};
restrictPeer = mkOption {
type = types.bool;
default = true;
description = mdDoc ''
Prevent the peer from connecting to any addresses except for the WireGuard server address.
'';
};
};
cfg = config.nix-bitcoin.wireguard;
wgSubnet = cfg.subnet;
inherit (config.networking.wireguard.interfaces) wg-nb;
inherit (config.services)
lnd
clightning-rest;
lndconnect = lnd.enable && lnd.lndconnect.enable;
lndconnect-clightning = clightning-rest.enable && clightning-rest.lndconnect.enable;
serverAddress = "${wgSubnet}.1";
peerAddress = "${wgSubnet}.2";
secretsDir = config.nix-bitcoin.secretsDir;
wgConnectUser = if config.nix-bitcoin.operator.enable
then config.nix-bitcoin.operator.name
else "root";
# A script that prints a QR code to connect a peer to the server.
# The QR code encodes a wg-quick config that can be imported by the wireguard
# mobile app.
wgConnect = pkgs.writers.writeBashBin "nix-bitcoin-wg-connect" ''
set -euo pipefail
text=
host=
for arg in "$@"; do
case $arg in
--text)
text=1
;;
*)
host=$arg
;;
esac
done
if [[ ! $host ]]; then
# Use lndconnect to fetch the external ip.
# This internally uses https://github.com/GlenDC/go-external-ip, which
# queries a set of external ip providers.
host=$(
${getExe config.nix-bitcoin.pkgs.lndconnect} --url --nocert \
--configfile=/dev/null --adminmacaroonpath=/dev/null \
| sed -nE 's|.*?/(.*?):.*|\1|p'
)
fi
config="[Interface]
PrivateKey = $(cat ${secretsDir}/wg-peer-private-key)
Address = ${peerAddress}/24
[Peer]
PublicKey = $(cat ${secretsDir}/wg-server-public-key)
AllowedIPs = ${wgSubnet}.0/24
Endpoint = $host:${toString wg-nb.listenPort}
PersistentKeepalive = 25
"
if [[ $text ]]; then
echo "$config"
else
echo "$config" | ${getExe pkgs.qrencode} -t UTF8 -o -
fi
'';
in {
inherit options;
config = {
assertions = [
{
# Don't support `netns-isolation` for now to keep things simple
assertion = !(config.nix-bitcoin.netns-isolation.enable or false);
message = "`nix-bitcoin.wireguard` is not compatible with `netns-isolation`.";
}
];
networking.wireguard.interfaces.wg-nb = {
ips = [ "${serverAddress}/24" ];
listenPort = mkDefault 51820;
privateKeyFile = "${secretsDir}/wg-server-private-key";
allowedIPsAsRoutes = false;
peers = [
{
# To use the actual public key from the secrets file, use dummy pubkey
# `peer0` and replace it via `getPubkeyFromFile` (see further below)
# at peer service runtime.
publicKey = "peer0";
allowedIPs = [ "${peerAddress}/32" ];
}
];
};
systemd.services = {
wireguard-wg-nb = rec {
wants = [ "nix-bitcoin-secrets.target" ];
after = wants;
};
# HACK: Modify start/stop scripts of the peer setup service to read
# the pubkey from a secrets file.
wireguard-wg-nb-peer-peer0 = let
getPubkeyFromFile = mkBefore ''
if [[ ! -v inPatchedSrc ]]; then
export inPatchedSrc=1
publicKey=$(cat "${secretsDir}/wg-peer-public-key")
<"''${BASH_SOURCE[0]}" sed "s|\bpeer0\b|$publicKey|g" | ${pkgs.bash}/bin/bash -s
exit
fi
'';
in {
script = getPubkeyFromFile;
postStop = getPubkeyFromFile;
};
};
environment.systemPackages = [
wgConnect
] ++ (optional lndconnect
(pkgs.writers.writeBashBin "lndconnect-wg" ''
exec lndconnect --host "${serverAddress}" --nocert "$@"
'')
) ++ (optional lndconnect-clightning
(pkgs.writers.writeBashBin "lndconnect-clightning-wg" ''
exec lndconnect-clightning --host "${serverAddress}" --nocert "$@"
'')
);
networking.firewall = let
restrictPeerRule = "-s ${peerAddress} ! -d ${serverAddress} -j REJECT";
in {
allowedUDPPorts = [ wg-nb.listenPort ];
extraCommands =
optionalString lndconnect ''
iptables -w -A nixos-fw -p tcp -s ${wgSubnet}.0/24 --dport ${toString lnd.restPort} -j nixos-fw-accept
''
+ optionalString lndconnect-clightning ''
iptables -w -A nixos-fw -p tcp -s ${wgSubnet}.0/24 --dport ${toString clightning-rest.port} -j nixos-fw-accept
''
+ optionalString cfg.restrictPeer ''
iptables -w -A nixos-fw ${restrictPeerRule}
iptables -w -A FORWARD ${restrictPeerRule}
'';
extraStopCommands =
# Rules added to chain `nixos-fw` are automatically removed when restarting
# the NixOS firewall service.
mkIf cfg.restrictPeer ''
iptables -w -D FORWARD ${restrictPeerRule} || :
'';
};
# Listen on all addresses, including `serverAddress`.
# This is safe because the listen ports are secured by the firewall.
services.lnd.restAddress = mkIf lndconnect "0.0.0.0";
# clightning-rest always listens on "0.0.0.0"
nix-bitcoin.secrets = {
wg-server-private-key = {};
wg-server-public-key = { user = wgConnectUser; group = "root"; };
wg-peer-private-key = { user = wgConnectUser; group = "root"; };
wg-peer-public-key = {};
};
nix-bitcoin.generateSecretsCmds.wireguard = let
wg = "${pkgs.wireguard-tools}/bin/wg";
in ''
makeWireguardKey() {
local name=$1
local priv=wg-$name-private-key
local pub=wg-$name-public-key
if [[ ! -e $priv ]]; then
${wg} genkey > $priv
fi
if [[ $priv -nt $pub ]]; then
${wg} pubkey < $priv > $pub
fi
}
makeWireguardKey server
makeWireguardKey peer
'';
};
}

View File

@ -228,7 +228,7 @@ let
version = "0.0.70"; version = "0.0.70";
condition = config.services.lnd.lndconnectOnion.enable; condition = config.services.lnd.lndconnectOnion.enable;
message = '' message = ''
The `lndconnect-rest-onion` binary has been renamed to `lndconnect-onion`. The `lndconnect-rest-onion` binary has been renamed to `lndconnect`.
''; '';
} }
{ {

View File

@ -274,6 +274,7 @@ buildable=(
hardened hardened
clightning-replication clightning-replication
lndPruned lndPruned
wireguard-lndconnect
) )
buildable() { buildTests buildable "$@"; } buildable() { buildTests buildable "$@"; }

View File

@ -86,8 +86,8 @@ let
nix-bitcoin.onionServices.lnd.public = true; nix-bitcoin.onionServices.lnd.public = true;
tests.lndconnect-onion-lnd = cfg.lnd.lndconnectOnion.enable; tests.lndconnect-onion-lnd = with cfg.lnd.lndconnect; enable && onion;
tests.lndconnect-onion-clightning = cfg.clightning-rest.lndconnectOnion.enable; tests.lndconnect-onion-clightning = with cfg.clightning-rest.lndconnect; enable && onion;
tests.lightning-loop = cfg.lightning-loop.enable; tests.lightning-loop = cfg.lightning-loop.enable;
services.lightning-loop.certificate.extraIPs = [ "20.0.0.1" ]; services.lightning-loop.certificate.extraIPs = [ "20.0.0.1" ];
@ -187,9 +187,9 @@ let
services.rtl.enable = true; services.rtl.enable = true;
services.spark-wallet.enable = true; services.spark-wallet.enable = true;
services.clightning-rest.enable = true; services.clightning-rest.enable = true;
services.clightning-rest.lndconnectOnion.enable = true; services.clightning-rest.lndconnect = { enable = true; onion = true; };
services.lnd.enable = true; services.lnd.enable = true;
services.lnd.lndconnectOnion.enable = true; services.lnd.lndconnect = { enable = true; onion = true; };
services.lightning-loop.enable = true; services.lightning-loop.enable = true;
services.lightning-pool.enable = true; services.lightning-pool.enable = true;
services.charge-lnd.enable = true; services.charge-lnd.enable = true;
@ -405,6 +405,7 @@ in {
in in
{ {
clightning-replication = import ./clightning-replication.nix makeTestVM pkgs; clightning-replication = import ./clightning-replication.nix makeTestVM pkgs;
wireguard-lndconnect = import ./wireguard-lndconnect.nix makeTestVM pkgs;
} // mainTests; } // mainTests;
tests = makeTests scenarios; tests = makeTests scenarios;

View File

@ -177,12 +177,12 @@ def _():
@test("lndconnect-onion-lnd") @test("lndconnect-onion-lnd")
def _(): def _():
assert_running("lnd") assert_running("lnd")
assert_matches("runuser -u operator -- lndconnect-onion --url", ".onion") assert_matches("runuser -u operator -- lndconnect --url", ".onion")
@test("lndconnect-onion-clightning") @test("lndconnect-onion-clightning")
def _(): def _():
assert_running("clightning-rest") assert_running("clightning-rest")
assert_matches("runuser -u operator -- lndconnect-onion-clightning --url", ".onion") assert_matches("runuser -u operator -- lndconnect-clightning --url", ".onion")
@test("lightning-loop") @test("lightning-loop")
def _(): def _():

View File

@ -0,0 +1,103 @@
# You can run this test via `run-tests.sh -s wireguard-lndconnect`
makeTestVM: pkgs:
with pkgs.lib;
makeTestVM {
name = "wireguard-lndconnect";
nodes = {
server = {
imports = [
../modules/modules.nix
../modules/presets/wireguard.nix
];
nixpkgs.pkgs = pkgs;
nix-bitcoin.generateSecrets = true;
nix-bitcoin.operator.enable = true;
services.clightning-rest = {
enable = true;
lndconnect.enable = true;
};
# TODO-EXTERNAL:
# When WAN is disabled, DNS bootstrapping slows down service startup by ~15 s.
services.clightning.extraConfig = "disable-dns";
services.lnd = {
enable = true;
lndconnect.enable = true;
port = 9736;
};
};
client = {
nixpkgs.pkgs = pkgs;
environment.systemPackages = with pkgs; [
wireguard-tools
];
};
};
testScript = ''
import base64
import urllib.parse as Url
from types import SimpleNamespace
def parse_lndconnect_url(url):
u = Url.urlparse(url)
queries = Url.parse_qs(u.query)
macaroon = queries['macaroon'][0]
is_clightning = url.startswith("c-lightning-rest")
return SimpleNamespace(
host = u.hostname,
port = u.port,
macaroon_hex =
macaroon if is_clightning else base64.urlsafe_b64decode(macaroon + '===').hex().upper()
)
client.start()
server.connect()
if not "is_interactive" in vars():
with subtest("connect client to server via WireGuard"):
server.wait_for_unit("wireguard-wg-nb-peer-peer0.service")
# Get WireGuard config from server and save it to `/tmp/wireguard.conf` on the client
wg_config = server.succeed("runuser -u operator -- nix-bitcoin-wg-connect server --text")
# Encode to base64
b64 = base64.b64encode(wg_config.encode('utf-8')).decode()
client.succeed(f"install -m 400 <(echo -n {b64} | base64 -d) /tmp/wireguard.conf")
# Connect to server via WireGuard
client.succeed("wg-quick up /tmp/wireguard.conf")
# Ping server from client
print(client.succeed("ping -c 1 -W 0.5 10.10.0.1"))
with subtest("lndconnect-wg"):
server.wait_for_unit("lnd.service")
lndconnect_url = server.succeed("runuser -u operator -- lndconnect-wg --url")
api = parse_lndconnect_url(lndconnect_url)
# Make lnd REST API call
client.succeed(
f"curl -fsS --max-time 3 --insecure --header 'Grpc-Metadata-macaroon: {api.macaroon_hex}' "
f"-X GET https://{api.host}:{api.port}/v1/getinfo"
)
with subtest("lndconnect-clightning-wg"):
server.wait_for_unit("clightning-rest.service")
lndconnect_url = server.succeed("runuser -u operator -- lndconnect-clightning-wg --url")
api = parse_lndconnect_url(lndconnect_url)
# Make clightning-rest API call
client.succeed(
f"curl -fsS --max-time 3 --insecure --header 'macaroon: {api.macaroon_hex}' "
f"--header 'encodingtype: hex' -X GET https://{api.host}:{api.port}/v1/getinfo"
)
'';
}