Add SSH tunnel support to Lamassu configuration: update database schema, models, and UI components to include SSH settings. Implement SSH tunnel setup and teardown in transaction processing for enhanced security.

This commit is contained in:
padreug 2025-06-18 13:40:30 +02:00
parent 1f7999a556
commit 8f046ad0c5
6 changed files with 287 additions and 14 deletions

View file

@ -5,6 +5,17 @@ import asyncpg
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any
from loguru import logger
import socket
import threading
import time
try:
import paramiko
from sshtunnel import SSHTunnelForwarder
SSH_AVAILABLE = True
except ImportError:
SSH_AVAILABLE = False
logger.warning("SSH tunnel support not available. Install paramiko and sshtunnel: pip install paramiko sshtunnel")
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.settings import settings
@ -26,6 +37,7 @@ class LamassuTransactionProcessor:
def __init__(self):
self.last_check_time = None
self.processed_transaction_ids = set()
self.ssh_tunnel = None
async def get_db_config(self) -> Optional[Dict[str, Any]]:
"""Get database configuration from the database"""
@ -41,12 +53,78 @@ class LamassuTransactionProcessor:
"database": config.database_name,
"user": config.username,
"password": config.password,
"config_id": config.id
"config_id": config.id,
"use_ssh_tunnel": config.use_ssh_tunnel,
"ssh_host": config.ssh_host,
"ssh_port": config.ssh_port,
"ssh_username": config.ssh_username,
"ssh_password": config.ssh_password,
"ssh_private_key": config.ssh_private_key
}
except Exception as e:
logger.error(f"Error getting database configuration: {e}")
return None
def setup_ssh_tunnel(self, db_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Setup SSH tunnel if required and return modified connection config"""
if not db_config.get("use_ssh_tunnel"):
return db_config
if not SSH_AVAILABLE:
logger.error("SSH tunnel requested but paramiko/sshtunnel not available")
return None
try:
# Close existing tunnel if any
self.close_ssh_tunnel()
ssh_config = {
"ssh_address_or_host": (db_config["ssh_host"], db_config["ssh_port"]),
"remote_bind_address": (db_config["host"], db_config["port"]),
"ssh_username": db_config["ssh_username"],
"local_bind_address": ("127.0.0.1",) # Let sshtunnel choose local port
}
# Add authentication method
if db_config.get("ssh_private_key"):
# Use private key authentication
ssh_config["ssh_pkey"] = db_config["ssh_private_key"]
elif db_config.get("ssh_password"):
# Use password authentication
ssh_config["ssh_password"] = db_config["ssh_password"]
else:
logger.error("SSH tunnel requires either private key or password")
return None
self.ssh_tunnel = SSHTunnelForwarder(**ssh_config)
self.ssh_tunnel.start()
local_port = self.ssh_tunnel.local_bind_port
logger.info(f"SSH tunnel established: localhost:{local_port} -> {db_config['ssh_host']}:{db_config['ssh_port']} -> {db_config['host']}:{db_config['port']}")
# Return modified config to connect through tunnel
tunnel_config = db_config.copy()
tunnel_config["host"] = "127.0.0.1"
tunnel_config["port"] = local_port
return tunnel_config
except Exception as e:
logger.error(f"Failed to setup SSH tunnel: {e}")
self.close_ssh_tunnel()
return None
def close_ssh_tunnel(self):
"""Close SSH tunnel if active"""
if self.ssh_tunnel:
try:
self.ssh_tunnel.stop()
logger.info("SSH tunnel closed")
except Exception as e:
logger.warning(f"Error closing SSH tunnel: {e}")
finally:
self.ssh_tunnel = None
async def connect_to_lamassu_db(self) -> Optional[asyncpg.Connection]:
"""Establish connection to Lamassu Postgres database"""
try:
@ -54,12 +132,17 @@ class LamassuTransactionProcessor:
if not db_config:
return None
# Setup SSH tunnel if required
connection_config = self.setup_ssh_tunnel(db_config)
if not connection_config:
return None
connection = await asyncpg.connect(
host=db_config["host"],
port=db_config["port"],
database=db_config["database"],
user=db_config["user"],
password=db_config["password"],
host=connection_config["host"],
port=connection_config["port"],
database=connection_config["database"],
user=connection_config["user"],
password=connection_config["password"],
timeout=30
)
logger.info("Successfully connected to Lamassu database")
@ -286,9 +369,13 @@ class LamassuTransactionProcessor:
finally:
await connection.close()
# Close SSH tunnel if it was used
self.close_ssh_tunnel()
except Exception as e:
logger.error(f"Error in polling cycle: {e}")
# Ensure cleanup on error
self.close_ssh_tunnel()
# Global processor instance