improve nodeinfo

- enable usage outside of secure-node.nix
- use json as the output format
- show ports
- also show local addresses, which is particularly useful when
  netns-isolation is enabled
- only show enabled services
This commit is contained in:
Erik Arvstedt 2021-01-14 13:24:26 +01:00
parent f6b883a9ac
commit 323a431aba
No known key found for this signature in database
GPG Key ID: 33312B944DD97846
7 changed files with 126 additions and 67 deletions

View File

@ -49,7 +49,6 @@ Features
--- ---
A [configuration preset](modules/presets/secure-node.nix) for setting up a secure node A [configuration preset](modules/presets/secure-node.nix) for setting up a secure node
* All applications use Tor for outbound connections and support accepting inbound connections via onion services. * All applications use Tor for outbound connections and support accepting inbound connections via onion services.
* Includes a [nodeinfo](modules/nodeinfo.nix) script which prints basic info about the node.
NixOS modules NixOS modules
* Application services * Application services
@ -74,6 +73,7 @@ NixOS modules
* [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI) * [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI)
* Helper * Helper
* [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces * [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces
* [nodeinfo](modules/nodeinfo.nix): script which prints info about the node's services
* [backups](modules/backups.nix): daily duplicity backups of all your node's important files * [backups](modules/backups.nix): daily duplicity backups of all your node's important files
* [operator](modules/operator.nix): adds non-root user `operator` who has access to client tools (e.g. `bitcoin-cli`, `lightning-cli`) * [operator](modules/operator.nix): adds non-root user `operator` who has access to client tools (e.g. `bitcoin-cli`, `lightning-cli`)

View File

@ -8,7 +8,7 @@ fetch-release > nix-bitcoin-release.nix
Nodeinfo Nodeinfo
--- ---
Run `nodeinfo` to see the onion addresses for enabled services. Run `nodeinfo` to see onion addresses and local addresses for enabled services.
Connect to spark-wallet Connect to spark-wallet
--- ---
@ -86,10 +86,10 @@ Connect to electrs
nixops deploy -d bitcoin-node nixops deploy -d bitcoin-node
``` ```
3. Get electrs onion address 3. Get electrs onion address with format `<onion-address>:<port>`
``` ```
nodeinfo | grep 'ELECTRS_ONION' nodeinfo | jq -r .electrs.onion_address
``` ```
4. Connect to electrs 4. Connect to electrs
@ -98,7 +98,7 @@ Connect to electrs
On Desktop On Desktop
``` ```
electrum --oneserver -1 -s "<ELECTRS_ONION>:50001:t" -p socks5:localhost:9050 electrum --oneserver -1 -s "<electrs onion address>:t" -p socks5:localhost:9050
``` ```
On Android On Android
@ -107,16 +107,16 @@ Connect to electrs
Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050 Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050
Network > Auto-connect: OFF Network > Auto-connect: OFF
Network > One-server mode: ON Network > One-server mode: ON
Network > Server: <ELECTRS_ONION>:50001:t Network > Server: <electrs onion address>:t
``` ```
Connect to nix-bitcoin node through ssh Tor Hidden Service Connect to nix-bitcoin node through the SSH onion service
--- ---
1. Run `nodeinfo` on your nix-bitcoin node and note the `SSHD_ONION` 1. Get the SSH onion address (excluding the port suffix)
``` ```
nixops ssh operator@bitcoin-node nixops ssh operator@bitcoin-node
nodeinfo | grep 'SSHD_ONION' nodeinfo | jq -r .sshd.onion_address | sed 's/:.*//'
``` ```
2. Create a SSH key 2. Create a SSH key
@ -131,14 +131,14 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
# FIXME: Add your SSH pubkey # FIXME: Add your SSH pubkey
services.openssh.enable = true; services.openssh.enable = true;
users.users.root = { users.users.root = {
openssh.authorizedKeys.keys = [ "[contents of ~/.ssh/id_ed25519.pub]" ]; openssh.authorizedKeys.keys = [ "<contents of ~/.ssh/id_ed25519.pub>" ];
}; };
``` ```
4. Connect to your nix-bitcoin node's ssh Tor Hidden Service, forwarding a local port to the nix-bitcoin node's ssh server 4. Connect to your nix-bitcoin node's SSH onion service, forwarding a local port to the nix-bitcoin node's SSH server
``` ```
ssh -i ~/.ssh/id_ed25519 -L [random port of your choosing]:localhost:22 root@[your SSHD_ONION] ssh -i ~/.ssh/id_ed25519 -L <random port of your choosing>:localhost:22 root@<SSH onion address>
``` ```
5. Edit your `network-nixos.nix` to look like this 5. Edit your `network-nixos.nix` to look like this
@ -148,12 +148,12 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
bitcoin-node = bitcoin-node =
{ config, pkgs, ... }: { config, pkgs, ... }:
{ deployment.targetHost = "127.0.0.1"; { deployment.targetHost = "127.0.0.1";
deployment.targetPort = [random port of your choosing]; deployment.targetPort = <random port of your choosing>;
}; };
} }
``` ```
6. Now you can run `nixops deploy -d bitcoin-node` and it will connect through the ssh tunnel you established in step iv. This also allows you to do more complex ssh setups that `nixops ssh` doesn't support. An example would be authenticating with [Trezor's ssh agent](https://github.com/romanz/trezor-agent), which provides extra security. 6. Now you can run `nixops deploy -d bitcoin-node` and it will connect through the SSH tunnel you established in step iv. This also allows you to do more complex SSH setups that `nixops ssh` doesn't support. An example would be authenticating with [Trezor's SSH agent](https://github.com/romanz/trezor-agent), which provides extra security.
Initialize a Trezor for Bitcoin Core's Hardware Wallet Interface Initialize a Trezor for Bitcoin Core's Hardware Wallet Interface
--- ---
@ -263,7 +263,7 @@ you. If however, you want to manually initialize your wallet, follow these steps
## Run the tumbler ## Run the tumbler
The tumbler needs to be able to run in the background for a long time, use screen The tumbler needs to be able to run in the background for a long time, use screen
to run it accross ssh sessions. You can also use tmux in the same fashion. to run it accross SSH sessions. You can also use tmux in the same fashion.
1. Add screen to your `environment.systemPackages`, for example 1. Add screen to your `environment.systemPackages`, for example

View File

@ -27,6 +27,7 @@ with lib;
./onion-addresses.nix ./onion-addresses.nix
./onion-services.nix ./onion-services.nix
./netns-isolation.nix ./netns-isolation.nix
./nodeinfo.nix
./backups.nix ./backups.nix
]; ];

