Compare commits

..

No commits in common. "800afe647fd34ce5216864f7db14341cd5734dd7" and "8ee71833b985239fb496e452c51a70ac9c1e59f0" have entirely different histories.

6 changed files with 32 additions and 220 deletions

View file

@ -1,167 +0,0 @@
# Lamassu Server: Future Peer Authentication Implementation
This document describes how to implement PostgreSQL peer authentication (Unix socket) for the lamassu-server module in the future.
## Current Limitation
The lamassu-server's `constants.js` builds the database URL from individual environment variables:
```javascript
const POSTGRES_USER = process.env.POSTGRES_USER
const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD
const POSTGRES_HOST = process.env.POSTGRES_HOST
const POSTGRES_PORT = process.env.POSTGRES_PORT
const POSTGRES_DB = process.env.POSTGRES_DB
const PSQL_URL = `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`
```
This means:
- `DATABASE_URL` environment variable is **not supported**
- Unix socket connections are **not possible** (setting `POSTGRES_HOST=/run/postgresql` produces invalid URL syntax)
- Password authentication via TCP is **required**
## Required Upstream Changes
### 1. Modify `packages/server/lib/constants.js`
Add support for `DATABASE_URL` environment variable:
```javascript
const POSTGRES_USER = process.env.POSTGRES_USER
const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD
const POSTGRES_HOST = process.env.POSTGRES_HOST
const POSTGRES_PORT = process.env.POSTGRES_PORT
const POSTGRES_DB = process.env.POSTGRES_DB
// Support DATABASE_URL for Unix socket connections (peer auth)
const PSQL_URL = process.env.DATABASE_URL ||
`postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`
```
### 2. Update nix-bitcoin module
Once upstream supports `DATABASE_URL`, update `modules/lamassu-lnbits.nix`:
#### Remove password secret
```nix
nix-bitcoin.secrets = {
lamassu-key.user = cfg.user;
lamassu-cert = {
user = cfg.user;
permissions = "444";
};
# Remove: lamassu-db-password
};
nix-bitcoin.generateSecretsCmds.lamassu = ''
makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}'
# Remove: makePasswordSecret lamassu-db-password
'';
```
#### Remove lamassu-postgres-setup service
Delete the entire `systemd.services.lamassu-postgres-setup` block.
#### Simplify PostgreSQL config
```nix
services.postgresql = {
enable = true;
package = pkgs.postgresql_15;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{
name = cfg.database.user;
ensureDBOwnership = true;
}
];
# No custom authentication needed - default peer auth works
};
```
#### Update service wrapper scripts
```nix
lamassuEnv = pkgs.writeShellScript "lamassu-env" ''
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Use Unix socket for peer authentication (no password needed)
export DATABASE_URL="postgresql://${cfg.database.user}@/${cfg.database.name}?host=/run/postgresql"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules
cd ${cfg.package}
exec "$@"
'';
```
#### Remove password-related env vars
Remove from service `environment` blocks:
- `POSTGRES_HOST`
- `POSTGRES_PORT`
- `POSTGRES_PASSWORD`
## Benefits of Peer Authentication
1. **No password to manage** - No secrets generation, storage, or rotation
2. **More secure** - Authentication handled by OS kernel, not application
3. **Simpler module** - ~55 fewer lines of code
4. **Consistent with btcpayserver** - Same pattern used by other nix-bitcoin modules
## Architecture Comparison
### Current (password auth via TCP)
```
┌─────────────────────────────────────────────────────────────┐
│ lamassu-server │
│ └── connects to 127.0.0.1:5432 with password │
│ │ │
│ ▼ │
│ PostgreSQL (TCP) │
│ └── validates password via md5 auth │
└─────────────────────────────────────────────────────────────┘
Requires:
- lamassu-db-password secret
- lamassu-postgres-setup service
- POSTGRES_HOST, POSTGRES_PORT, POSTGRES_PASSWORD env vars
```
### Future (peer auth via Unix socket)
```
┌─────────────────────────────────────────────────────────────┐
│ lamassu-server (runs as user: lamassu-server) │
│ └── connects to /run/postgresql socket │
│ │ │
│ ▼ │
│ PostgreSQL (Unix socket) │
│ └── validates OS user matches DB user (peer auth) │
└─────────────────────────────────────────────────────────────┘
Requires:
- Nothing extra! Default NixOS PostgreSQL config works.
```
## DATABASE_URL Format for Unix Socket
The node-postgres library supports Unix sockets via the `host` query parameter:
```
postgresql://username@/database?host=/run/postgresql
```
Components:
- `username` - PostgreSQL user (must match OS user for peer auth)
- `database` - Database name
- `host=/run/postgresql` - Path to Unix socket directory
## Reference
- btcpayserver module uses peer auth: `postgres=User ID=btcpayserver;Host=/run/postgresql;Database=btcpaydb`
- node-postgres Unix socket docs: https://node-postgres.com/features/connecting
- pg-promise (used by lamassu): https://github.com/vitaly-t/pg-promise

