nix-bitcoin/docs/security-model.md
padreug b2f2e14e2e
Some checks are pending
nix-bitcoin tests / build_test_drivers (push) Waiting to run
nix-bitcoin tests / test_scenario (default) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (joinmarket-bitcoind-29) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (netns) (push) Blocked by required conditions
nix-bitcoin tests / test_scenario (netnsRegtest) (push) Blocked by required conditions
nix-bitcoin tests / check_flake (push) Waiting to run
docs: add security model documentation
2026-01-27 17:52:55 +00:00

358 lines
16 KiB
Markdown

# Security Model
This document explains how nix-bitcoin protects your node and funds. It is intended
for operators who may not be familiar with NixOS security primitives.
For vulnerability reporting and the security fund, see [SECURITY.md](../SECURITY.md).
---
## Table of Contents
- [Secrets Management](#secrets-management)
- [What is stored](#what-is-stored)
- [When secrets are generated](#when-secrets-are-generated)
- [File permissions](#file-permissions)
- [What is NOT in the secrets directory](#what-is-not-in-the-secrets-directory)
- [Wallet Key Material](#wallet-key-material)
- [LND](#lnd)
- [bitcoind](#bitcoind)
- [Service Isolation](#service-isolation)
- [Unix users and groups](#unix-users-and-groups)
- [systemd hardening](#systemd-hardening)
- [Network namespace isolation](#network-namespace-isolation)
- [RPC whitelisting](#rpc-whitelisting)
- [Operator user](#operator-user)
- [Network Security](#network-security)
- [RPC binding](#rpc-binding)
- [Tor](#tor)
- [WireGuard](#wireguard)
- [Firewall and D-Bus](#firewall-and-d-bus)
- [Hardening Presets](#hardening-presets)
- [Threat Model Summary](#threat-model-summary)
- [Operator Responsibilities](#operator-responsibilities)
- [Further Reading](#further-reading)
---
## Secrets Management
nix-bitcoin has its own secrets system for managing service credentials. Secrets
are files stored in a dedicated directory on the node, protected by unix
permissions and never written to the
[Nix store](https://nixos.org/manual/nix/stable/store/) (which is world-readable).
The secrets directory location depends on the deployment method:
- **Krops / NixOps:** `/var/src/secrets`
- **Flakes / Containers:** `/etc/nix-bitcoin-secrets`
### What is stored
Each enabled service registers the secret files it needs. The secrets fall into
these categories:
**Passwords** (random 20-character strings):
- `bitcoin-rpcpassword-privileged`, `bitcoin-rpcpassword-public` — bitcoind RPC authentication
- `lnd-wallet-password` — encrypts LND's `wallet.db` on disk
- `rtl-password`, `joinmarket-password`, `btcpayserver-password`, `lightning-loop-password` — web UI and service passwords
- `backup-encryption-env` — backup encryption passphrase
**Derived credentials** (computed from passwords):
- `bitcoin-HMAC-privileged`, `bitcoin-HMAC-public` — HMAC hashes used in bitcoind's `rpcauth=` config
- Same pattern for Liquid (`liquid-rpcpassword-*`, `liquid-HMAC-*`)
**TLS keys and certificates:**
- `lnd-key` — EC private key (prime256v1) for LND's gRPC/REST API
- `lnd-cert` — self-signed x509 certificate (10-year validity)
**Other keys:**
- `clightning-replication-ssh-key` — for database replication
- WireGuard server/peer private and public keys (when the WireGuard preset is enabled)
### When secrets are generated
Secrets generation depends on the deployment method:
| Method | Where generated | Mechanism |
|--------|----------------|-----------|
| **Krops** | Locally (your machine) | `generate-secrets` shell command runs before rsync to target |
| **NixOps** | Locally (your machine) | `generate-secrets` shell command, transferred via NixOps keys |
| **Flakes** | On the target node | `setup-secrets.service` at boot with `nix-bitcoin.generateSecrets = true` |
| **Containers** | Inside the container | Same as Flakes |
| **Manual** | You create them yourself | Set `nix-bitcoin.secretsSetupMethod = "manual"` |
For Krops and NixOps, secrets are generated once locally and then synced to the
target. They are idempotent: existing files are never overwritten. If you delete
a secret file and redeploy, it will be regenerated.
### File permissions
The `setup-secrets` systemd service enforces permissions on every boot or deploy:
1. The secrets directory is set to `root:root 0700` during setup
2. Each secret file is assigned ownership and permissions as declared by its
module (e.g. `lnd-wallet-password` is owned by `lnd:lnd 0440`)
3. Any file in the directory not claimed by a module is locked to `root:root 0440`
4. The directory is opened to `0751` after setup completes, allowing services
to access their specific files
All nix-bitcoin services declare a systemd dependency on `nix-bitcoin-secrets.target`,
which is only reached after `setup-secrets` completes successfully.
### What is NOT in the secrets directory
The secrets system manages **service-to-service credentials**. The following are
explicitly outside its scope:
- **LND seed mnemonic** (`/var/lib/lnd/lnd-seed-mnemonic`) — see [Wallet Key Material](#wallet-key-material)
- **LND wallet database** (`/var/lib/lnd/chain/bitcoin/<network>/wallet.db`)
- **LND macaroons** (`/var/lib/lnd/chain/bitcoin/<network>/*.macaroon`)
- **LND channel backup** (`/var/lib/lnd/chain/bitcoin/<network>/channel.backup`)
- **bitcoind wallet** (`wallet.dat` or descriptors, if created by the operator)
---
## Wallet Key Material
### LND
LND uses the [aezeed cipher seed](https://github.com/lightningnetwork/lnd/tree/master/aezeed)
scheme — a 24-word mnemonic that encodes the wallet's master entropy and a
birthday timestamp.
**How the wallet is created on first boot:**
The LND module's `preStart` script checks if `wallet.db` exists. If not:
1. `lndinit gen-seed` generates a fresh 24-word aezeed mnemonic and writes it
to `/var/lib/lnd/lnd-seed-mnemonic`
2. `lndinit init-wallet` creates `wallet.db` using the seed and the
`lnd-wallet-password` from the secrets directory
3. LND starts and auto-unlocks using the wallet password file
**Important details:**
- **No cipher seed passphrase:** nix-bitcoin does not set an aezeed passphrase.
The mnemonic is encrypted with the default passphrase `"aezeed"`, which offers
no real protection. Anyone with the 24 words can derive all keys.
- **Auto-unlock tradeoff:** The wallet password is stored on disk in the secrets
directory so LND can start unattended. This means root access to the node
grants access to the wallet.
- **The seed file is only used once:** After `wallet.db` is created, LND never
reads the seed file again. It can and should be deleted from disk after backup.
- **Static Channel Backups (SCB):** LND maintains `channel.backup` which is
updated atomically every time a channel is opened or closed. It is encrypted
with a key derived from the seed. If you re-seed, old SCBs become invalid.
### bitcoind
nix-bitcoin does **not** create or manage a bitcoind wallet. The `bitcoind.nix`
module configures the daemon and RPC authentication only. If you create a wallet
via `bitcoin-cli createwallet`, its key material is entirely your responsibility
to manage and back up.
---
## Service Isolation
### Unix users and groups
Each nix-bitcoin service runs as its own dedicated system user (e.g. `bitcoind`,
`lnd`, `clightning`). Services cannot read each other's data directories unless
explicitly granted access through group memberships.
### systemd hardening
All nix-bitcoin services apply a strict systemd security profile
([`pkgs/lib.nix`](../pkgs/lib.nix)), which includes:
| Setting | Effect |
|---------|--------|
| `ProtectSystem = "strict"` | Filesystem is read-only except for explicitly allowed paths |
| `ProtectHome = true` | No access to home directories |
| `PrivateTmp = true` | Isolated `/tmp` per service |
| `PrivateDevices = true` | No access to physical devices |
| `NoNewPrivileges = true` | Cannot gain new privileges via setuid/setgid |
| `MemoryDenyWriteExecute = true` | Prevents writable+executable memory (JIT disabled) |
| `ProtectKernelTunables = true` | Cannot modify kernel parameters |
| `ProtectKernelModules = true` | Cannot load kernel modules |
| `ProtectProc = "invisible"` | Other processes hidden in `/proc` |
| `PrivateUsers = true` | Cannot see other users |
| `IPAddressDeny = "any"` | All network denied by default (services opt in to what they need) |
| `CapabilityBoundingSet = ""` | No Linux capabilities |
| `SystemCallFilter` | Restricted to `@system-service` syscall set |
| `RestrictAddressFamilies` | Only `AF_UNIX`, `AF_INET`, `AF_INET6` |
For the full list, see `man systemd.exec` and `man systemd.resource-control`.
### Network namespace isolation
The optional [`netns-isolation`](../modules/netns-isolation.nix) module places
each service in its own
[network namespace](https://man7.org/linux/man-pages/man7/network_namespaces.7.html).
Services can only communicate with other services through explicitly allowed
paths. For example, LND can reach bitcoind's RPC, but RTL cannot directly reach
bitcoind.
Enable with:
```nix
nix-bitcoin.netns-isolation.enable = true;
```
Note: This is not compatible with the WireGuard preset.
### RPC whitelisting
The [`bitcoind-rpc-public-whitelist`](../modules/bitcoind-rpc-public-whitelist.nix)
module restricts which RPC methods the `public` RPC user can call. Services that
only need read access (like electrs or mempool) use the `public` user, which
cannot call wallet or administrative RPCs. Only the `privileged` user has full
RPC access.
### Operator user
The [`operator`](../modules/operator.nix) module creates a non-root user with
group memberships for each enabled service. This lets you run `bitcoin-cli`,
`lncli`, `lightning-cli`, etc. without being root. The operator user has read
access to service data but does not have write access or the ability to modify
service configuration.
---
## Network Security
### RPC binding
bitcoind and LND bind their RPC/API interfaces to `127.0.0.1` by default. They
are not reachable from outside the machine unless you explicitly change the bind
address.
### Tor
The [`secure-node`](../modules/presets/secure-node.nix) preset imports
[`enable-tor`](../modules/presets/enable-tor.nix), which routes all outbound
traffic from nix-bitcoin services through Tor. Services that support it can also
accept inbound connections via
[onion services](https://community.torproject.org/onion-services/overview/).
When Tor enforcement is active (`tor.enforce = true`), a service's systemd
`IPAddressAllow` is restricted to localhost and link-local addresses only,
preventing any clearnet communication.
### WireGuard
The [`wireguard`](../modules/presets/wireguard.nix) preset creates an encrypted
VPN tunnel for connecting a mobile wallet (e.g. Zeus) to your node. It sets up a
single-peer WireGuard interface with:
- The node as server (`10.10.0.1`)
- Your device as peer (`10.10.0.2`)
- Firewall rules restricting the peer to only reach the node's address (no
routing to the broader network)
- Helper commands (`nix-bitcoin-wg-connect`, `lndconnect-wg`) that generate QR
codes for one-scan setup
This is an alternative to connecting over Tor, offering lower latency at the
cost of requiring a reachable IP and port forwarding for NAT.
### Firewall and D-Bus
The `secure-node` preset enables the NixOS firewall and the
[`security`](../modules/security.nix) module's D-Bus process information hiding.
The D-Bus restriction prevents unprivileged services from discovering other
services' process information (command lines, cgroup paths) via systemd's D-Bus
interface.
---
## Hardening Presets
nix-bitcoin provides three presets that can be combined:
**[`secure-node.nix`](../modules/presets/secure-node.nix)** — Opinionated base
configuration:
- Enables firewall
- Routes all traffic through Tor
- Replaces `sudo` with `doas`
- Enables D-Bus process information hiding
- Creates an SSH onion service
- Enables the operator user
- Sets up daily backups
- Enables `nodeinfo` command
**[`hardened.nix`](../modules/presets/hardened.nix)** — Imports the
[NixOS hardened kernel profile](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix).
This enables kernel-level hardening (address space layout randomization, kernel
module restrictions, etc.) at a ~50% performance cost. Resets `allowUserNamespaces`
to `true` (needed for Nix sandboxing) and uses the standard `libc` allocator.
**[`hardened-extended.nix`](../modules/presets/hardened-extended.nix)** — Builds
on `hardened.nix` with additional restrictions:
- Disables kernel log leaks, SysRq, debugfs
- Restricts ptrace, TTY line disciplines, core dumps
- Disables TCP SACK and timestamps
- Blacklists obscure network protocols, rare filesystems, Bluetooth, webcam,
Thunderbolt/FireWire (DMA attack prevention)
- Enables USBGuard
- Enforces signed kernel modules and kernel lockdown
See [madaidan's Linux Hardening Guide](https://madaidans-insecurities.github.io/guides/linux-hardening.html)
for background on these settings.
None of these presets affect secrets generation — that is controlled separately
by the deployment method and `nix-bitcoin.generateSecrets`.
---
## Threat Model Summary
| Scenario | Impact | Notes |
|----------|--------|-------|
| **Attacker has root on the node** | Full compromise. All funds at risk. | `lnd-wallet-password` is on disk, allowing wallet decryption. All secrets are readable. |
| **Secrets directory leaked, no network access** | No direct fund theft. | Secrets contain RPC passwords and TLS keys but not wallet key material. Without network access to the RPC port, passwords are not exploitable. |
| **Secrets directory leaked, with RPC network access** | bitcoind wallet funds at risk (if a wallet exists). | The `privileged` RPC password allows calling any bitcoind RPC, including spending. LND funds are safer: the attacker also needs a macaroon (not in secrets dir) to call LND RPCs. |
| **LND seed mnemonic leaked** | All LND on-chain funds compromised. | The attacker can derive all keys. Channel funds are at risk if the attacker broadcasts old commitment transactions. Immediate action required: sweep funds, close channels, re-seed. |
| **Nix store accessed** | No secrets exposed. | Secrets are never written to the Nix store. Configuration files reference secret file paths, not secret values. |
| **Deploy machine compromised** | Full compromise. | For Krops/NixOps, the deploy machine holds plaintext secrets and controls what code is deployed to the node. |
---
## Operator Responsibilities
These items are not automated by nix-bitcoin and require manual action:
- [ ] **Back up the LND seed mnemonic.** After first boot, copy
`/var/lib/lnd/lnd-seed-mnemonic` to secure offline storage. Then delete the
file from the node. This is the only way to recover on-chain funds.
- [ ] **Back up channel state.** Copy
`/var/lib/lnd/chain/bitcoin/mainnet/channel.backup` after opening new channels.
This allows off-chain fund recovery via the
[Data Loss Protection protocol](https://github.com/lightningnetwork/lnd/blob/master/docs/recovery.md).
Alternatively, enable `services.backups` to automate this.
- [ ] **Secure the deploy machine.** For Krops and NixOps deployments, your local
`secrets/` directory contains plaintext credentials. If your local machine is
compromised, the node is compromised.
- [ ] **Review enabled presets.** Consider enabling `secure-node.nix` (Tor,
firewall, operator user) and `netns-isolation` (network namespace separation
between services). These are not enabled by default.
- [ ] **Understand the auto-unlock tradeoff.** LND's wallet password is stored
on disk so the service can start unattended. This means anyone with root access
to the node can unlock the wallet. There is no way to require manual unlock at
boot within the current nix-bitcoin design.
---
## Further Reading
- [NixOS Hardened Profile](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix) — kernel and userspace hardening applied by `hardened.nix`
- [systemd.exec(5)](https://www.freedesktop.org/software/systemd/man/systemd.exec.html) — reference for the systemd sandboxing options used in `pkgs/lib.nix`
- [systemd.resource-control(5)](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html) — resource limiting and IP address filtering
- [Linux network namespaces](https://man7.org/linux/man-pages/man7/network_namespaces.7.html) — the kernel feature behind `netns-isolation`
- [aezeed cipher seed](https://github.com/lightningnetwork/lnd/tree/master/aezeed) — LND's seed scheme and how it differs from BIP39
- [LND fund recovery](https://github.com/lightningnetwork/lnd/blob/master/docs/recovery.md) — on-chain and off-chain recovery procedures
- [madaidan's Linux Hardening Guide](https://madaidans-insecurities.github.io/guides/linux-hardening.html) — background for `hardened-extended.nix` settings
- [Nix Store security](https://nixos.org/manual/nix/stable/store/) — why secrets must not be written to the store