View File

@ -1,74 +1,117 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
let let
operatorName = config.nix-bitcoin.operator.name; cfg = config.nix-bitcoin.nodeinfo;
# Services included in the output
services = {
bitcoind = mkInfo "";
clightning = mkInfo ''
info["nodeid"] = shell("lightning-cli getinfo | jq -r '.id'")
if 'onion_address' in info:
info["id"] = f"{info['nodeid']}@{info['onion_address']}"
'';
lnd = mkInfo ''
info["nodeid"] = shell("lightning-cli getinfo | jq -r '.id'")
'';
electrs = mkInfo "";
spark-wallet = mkInfo "";
btcpayserver = mkInfo "";
liquidd = mkInfo "";
# Only add sshd when it has an onion service
sshd = name: cfg: mkIfOnionPort "sshd" (onionPort: ''
add_service("sshd", """set_onion_address(info, "sshd", ${onionPort})""")
'');
};
script = pkgs.writeScriptBin "nodeinfo" '' script = pkgs.writeScriptBin "nodeinfo" ''
set -eo pipefail #!${pkgs.python3}/bin/python
BITCOIND_ONION="$(cat /var/lib/onion-addresses/${operatorName}/bitcoind)" import json
echo BITCOIND_ONION="$BITCOIND_ONION" import subprocess
from collections import OrderedDict
if systemctl is-active --quiet clightning; then def success(*args):
CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id') return subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
CLIGHTNING_ONION="$(cat /var/lib/onion-addresses/${operatorName}/clightning)"
CLIGHTNING_ID="$CLIGHTNING_NODEID@$CLIGHTNING_ONION:9735"
echo CLIGHTNING_NODEID="$CLIGHTNING_NODEID"
echo CLIGHTNING_ONION="$CLIGHTNING_ONION"
echo CLIGHTNING_ID="$CLIGHTNING_ID"
fi
if systemctl is-active --quiet lnd; then def is_active(unit):
LND_NODEID=$(lncli getinfo | jq -r '.uris[0]') return success("systemctl", "is-active", "--quiet", unit)
echo LND_NODEID="$LND_NODEID"
fi
NGINX_ONION_FILE=/var/lib/onion-addresses/${operatorName}/nginx def is_enabled(unit):
if [ -e "$NGINX_ONION_FILE" ]; then return success("systemctl", "is-enabled", "--quiet", unit)
NGINX_ONION="$(cat $NGINX_ONION_FILE)"
echo NGINX_ONION="$NGINX_ONION"
fi
LIQUIDD_ONION_FILE=/var/lib/onion-addresses/${operatorName}/liquidd def cmd(*args):
if [ -e "$LIQUIDD_ONION_FILE" ]; then return subprocess.run(args, stdout=subprocess.PIPE).stdout.decode('utf-8')
LIQUIDD_ONION="$(cat $LIQUIDD_ONION_FILE)"
echo LIQUIDD_ONION="$LIQUIDD_ONION"
fi
SPARKWALLET_ONION_FILE=/var/lib/onion-addresses/${operatorName}/spark-wallet def shell(*args):
if [ -e "$SPARKWALLET_ONION_FILE" ]; then return cmd("bash", "-c", *args).strip()
SPARKWALLET_ONION="$(cat $SPARKWALLET_ONION_FILE)"
echo SPARKWALLET_ONION="http://$SPARKWALLET_ONION"
fi
ELECTRS_ONION_FILE=/var/lib/onion-addresses/${operatorName}/electrs infos = OrderedDict()
if [ -e "$ELECTRS_ONION_FILE" ]; then operator = "${config.nix-bitcoin.operator.name}"
ELECTRS_ONION="$(cat $ELECTRS_ONION_FILE)"
echo ELECTRS_ONION="$ELECTRS_ONION"
fi
BTCPAYSERVER_ONION_FILE=/var/lib/onion-addresses/${operatorName}/btcpayserver def set_onion_address(info, name, port):
if [ -e "$BTCPAYSERVER_ONION_FILE" ]; then path = f"/var/lib/onion-addresses/{operator}/{name}"
BTCPAYSERVER_ONION="$(cat $BTCPAYSERVER_ONION_FILE)" try:
echo BTCPAYSERVER_ONION="$BTCPAYSERVER_ONION" with open(path, "r") as f:
fi onion_address = f.read().strip()
except OSError:
print(f"error reading file {path}", file=sys.stderr)
return
info["onion_address"] = f"{onion_address}:{port}"
SSHD_ONION_FILE=/var/lib/onion-addresses/${operatorName}/sshd def add_service(service, make_info):
if [ -e "$SSHD_ONION_FILE" ]; then if not is_active(service):
SSHD_ONION="$(cat $SSHD_ONION_FILE)" infos[service] = "service is not running"
echo SSHD_ONION="$SSHD_ONION" else:
fi info = OrderedDict()
exec(make_info, globals(), locals())
infos[service] = info
if is_enabled("onion-adresses") and not is_active("onion-adresses"):
print("error: service 'onion-adresses' is not running")
exit(1)
${concatStrings infos}
print(json.dumps(infos, indent=2))
''; '';
infos = map (service:
let cfg = config.services.${service};
in optionalString cfg.enable (services.${service} service cfg)
) (builtins.attrNames services);
mkInfo = extraCode: name: cfg:
''
add_service("${name}", """
info["local_address"] = "${cfg.address}:${toString cfg.port}"
'' + mkIfOnionPort name (onionPort: ''
set_onion_address(info, "${name}", ${onionPort})
'') + extraCode + ''
""")
'';
mkIfOnionPort = name: fn:
if hiddenServices ? ${name} then
fn (toString (builtins.elemAt hiddenServices.${name}.map 0).port)
else
"";
inherit (config.services.tor) hiddenServices;
in { in {
options = { options = {
programs.nodeinfo = mkOption { nix-bitcoin.nodeinfo = {
readOnly = true; enable = mkEnableOption "nodeinfo";
default = script; program = mkOption {
readOnly = true;
default = script;
};
}; };
}; };
config = { config = {
environment.systemPackages = [ script ]; environment.systemPackages = optional cfg.enable script;
}; };
} }