View file

@ -80,15 +80,6 @@
# Set this to enable lnd, a lightning implementation written in Go. # Set this to enable lnd, a lightning implementation written in Go.
# services.lnd.enable = true; # services.lnd.enable = true;
# #
# By default, lnd uses bitcoind as its backend. You can use neutrino instead
# to run lnd without a full Bitcoin node. This is useful for resource-constrained
# systems, but provides less privacy and security than a local bitcoind.
# services.lnd = {
# enable = true;
# backend = "neutrino";
# neutrino.peers = [ "btcd.example.com:8333" ];
# };
#
# NOTE: In order to avoid collisions with clightning you must disable clightning or # NOTE: In order to avoid collisions with clightning you must disable clightning or
# change the services.clightning.port or services.lnd.port to a port other than # change the services.clightning.port or services.lnd.port to a port other than
# 9735. # 9735.

View file

@ -104,8 +104,11 @@ in
description = "PostgreSQL username"; description = "PostgreSQL username";
}; };
# Password is managed by nix-bitcoin secrets system. password = mkOption {
# See: ${secretsDir}/lamassu-db-password type = types.str;
default = "lamassu123";
description = "PostgreSQL password for lamassu-server user";
};
}; };
hostname = mkOption { hostname = mkOption {
@ -184,15 +187,10 @@ in
user = cfg.user; user = cfg.user;
permissions = "444"; # World readable (it's a public cert) permissions = "444"; # World readable (it's a public cert)
}; };
lamassu-db-password = {
user = cfg.user;
group = "postgres"; # PostgreSQL needs to read this too
};
}; };
nix-bitcoin.generateSecretsCmds.lamassu = '' nix-bitcoin.generateSecretsCmds.lamassu = ''
makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}' makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}'
makePasswordSecret lamassu-db-password
''; '';
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
@ -218,6 +216,10 @@ in
host all all 127.0.0.1/32 md5 host all all 127.0.0.1/32 md5
host all all ::1/128 md5 host all all ::1/128 md5
''; '';
# Set initial password for lamassu-server user
initialScript = pkgs.writeText "postgres-init.sql" ''
ALTER USER "${cfg.database.user}" WITH PASSWORD '${cfg.database.password}';
'';
}; };
# Create system users and groups # Create system users and groups
@ -247,11 +249,11 @@ in
"Z '${cfg.package}' 0755 ${cfg.user} ${cfg.group} - -" "Z '${cfg.package}' 0755 ${cfg.user} ${cfg.group} - -"
]; ];
# Service to set PostgreSQL password from nix-bitcoin secrets # Service to set PostgreSQL password
systemd.services.lamassu-postgres-setup = { systemd.services.lamassu-postgres-setup = {
description = "Setup PostgreSQL password for lamassu-server"; description = "Setup PostgreSQL password for lamassu-server";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "postgresql.service" "nix-bitcoin-secrets.target" ]; after = [ "postgresql.service" ];
wants = [ "postgresql.service" ]; wants = [ "postgresql.service" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
@ -259,12 +261,11 @@ in
User = "postgres"; User = "postgres";
}; };
script = '' script = ''
# Wait for user to exist, then set password from secrets # Wait for user to exist, then set password
for i in {1..30}; do for i in {1..30}; do
if ${pkgs.postgresql}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.database.user}'" | grep -q 1; then if ${pkgs.postgresql}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.database.user}'" | grep -q 1; then
echo "Setting password for ${cfg.database.user}..." echo "Setting password for ${cfg.database.user}..."
password=$(cat ${secretsDir}/lamassu-db-password) ${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '${cfg.database.password}';"
${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '$password';"
exit 0 exit 0
fi fi
echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..." echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..."
@ -285,11 +286,12 @@ in
environment = { environment = {
NODE_ENV = "production"; NODE_ENV = "production";
# Database configuration (password read at runtime from secrets) # Database configuration (using TCP with password auth)
POSTGRES_HOST = "127.0.0.1"; POSTGRES_HOST = "127.0.0.1";
POSTGRES_PORT = "5432"; POSTGRES_PORT = "5432";
POSTGRES_DB = cfg.database.name; POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user; POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
# Server configuration # Server configuration
SERVER_PORT = toString cfg.serverPort; SERVER_PORT = toString cfg.serverPort;
@ -327,10 +329,8 @@ in
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH export PATH=${pkgs.nodejs_22}/bin:$PATH
# Read database password from nix-bitcoin secrets # Use TCP connection to localhost with password
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules
cd ${cfg.package} cd ${cfg.package}
exec "$@" exec "$@"
@ -391,11 +391,12 @@ in
CA_PATH = cfg.certPath; CA_PATH = cfg.certPath;
CERT_PATH = cfg.certPath; CERT_PATH = cfg.certPath;
KEY_PATH = cfg.keyPath; KEY_PATH = cfg.keyPath;
# Database configuration (password read at runtime from secrets) # Database configuration (using TCP with password auth)
POSTGRES_HOST = "127.0.0.1"; POSTGRES_HOST = "127.0.0.1";
POSTGRES_PORT = "5432"; POSTGRES_PORT = "5432";
POSTGRES_DB = cfg.database.name; POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user; POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic"; MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic";
SKIP_2FA = if cfg.skip2FA then "true" else "false"; SKIP_2FA = if cfg.skip2FA then "true" else "false";
# Data directories # Data directories
@ -410,10 +411,8 @@ in
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH export PATH=${pkgs.nodejs_22}/bin:$PATH
# Read database password from nix-bitcoin secrets # Use TCP connection to localhost with password
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules
cd ${cfg.package} cd ${cfg.package}
exec "$@" exec "$@"
@ -445,16 +444,14 @@ in
nodePackages.pnpm nodePackages.pnpm
postgresql postgresql
(writeShellScriptBin "lamassu-register-user" '' (writeShellScriptBin "lamassu-register-user" ''
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export NODE_PATH="${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules" export NODE_PATH="${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules"
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}" export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export HOSTNAME="${cfg.hostname}" export HOSTNAME="${cfg.hostname}"
export POSTGRES_HOST="127.0.0.1" export POSTGRES_HOST="127.0.0.1"
export POSTGRES_PORT="5432" export POSTGRES_PORT="5432"
export POSTGRES_DB="${cfg.database.name}" export POSTGRES_DB="${cfg.database.name}"
export POSTGRES_USER="${cfg.database.user}" export POSTGRES_USER="${cfg.database.user}"
export POSTGRES_PASSWORD="$DB_PASSWORD" export POSTGRES_PASSWORD="${cfg.database.password}"
export SKIP_2FA="${if cfg.skip2FA then "true" else "false"}" export SKIP_2FA="${if cfg.skip2FA then "true" else "false"}"
sudo -E -u ${cfg.user} bash -c "cd ${cfg.package}/packages/server && ${pkgs.nodejs_22}/bin/node bin/lamassu-register \"\$@\"" -- "$@" sudo -E -u ${cfg.user} bash -c "cd ${cfg.package}/packages/server && ${pkgs.nodejs_22}/bin/node bin/lamassu-register \"\$@\"" -- "$@"

View file

@ -150,12 +150,12 @@ let
description = "The backend to use for fetching blockchain data."; description = "The backend to use for fetching blockchain data.";
}; };
neutrino = { neutrino = {
peers = mkOption { addpeers = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [];
example = [ "192.168.1.1:8333" "btcd.example.com:8333" ]; example = [ "192.168.1.1:8333" "btcd.example.com:8333" ];
description = '' description = ''
List of Bitcoin full node peers to connect to for neutrino. List of Bitcoin full node peers to connect to via neutrino.addpeer.
Multiple peers provide redundancy for maximum uptime. Multiple peers provide redundancy for maximum uptime.
''; '';
}; };
@ -167,13 +167,6 @@ let
Required because neutrino doesn't have access to mempool data. Required because neutrino doesn't have access to mempool data.
''; '';
}; };
maxPeers = mkOption {
type = types.int;
default = 8;
description = ''
Maximum number of inbound and outbound peers for neutrino.
'';
};
}; };
}; };
@ -213,8 +206,7 @@ let
bitcoind.zmqpubrawtx=${zmqHandleSpecialAddress bitcoind.zmqpubrawtx} bitcoind.zmqpubrawtx=${zmqHandleSpecialAddress bitcoind.zmqpubrawtx}
'' else '' '' else ''
bitcoin.node=neutrino bitcoin.node=neutrino
${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.peers} ${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.addpeers}
neutrino.maxpeers=${toString cfg.neutrino.maxPeers}
fee.url=${cfg.neutrino.feeUrl} fee.url=${cfg.neutrino.feeUrl}
''} ''}
@ -240,10 +232,10 @@ in {
services.lnd.port to a port other than 9735. services.lnd.port to a port other than 9735.
''; '';
} }
{ assertion = cfg.backend != "neutrino" || cfg.neutrino.peers != []; { assertion = cfg.backend != "neutrino" || cfg.neutrino.addpeers != [];
message = '' message = ''
When using neutrino backend, you must configure at least one peer When using neutrino backend, you must configure at least one peer
in services.lnd.neutrino.peers. in services.lnd.neutrino.addpeers.
''; '';
} }
]; ];
@ -269,8 +261,8 @@ in {
systemd.services.lnd = { systemd.services.lnd = {
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
requires = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ]; requires = optional (cfg.backend == "bitcoind") "bitcoind.service";
after = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ] ++ [ "nix-bitcoin-secrets.target" ]; after = optional (cfg.backend == "bitcoind") "bitcoind.service" ++ [ "nix-bitcoin-secrets.target" ];
preStart = '' preStart = ''
install -m600 ${configFile} '${cfg.dataDir}/lnd.conf' install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
${optionalString (cfg.backend == "bitcoind") '' ${optionalString (cfg.backend == "bitcoind") ''
@ -332,7 +324,7 @@ in {
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true; isSystemUser = true;
group = cfg.group; group = cfg.group;
extraGroups = optionals (cfg.backend == "bitcoind") [ "bitcoinrpc-public" ]; extraGroups = optional (cfg.backend == "bitcoind") "bitcoinrpc-public";
home = cfg.dataDir; # lnd creates .lnd dir in HOME home = cfg.dataDir; # lnd creates .lnd dir in HOME
}; };
users.groups.${cfg.group} = {}; users.groups.${cfg.group} = {};

View file

@ -28,7 +28,6 @@
./joinmarket.nix ./joinmarket.nix
./joinmarket-ob-watcher.nix ./joinmarket-ob-watcher.nix
./hardware-wallets.nix ./hardware-wallets.nix
./lamassu-lnbits.nix
# Support features # Support features
./versioning.nix ./versioning.nix

View file

@ -11,7 +11,7 @@ let
}; };
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 3001; default = 3000;
description = "HTTP server port."; description = "HTTP server port.";
}; };
dataDir = mkOption { dataDir = mkOption {