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.
# 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
# change the services.clightning.port or services.lnd.port to a port other than
# 9735.

View file

@ -104,8 +104,11 @@ in
description = "PostgreSQL username";
};
# Password is managed by nix-bitcoin secrets system.
# See: ${secretsDir}/lamassu-db-password
password = mkOption {
type = types.str;
default = "lamassu123";
description = "PostgreSQL password for lamassu-server user";
};
};
hostname = mkOption {
@ -184,15 +187,10 @@ in
user = cfg.user;
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 = ''
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 ::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
@ -247,11 +249,11 @@ in
"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 = {
description = "Setup PostgreSQL password for lamassu-server";
wantedBy = [ "multi-user.target" ];
after = [ "postgresql.service" "nix-bitcoin-secrets.target" ];
after = [ "postgresql.service" ];
wants = [ "postgresql.service" ];
serviceConfig = {
Type = "oneshot";
@ -259,12 +261,11 @@ in
User = "postgres";
};
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
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}..."
password=$(cat ${secretsDir}/lamassu-db-password)
${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '$password';"
${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '${cfg.database.password}';"
exit 0
fi
echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..."
@ -285,11 +286,12 @@ in
environment = {
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_PORT = "5432";
POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
# Server configuration
SERVER_PORT = toString cfg.serverPort;
@ -327,10 +329,8 @@ in
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
# Use TCP connection to localhost with password
export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules
cd ${cfg.package}
exec "$@"
@ -391,11 +391,12 @@ in
CA_PATH = cfg.certPath;
CERT_PATH = cfg.certPath;
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_PORT = "5432";
POSTGRES_DB = cfg.database.name;
POSTGRES_USER = cfg.database.user;
POSTGRES_PASSWORD = cfg.database.password;
MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic";
SKIP_2FA = if cfg.skip2FA then "true" else "false";
# Data directories
@ -410,10 +411,8 @@ in
#!/bin/bash
set -euo pipefail
export PATH=${pkgs.nodejs_22}/bin:$PATH
# Read database password from nix-bitcoin secrets
DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password)
export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}"
export POSTGRES_PASSWORD="$DB_PASSWORD"
# Use TCP connection to localhost with password
export DATABASE_URL="postgresql://${cfg.database.user}:${cfg.database.password}@127.0.0.1:5432/${cfg.database.name}"
export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules
cd ${cfg.package}
exec "$@"
@ -445,16 +444,14 @@ in
nodePackages.pnpm
postgresql
(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 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 POSTGRES_HOST="127.0.0.1"
export POSTGRES_PORT="5432"
export POSTGRES_DB="${cfg.database.name}"
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"}"
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.";
};
neutrino = {
peers = mkOption {
addpeers = mkOption {
type = types.listOf types.str;
default = [];
example = [ "192.168.1.1:8333" "btcd.example.com:8333" ];
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.
'';
};
@ -167,13 +167,6 @@ let
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}
'' else ''
bitcoin.node=neutrino
${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.peers}
neutrino.maxpeers=${toString cfg.neutrino.maxPeers}
${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.addpeers}
fee.url=${cfg.neutrino.feeUrl}
''}
@ -240,10 +232,10 @@ in {
services.lnd.port to a port other than 9735.
'';
}
{ assertion = cfg.backend != "neutrino" || cfg.neutrino.peers != [];
{ assertion = cfg.backend != "neutrino" || cfg.neutrino.addpeers != [];
message = ''
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 = {
wantedBy = [ "multi-user.target" ];
requires = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ];
after = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ] ++ [ "nix-bitcoin-secrets.target" ];
requires = optional (cfg.backend == "bitcoind") "bitcoind.service";
after = optional (cfg.backend == "bitcoind") "bitcoind.service" ++ [ "nix-bitcoin-secrets.target" ];
preStart = ''
install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
${optionalString (cfg.backend == "bitcoind") ''
@ -332,7 +324,7 @@ in {
users.users.${cfg.user} = {
isSystemUser = true;
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
};
users.groups.${cfg.group} = {};

View file

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

View file

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