From 3af1b58eb4c007abc34a12fa923ebc6827b10fc6 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:29:09 +0300 Subject: [PATCH] Add comprehensive production security for RQLite clustering Production Security Features: - RQLite authentication with secure user management - Firewall configuration with IP-based restrictions - Automated credential generation and storage - Authenticated cluster join addresses - Credential masking in logs for security - Helper scripts for secure RQLite connections Network Architecture: - Port 4000: Public LibP2P P2P (encrypted) - Port 4001/4002: RQLite cluster (IP-restricted to cluster members) - UFW firewall rules restricting RQLite access to cluster IPs only Security Components: - /opt/debros/configs/rqlite-users.json: User authentication - /opt/debros/keys/rqlite-cluster-auth: Secure credential storage - Automatic credential masking in logs - Production-ready setup script This implements enterprise-grade security for public network deployment while maintaining seamless cluster communication between trusted nodes. --- pkg/database/rqlite.go | 111 +++++++++++++++++++-- scripts/setup-production-security.sh | 140 +++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 7 deletions(-) create mode 100755 scripts/setup-production-security.sh diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index f806eb5..fc32963 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -55,6 +55,7 @@ func (r *RQLiteManager) Start(ctx context.Context) error { args := []string{ "-http-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLitePort), "-raft-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLiteRaftPort), + "-auth", "/opt/debros/configs/rqlite-users.json", // Enable authentication } // Add advertised addresses if we have an external IP @@ -65,23 +66,33 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // Add join address if specified (for non-bootstrap or secondary bootstrap nodes) if r.config.RQLiteJoinAddress != "" { - r.logger.Info("Joining RQLite cluster", zap.String("join_address", r.config.RQLiteJoinAddress)) + r.logger.Info("Joining RQLite cluster", zap.String("join_address", r.maskCredentials(r.config.RQLiteJoinAddress))) + + // Check for authenticated join address with credentials + joinAddress := r.config.RQLiteJoinAddress + if !strings.Contains(joinAddress, "@") { + // Try to load authentication credentials for cluster joining + if authAddr := r.loadAuthenticatedJoinAddress(); authAddr != "" { + joinAddress = authAddr + r.logger.Info("Using authenticated cluster join address") + } + } // Validate join address format before using it - if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") { + if strings.HasPrefix(joinAddress, "http://") { // Test connectivity and log the results, but always attempt to join - if err := r.testJoinAddress(r.config.RQLiteJoinAddress); err != nil { + if err := r.testJoinAddress(r.stripCredentials(joinAddress)); err != nil { r.logger.Warn("Join address connectivity test failed, but will still attempt to join", - zap.String("join_address", r.config.RQLiteJoinAddress), + zap.String("join_address", r.maskCredentials(joinAddress)), zap.Error(err)) } else { r.logger.Info("Join address is reachable, proceeding with cluster join") } // Always add the join parameter - let RQLite handle retries - args = append(args, "-join", r.config.RQLiteJoinAddress) + args = append(args, "-join", joinAddress) } else { - r.logger.Warn("Invalid join address format, skipping join", zap.String("address", r.config.RQLiteJoinAddress)) - return fmt.Errorf("invalid RQLite join address format: %s (must start with http://)", r.config.RQLiteJoinAddress) + r.logger.Warn("Invalid join address format, skipping join", zap.String("address", r.maskCredentials(joinAddress))) + return fmt.Errorf("invalid RQLite join address format: %s (must start with http://)", r.maskCredentials(joinAddress)) } } else { r.logger.Info("No join address specified - starting as new cluster") @@ -321,3 +332,89 @@ func (r *RQLiteManager) testJoinAddress(joinAddress string) error { r.logger.Info("Join address is reachable", zap.String("address", joinAddress)) return nil } + +// loadAuthenticatedJoinAddress loads authentication credentials and creates authenticated join URL +func (r *RQLiteManager) loadAuthenticatedJoinAddress() string { + // Try to load authentication credentials from environment file + if data, err := os.ReadFile("/opt/debros/configs/rqlite-env"); err == nil { + lines := strings.Split(string(data), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") { + authAddr := strings.TrimPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") + authAddr = strings.Trim(authAddr, `"`) + if authAddr != "" { + return authAddr + } + } + } + } + + // Fallback: try to construct from separate user/pass environment variables + if data, err := os.ReadFile("/opt/debros/keys/rqlite-cluster-auth"); err == nil { + lines := strings.Split(string(data), "\n") + var user, pass string + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "RQLITE_CLUSTER_USER=") { + user = strings.TrimPrefix(line, "RQLITE_CLUSTER_USER=") + user = strings.Trim(user, `"`) + } else if strings.HasPrefix(line, "RQLITE_CLUSTER_PASS=") { + pass = strings.TrimPrefix(line, "RQLITE_CLUSTER_PASS=") + pass = strings.Trim(pass, `"`) + } + } + + if user != "" && pass != "" && r.config.RQLiteJoinAddress != "" { + // Extract base URL and add credentials + baseURL := r.config.RQLiteJoinAddress + if strings.HasPrefix(baseURL, "http://") { + // Insert credentials: http://user:pass@host:port + host := strings.TrimPrefix(baseURL, "http://") + return fmt.Sprintf("http://%s:%s@%s", user, pass, host) + } + } + } + + return "" // No credentials found +} + +// maskCredentials masks authentication credentials in URLs for logging +func (r *RQLiteManager) maskCredentials(url string) string { + if strings.Contains(url, "@") { + // URL contains credentials: http://user:pass@host:port + parts := strings.SplitN(url, "@", 2) + if len(parts) == 2 { + protocolAndCreds := parts[0] + hostAndPort := parts[1] + + // Extract protocol + protocolParts := strings.SplitN(protocolAndCreds, "://", 2) + if len(protocolParts) == 2 { + protocol := protocolParts[0] + return fmt.Sprintf("%s://***:***@%s", protocol, hostAndPort) + } + } + } + return url +} + +// stripCredentials removes authentication credentials from URLs for connectivity testing +func (r *RQLiteManager) stripCredentials(url string) string { + if strings.Contains(url, "@") { + // URL contains credentials: http://user:pass@host:port + parts := strings.SplitN(url, "@", 2) + if len(parts) == 2 { + protocolAndCreds := parts[0] + hostAndPort := parts[1] + + // Extract protocol + protocolParts := strings.SplitN(protocolAndCreds, "://", 2) + if len(protocolParts) == 2 { + protocol := protocolParts[0] + return fmt.Sprintf("%s://%s", protocol, hostAndPort) + } + } + } + return url +} diff --git a/scripts/setup-production-security.sh b/scripts/setup-production-security.sh new file mode 100755 index 0000000..9d2b4fa --- /dev/null +++ b/scripts/setup-production-security.sh @@ -0,0 +1,140 @@ +#!/bin/bash +set -euo pipefail + +# DeBros Network Production Security Setup +# This script configures secure RQLite clustering with authentication + +DEBROS_DIR="/opt/debros" +CONFIG_DIR="$DEBROS_DIR/configs" +KEYS_DIR="$DEBROS_DIR/keys" + +echo "🔐 Setting up DeBros Network Production Security..." + +# Create security directories +sudo mkdir -p "$CONFIG_DIR" "$KEYS_DIR" +sudo chown debros:debros "$CONFIG_DIR" "$KEYS_DIR" +sudo chmod 750 "$KEYS_DIR" + +# Generate cluster authentication credentials +CLUSTER_USER="debros_cluster" +CLUSTER_PASS=$(openssl rand -base64 32) +API_USER="debros_api" +API_PASS=$(openssl rand -base64 32) + +echo "🔑 Generated cluster credentials:" +echo " Cluster User: $CLUSTER_USER" +echo " API User: $API_USER" + +# Create RQLite users configuration +cat > "$CONFIG_DIR/rqlite-users.json" << EOF +[ + { + "username": "$CLUSTER_USER", + "password": "$CLUSTER_PASS", + "perms": ["*"] + }, + { + "username": "$API_USER", + "password": "$API_PASS", + "perms": ["status", "ready", "nodes", "db:*"] + } +] +EOF + +sudo chown debros:debros "$CONFIG_DIR/rqlite-users.json" +sudo chmod 600 "$CONFIG_DIR/rqlite-users.json" + +# Store credentials securely +cat > "$KEYS_DIR/rqlite-cluster-auth" << EOF +RQLITE_CLUSTER_USER="$CLUSTER_USER" +RQLITE_CLUSTER_PASS="$CLUSTER_PASS" +RQLITE_API_USER="$API_USER" +RQLITE_API_PASS="$API_PASS" +EOF + +sudo chown debros:debros "$KEYS_DIR/rqlite-cluster-auth" +sudo chmod 600 "$KEYS_DIR/rqlite-cluster-auth" + +# Configure firewall for production +echo "🛡️ Configuring production firewall rules..." + +# Reset UFW to defaults +sudo ufw --force reset + +# Default policies +sudo ufw default deny incoming +sudo ufw default allow outgoing + +# SSH (adjust port as needed) +sudo ufw allow 22/tcp comment "SSH" + +# LibP2P P2P networking (public, encrypted) +sudo ufw allow 4000/tcp comment "LibP2P P2P" +sudo ufw allow 4000/udp comment "LibP2P QUIC" + +# RQLite ports (restrict to cluster IPs only) +BOOTSTRAP_IPS=("57.129.81.31" "38.242.250.186") +for ip in "${BOOTSTRAP_IPS[@]}"; do + sudo ufw allow from "$ip" to any port 4001 comment "RQLite HTTP from $ip" + sudo ufw allow from "$ip" to any port 4002 comment "RQLite Raft from $ip" +done + +# Enable firewall +sudo ufw --force enable + +echo "🔧 Configuring RQLite cluster authentication..." + +# Update RQLite join addresses with authentication +AUTHENTICATED_JOIN_ADDRESS="http://$CLUSTER_USER:$CLUSTER_PASS@57.129.81.31:4001" + +# Create environment file for authenticated connections +cat > "$CONFIG_DIR/rqlite-env" << EOF +# RQLite cluster authentication +RQLITE_JOIN_AUTH_USER="$CLUSTER_USER" +RQLITE_JOIN_AUTH_PASS="$CLUSTER_PASS" +RQLITE_JOIN_ADDRESS_AUTH="$AUTHENTICATED_JOIN_ADDRESS" +EOF + +sudo chown debros:debros "$CONFIG_DIR/rqlite-env" +sudo chmod 600 "$CONFIG_DIR/rqlite-env" + +# Create connection helper script +cat > "$DEBROS_DIR/bin/rqlite-connect" << 'EOF' +#!/bin/bash +# Helper script for authenticated RQLite connections + +source /opt/debros/keys/rqlite-cluster-auth + +if [ "$1" = "cluster" ]; then + rqlite -H localhost -p 4001 -u "$RQLITE_CLUSTER_USER" -p "$RQLITE_CLUSTER_PASS" +elif [ "$1" = "api" ]; then + rqlite -H localhost -p 4001 -u "$RQLITE_API_USER" -p "$RQLITE_API_PASS" +else + echo "Usage: $0 {cluster|api}" + exit 1 +fi +EOF + +sudo chown debros:debros "$DEBROS_DIR/bin/rqlite-connect" +sudo chmod 755 "$DEBROS_DIR/bin/rqlite-connect" + +echo "✅ Production security setup complete!" +echo "" +echo "📋 Security Summary:" +echo " - RQLite authentication enabled" +echo " - Firewall configured with IP restrictions" +echo " - Cluster credentials generated and stored" +echo " - Port 4000: Public LibP2P (encrypted P2P)" +echo " - Port 4001/4002: RQLite cluster (IP-restricted)" +echo "" +echo "🔐 Credentials stored in:" +echo " - Users: $CONFIG_DIR/rqlite-users.json" +echo " - Auth: $KEYS_DIR/rqlite-cluster-auth" +echo "" +echo "🔌 Connect to RQLite:" +echo " - Cluster admin: $DEBROS_DIR/bin/rqlite-connect cluster" +echo " - API access: $DEBROS_DIR/bin/rqlite-connect api" +echo "" +echo "⚠️ IMPORTANT: Save these credentials securely!" +echo " Cluster User: $CLUSTER_USER" +echo " Cluster Pass: $CLUSTER_PASS"