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
* 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
* Application services
@ -74,6 +73,7 @@ NixOS modules
* [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI)
* Helper
* [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
* [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
---
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
---
@ -86,10 +86,10 @@ Connect to electrs
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
@ -98,7 +98,7 @@ Connect to electrs
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
@ -107,16 +107,16 @@ Connect to electrs
Network > Proxy mode: socks5, Host: 127.0.0.1, Port: 9050
Network > Auto-connect: OFF
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
nodeinfo | grep 'SSHD_ONION'
nodeinfo | jq -r .sshd.onion_address | sed 's/:.*//'
```
2. Create a SSH key
@ -131,14 +131,14 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
# FIXME: Add your SSH pubkey
services.openssh.enable = true;
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
@ -148,12 +148,12 @@ Connect to nix-bitcoin node through ssh Tor Hidden Service
bitcoin-node =
{ config, pkgs, ... }:
{ 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
---
@ -263,7 +263,7 @@ you. If however, you want to manually initialize your wallet, follow these steps
## Run the tumbler
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

View File

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

View File

@ -1,74 +1,117 @@
{ config, lib, pkgs, ... }:
with lib;
let
operatorName = config.nix-bitcoin.operator.name;
script = pkgs.writeScriptBin "nodeinfo" ''
set -eo pipefail
cfg = config.nix-bitcoin.nodeinfo;
BITCOIND_ONION="$(cat /var/lib/onion-addresses/${operatorName}/bitcoind)"
echo BITCOIND_ONION="$BITCOIND_ONION"
if systemctl is-active --quiet clightning; then
CLIGHTNING_NODEID=$(lightning-cli getinfo | jq -r '.id')
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
LND_NODEID=$(lncli getinfo | jq -r '.uris[0]')
echo LND_NODEID="$LND_NODEID"
fi
NGINX_ONION_FILE=/var/lib/onion-addresses/${operatorName}/nginx
if [ -e "$NGINX_ONION_FILE" ]; then
NGINX_ONION="$(cat $NGINX_ONION_FILE)"
echo NGINX_ONION="$NGINX_ONION"
fi
LIQUIDD_ONION_FILE=/var/lib/onion-addresses/${operatorName}/liquidd
if [ -e "$LIQUIDD_ONION_FILE" ]; then
LIQUIDD_ONION="$(cat $LIQUIDD_ONION_FILE)"
echo LIQUIDD_ONION="$LIQUIDD_ONION"
fi
SPARKWALLET_ONION_FILE=/var/lib/onion-addresses/${operatorName}/spark-wallet
if [ -e "$SPARKWALLET_ONION_FILE" ]; then
SPARKWALLET_ONION="$(cat $SPARKWALLET_ONION_FILE)"
echo SPARKWALLET_ONION="http://$SPARKWALLET_ONION"
fi
ELECTRS_ONION_FILE=/var/lib/onion-addresses/${operatorName}/electrs
if [ -e "$ELECTRS_ONION_FILE" ]; then
ELECTRS_ONION="$(cat $ELECTRS_ONION_FILE)"
echo ELECTRS_ONION="$ELECTRS_ONION"
fi
BTCPAYSERVER_ONION_FILE=/var/lib/onion-addresses/${operatorName}/btcpayserver
if [ -e "$BTCPAYSERVER_ONION_FILE" ]; then
BTCPAYSERVER_ONION="$(cat $BTCPAYSERVER_ONION_FILE)"
echo BTCPAYSERVER_ONION="$BTCPAYSERVER_ONION"
fi
SSHD_ONION_FILE=/var/lib/onion-addresses/${operatorName}/sshd
if [ -e "$SSHD_ONION_FILE" ]; then
SSHD_ONION="$(cat $SSHD_ONION_FILE)"
echo SSHD_ONION="$SSHD_ONION"
fi
# 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" ''
#!${pkgs.python3}/bin/python
import json
import subprocess
from collections import OrderedDict
def success(*args):
return subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
def is_active(unit):
return success("systemctl", "is-active", "--quiet", unit)
def is_enabled(unit):
return success("systemctl", "is-enabled", "--quiet", unit)
def cmd(*args):
return subprocess.run(args, stdout=subprocess.PIPE).stdout.decode('utf-8')
def shell(*args):
return cmd("bash", "-c", *args).strip()
infos = OrderedDict()
operator = "${config.nix-bitcoin.operator.name}"
def set_onion_address(info, name, port):
path = f"/var/lib/onion-addresses/{operator}/{name}"
try:
with open(path, "r") as f:
onion_address = f.read().strip()
except OSError:
print(f"error reading file {path}", file=sys.stderr)
return
info["onion_address"] = f"{onion_address}:{port}"
def add_service(service, make_info):
if not is_active(service):
infos[service] = "service is not running"
else:
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 {
options = {
programs.nodeinfo = mkOption {
nix-bitcoin.nodeinfo = {
enable = mkEnableOption "nodeinfo";
program = mkOption {
readOnly = true;
default = script;
};
};
};
config = {
environment.systemPackages = [ script ];
environment.systemPackages = optional cfg.enable script;
};
}

View File

@ -14,7 +14,6 @@ let
in {
imports = [
../modules.nix
../nodeinfo.nix
./enable-tor.nix
];
@ -75,5 +74,7 @@ in {
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;
# To test that unused secrets are made inaccessible by 'setup-secrets'
@ -119,6 +121,8 @@ let testEnv = rec {
services.joinmarket.enable = true;
services.backups.enable = true;
nix-bitcoin.nodeinfo.enable = true;
services.hardware-wallets = {
trezor = 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")
def _():
assert_running("onion-addresses")