From 471a4d982f87592c9b8cd262c225e86acf6fbdf1 Mon Sep 17 00:00:00 2001 From: padreug Date: Tue, 23 Dec 2025 11:53:38 +0100 Subject: [PATCH] lamassu: integrate with nix-bitcoin secrets system for TLS certificates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace runtime SSL generation with nix-bitcoin secrets integration - Add certificate.extraIPs and certificate.extraDomains options (same pattern as LND) - Certificates auto-regenerate when SAN configuration changes - Add certPath and keyPath read-only options - Update nginx and services to use secrets from secretsDir - Add nix-bitcoin-secrets.target dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- modules/lamassu-lnbits.nix | 119 +++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/modules/lamassu-lnbits.nix b/modules/lamassu-lnbits.nix index 35d1d42..6eba981 100644 --- a/modules/lamassu-lnbits.nix +++ b/modules/lamassu-lnbits.nix @@ -4,24 +4,12 @@ with lib; let cfg = config.services.lamassu-server; - secretsDir = "/var/src/secrets"; # krops deploys secrets here + nbLib = config.nix-bitcoin.lib; + secretsDir = config.nix-bitcoin.secretsDir; # Path to the deployed lamassu-server (krops puts it in /var/src) lamassuServerPath = "/var/src/lamassu-server-built"; - # Detect if a string is an IP address (simple regex check) - isIpAddress = str: builtins.match "([0-9]{1,3}\\.){3}[0-9]{1,3}" str != null; - - # Build Subject Alternative Name (SAN) extension for certificate - # Uses IP: prefix for IP addresses, DNS: prefix for hostnames - sanEntries = - [ (if isIpAddress cfg.hostname then "IP:${cfg.hostname}" else "DNS:${cfg.hostname}") ] ++ - (optional (cfg.ipAddress != null) "IP:${cfg.ipAddress}") ++ - (optional (cfg.nginx.enable && cfg.nginx.hostname != null && cfg.nginx.hostname != cfg.hostname) - (if isIpAddress cfg.nginx.hostname then "IP:${cfg.nginx.hostname}" else "DNS:${cfg.nginx.hostname}")); - - sanExtension = concatStringsSep "," sanEntries; - # Basic hardening settings (simplified from nix-bitcoin) defaultHardening = { # Sandboxing @@ -123,14 +111,40 @@ in hostname = mkOption { type = types.str; default = "localhost"; - description = "Hostname for the server (used by application and included in certificate)"; + description = "Hostname for the server (used by application)"; }; - ipAddress = mkOption { - type = types.nullOr types.str; - default = null; - description = "IP address to include in certificate SAN (optional)"; - example = "192.168.1.100"; + # Certificate options (same pattern as LND) + certificate = { + extraIPs = mkOption { + type = with types; listOf str; + default = []; + example = [ "192.168.1.100" ]; + description = '' + Extra IP addresses to include in the certificate SAN. + ''; + }; + extraDomains = mkOption { + type = with types; listOf str; + default = []; + example = [ "lamassu.example.com" ]; + description = '' + Extra domain names to include in the certificate SAN. + ''; + }; + }; + + # Read-only options for certificate paths + certPath = mkOption { + readOnly = true; + default = "${secretsDir}/lamassu-cert"; + description = "Path to the TLS certificate."; + }; + + keyPath = mkOption { + readOnly = true; + default = "${secretsDir}/lamassu-key"; + description = "Path to the TLS private key."; }; nginx = { @@ -152,6 +166,24 @@ in }; config = mkIf cfg.enable { + # ═══════════════════════════════════════════════════════════════════════════ + # nix-bitcoin secrets integration + # ═══════════════════════════════════════════════════════════════════════════ + + nix-bitcoin.secrets = { + lamassu-key.user = cfg.user; + lamassu-cert = { + user = cfg.user; + permissions = "444"; # World readable (it's a public cert) + }; + }; + + nix-bitcoin.generateSecretsCmds.lamassu = '' + makeCert lamassu '${nbLib.mkCertExtraAltNames cfg.certificate}' + ''; + + # ═══════════════════════════════════════════════════════════════════════════ + # Nginx reverse proxy (optional, disabled by default) services.nginx = mkIf cfg.nginx.enable { enable = true; @@ -160,8 +192,8 @@ in virtualHosts.${if cfg.nginx.hostname != null then cfg.nginx.hostname else cfg.hostname} = { forceSSL = true; - sslCertificate = "${cfg.dataDir}/ssl/cert.pem"; - sslCertificateKey = "${cfg.dataDir}/ssl/key.pem"; + sslCertificate = cfg.certPath; + sslCertificateKey = cfg.keyPath; # Route API endpoints to main server (port 3000) locations."/ca" = { @@ -253,7 +285,6 @@ in "d '${cfg.dataDir}/photos/idcards' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/photos/frontcamera' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/operator' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.dataDir}/ssl' 0770 ${cfg.user} ${cfg.group} - -" # Ensure lamassu-server user can read/write to the source directory "Z '${cfg.package}' 0755 ${cfg.user} ${cfg.group} - -" ]; @@ -289,7 +320,7 @@ in systemd.services.lamassu-server = { description = "Lamassu Bitcoin ATM Server"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" "postgresql.service" "lamassu-postgres-setup.service" ]; + after = [ "network.target" "postgresql.service" "lamassu-postgres-setup.service" "nix-bitcoin-secrets.target" ]; wants = [ "postgresql.service" "lamassu-postgres-setup.service" ]; environment = { @@ -307,10 +338,10 @@ in LOG_LEVEL = cfg.logLevel; HOSTNAME = cfg.hostname; - # SSL/TLS certificates - CA_PATH = "${cfg.dataDir}/ssl/cert.pem"; - CERT_PATH = "${cfg.dataDir}/ssl/cert.pem"; - KEY_PATH = "${cfg.dataDir}/ssl/key.pem"; + # SSL/TLS certificates (from nix-bitcoin secrets) + CA_PATH = cfg.certPath; + CERT_PATH = cfg.certPath; + KEY_PATH = cfg.keyPath; # Wallet and mnemonic MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic"; @@ -370,7 +401,6 @@ in preStart = '' mkdir -p ${cfg.dataDir}/logs - mkdir -p ${cfg.dataDir}/ssl # Wait for PostgreSQL using peer authentication timeout=30 @@ -384,31 +414,6 @@ in ((timeout--)) done echo "PostgreSQL is ready" - - # Generate self-signed SSL certificates if they don't exist - if [ ! -f ${cfg.dataDir}/ssl/key.pem ]; then - echo "Generating self-signed SSL certificates..." - echo " Hostname: ${cfg.hostname}" - ${optionalString (cfg.ipAddress != null) '' - echo " IP Address: ${cfg.ipAddress}" - ''} - ${optionalString (cfg.nginx.enable && cfg.nginx.hostname != null) '' - echo " Nginx hostname: ${cfg.nginx.hostname}" - ''} - - # Generate certificate with SAN extension - ${pkgs.openssl}/bin/openssl req -x509 -newkey rsa:4096 \ - -keyout ${cfg.dataDir}/ssl/key.pem \ - -out ${cfg.dataDir}/ssl/cert.pem \ - -days 365 -nodes \ - -subj "/CN=${cfg.hostname}" \ - -addext "subjectAltName=${sanExtension}" - - chmod 640 ${cfg.dataDir}/ssl/key.pem # Allow group read for nginx - chmod 644 ${cfg.dataDir}/ssl/cert.pem - - echo "Certificate generated with SAN: ${sanExtension}" - fi ''; }; @@ -424,9 +429,9 @@ in ADMIN_SERVER_PORT = toString cfg.adminPort; LOG_LEVEL = cfg.logLevel; HOSTNAME = cfg.hostname; - CA_PATH = "${cfg.dataDir}/ssl/cert.pem"; - CERT_PATH = "${cfg.dataDir}/ssl/cert.pem"; - KEY_PATH = "${cfg.dataDir}/ssl/key.pem"; + CA_PATH = cfg.certPath; + CERT_PATH = cfg.certPath; + KEY_PATH = cfg.keyPath; # Database configuration (using TCP with password auth) POSTGRES_HOST = "127.0.0.1"; POSTGRES_PORT = "5432";