View File

@ -14,7 +14,6 @@ let
in { in {
imports = [ imports = [
../modules.nix ../modules.nix
../nodeinfo.nix
./enable-tor.nix ./enable-tor.nix
]; ];
@ -75,5 +74,7 @@ in {
cp "${config.users.users.root.home}/.vbox-nixops-client-key" "${config.users.users.${operatorName}.home}" cp "${config.users.users.root.home}/.vbox-nixops-client-key" "${config.users.users.${operatorName}.home}"
''; '';
}; };
nix-bitcoin.nodeinfo.enable = true;
}; };
} }

View File

@ -68,6 +68,8 @@ let testEnv = rec {
''; '';
}; };
tests.nodeinfo = config.nix-bitcoin.nodeinfo.enable;
tests.backups = cfg.backups.enable; tests.backups = cfg.backups.enable;
# To test that unused secrets are made inaccessible by 'setup-secrets' # To test that unused secrets are made inaccessible by 'setup-secrets'
@ -119,6 +121,8 @@ let testEnv = rec {
services.joinmarket.enable = true; services.joinmarket.enable = true;
services.backups.enable = true; services.backups.enable = true;
nix-bitcoin.nodeinfo.enable = true;
services.hardware-wallets = { services.hardware-wallets = {
trezor = true; trezor = true;
ledger = true; ledger = true;

View File

@ -216,6 +216,16 @@ def _():
) )
@test("nodeinfo")
def _():
status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null")
if status == 0:
machine.wait_for_unit("onion-addresses")
json_info = succeed("sudo -u operator nodeinfo")
info = json.loads(json_info)
assert info["bitcoind"]["local_address"]
@test("secure-node") @test("secure-node")
def _(): def _():
assert_running("onion-addresses") assert_running("onion-addresses")