diff --git a/docs/lamassu-future-peer-auth.md b/docs/lamassu-future-peer-auth.md new file mode 100644 index 0000000..88cc9fc --- /dev/null +++ b/docs/lamassu-future-peer-auth.md @@ -0,0 +1,167 @@ +# 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 diff --git a/examples/configuration.nix b/examples/configuration.nix index 125beaa..b242392 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -80,6 +80,15 @@ # 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. diff --git a/modules/lamassu-lnbits.nix b/modules/lamassu-lnbits.nix index 82fbbd5..7bb6b4e 100644 --- a/modules/lamassu-lnbits.nix +++ b/modules/lamassu-lnbits.nix @@ -104,11 +104,8 @@ in description = "PostgreSQL username"; }; - password = mkOption { - type = types.str; - default = "lamassu123"; - description = "PostgreSQL password for lamassu-server user"; - }; + # Password is managed by nix-bitcoin secrets system. + # See: ${secretsDir}/lamassu-db-password }; hostname = mkOption { @@ -187,10 +184,15 @@ 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 ''; # ═══════════════════════════════════════════════════════════════════════════ @@ -216,10 +218,6 @@ 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 @@ -249,11 +247,11 @@ in "Z '${cfg.package}' 0755 ${cfg.user} ${cfg.group} - -" ]; - # Service to set PostgreSQL password + # Service to set PostgreSQL password from nix-bitcoin secrets systemd.services.lamassu-postgres-setup = { description = "Setup PostgreSQL password for lamassu-server"; wantedBy = [ "multi-user.target" ]; - after = [ "postgresql.service" ]; + after = [ "postgresql.service" "nix-bitcoin-secrets.target" ]; wants = [ "postgresql.service" ]; serviceConfig = { Type = "oneshot"; @@ -261,11 +259,12 @@ in User = "postgres"; }; script = '' - # Wait for user to exist, then set password + # Wait for user to exist, then set password from secrets 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}..." - ${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '${cfg.database.password}';" + password=$(cat ${secretsDir}/lamassu-db-password) + ${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '$password';" exit 0 fi echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..." @@ -286,12 +285,11 @@ in environment = { NODE_ENV = "production"; - # Database configuration (using TCP with password auth) + # Database configuration (password read at runtime from secrets) 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; @@ -329,8 +327,10 @@ in #!/bin/bash set -euo pipefail export PATH=${pkgs.nodejs_22}/bin:$PATH - # 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}" + # 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" export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules cd ${cfg.package} exec "$@" @@ -391,12 +391,11 @@ in CA_PATH = cfg.certPath; CERT_PATH = cfg.certPath; KEY_PATH = cfg.keyPath; - # Database configuration (using TCP with password auth) + # Database configuration (password read at runtime from secrets) 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 @@ -411,8 +410,10 @@ in #!/bin/bash set -euo pipefail export PATH=${pkgs.nodejs_22}/bin:$PATH - # 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}" + # 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" export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules cd ${cfg.package} exec "$@" @@ -444,14 +445,16 @@ 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}:${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 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="${cfg.database.password}" + export POSTGRES_PASSWORD="$DB_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 \"\$@\"" -- "$@" diff --git a/modules/lnd.nix b/modules/lnd.nix index 3482cce..38f00bc 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -150,12 +150,12 @@ let description = "The backend to use for fetching blockchain data."; }; neutrino = { - addpeers = mkOption { + peers = 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 via neutrino.addpeer. + List of Bitcoin full node peers to connect to for neutrino. Multiple peers provide redundancy for maximum uptime. ''; }; @@ -167,6 +167,13 @@ 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. + ''; + }; }; }; @@ -206,7 +213,8 @@ let bitcoind.zmqpubrawtx=${zmqHandleSpecialAddress bitcoind.zmqpubrawtx} '' else '' bitcoin.node=neutrino - ${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.addpeers} + ${lib.concatMapStringsSep "\n" (peer: "neutrino.addpeer=${peer}") cfg.neutrino.peers} + neutrino.maxpeers=${toString cfg.neutrino.maxPeers} fee.url=${cfg.neutrino.feeUrl} ''} @@ -232,10 +240,10 @@ in { services.lnd.port to a port other than 9735. ''; } - { assertion = cfg.backend != "neutrino" || cfg.neutrino.addpeers != []; + { assertion = cfg.backend != "neutrino" || cfg.neutrino.peers != []; message = '' When using neutrino backend, you must configure at least one peer - in services.lnd.neutrino.addpeers. + in services.lnd.neutrino.peers. ''; } ]; @@ -261,8 +269,8 @@ in { systemd.services.lnd = { wantedBy = [ "multi-user.target" ]; - requires = optional (cfg.backend == "bitcoind") "bitcoind.service"; - after = optional (cfg.backend == "bitcoind") "bitcoind.service" ++ [ "nix-bitcoin-secrets.target" ]; + requires = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ]; + after = optionals (cfg.backend == "bitcoind") [ "bitcoind.service" ] ++ [ "nix-bitcoin-secrets.target" ]; preStart = '' install -m600 ${configFile} '${cfg.dataDir}/lnd.conf' ${optionalString (cfg.backend == "bitcoind") '' @@ -324,7 +332,7 @@ in { users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; - extraGroups = optional (cfg.backend == "bitcoind") "bitcoinrpc-public"; + extraGroups = optionals (cfg.backend == "bitcoind") [ "bitcoinrpc-public" ]; home = cfg.dataDir; # lnd creates .lnd dir in HOME }; users.groups.${cfg.group} = {}; diff --git a/modules/modules.nix b/modules/modules.nix index 1b3c204..1759a1b 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -28,6 +28,7 @@ ./joinmarket.nix ./joinmarket-ob-watcher.nix ./hardware-wallets.nix + ./lamassu-lnbits.nix # Support features ./versioning.nix diff --git a/modules/rtl.nix b/modules/rtl.nix index 60a3bca..9d7df66 100644 --- a/modules/rtl.nix +++ b/modules/rtl.nix @@ -11,7 +11,7 @@ let }; port = mkOption { type = types.port; - default = 3000; + default = 3001; description = "HTTP server port."; }; dataDir = mkOption {