Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
274 lines
9.1 KiB
Bash
Executable File
274 lines
9.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# GT AI OS Secret Generation Library
|
|
# Centralized, idempotent secret generation for deployment scripts
|
|
#
|
|
# Usage: source scripts/lib/secrets.sh
|
|
# generate_all_secrets # Populates .env with missing secrets only
|
|
|
|
set -e
|
|
|
|
# Source common functions if available
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
if [ -f "$SCRIPT_DIR/common.sh" ]; then
|
|
source "$SCRIPT_DIR/common.sh"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# SECRET GENERATION FUNCTIONS
|
|
# =============================================================================
|
|
|
|
# Generate a random hex string (for JWT secrets, encryption keys)
|
|
# Usage: generate_secret_hex [length]
|
|
# Default length: 64 characters (32 bytes)
|
|
generate_secret_hex() {
|
|
local length=${1:-64}
|
|
openssl rand -hex $((length / 2))
|
|
}
|
|
|
|
# Generate a Fernet key (for TFA encryption, API key encryption)
|
|
# Fernet requires base64-encoded 32-byte key
|
|
generate_fernet_key() {
|
|
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" 2>/dev/null || \
|
|
openssl rand -base64 32
|
|
}
|
|
|
|
# Generate a secure password (for database passwords)
|
|
# Usage: generate_password [length]
|
|
# Default length: 32 characters
|
|
generate_password() {
|
|
local length=${1:-32}
|
|
# Use alphanumeric + special chars, avoiding problematic shell chars
|
|
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+-=' | head -c "$length"
|
|
}
|
|
|
|
# Generate a simple alphanumeric password (for services that don't handle special chars well)
|
|
# Usage: generate_simple_password [length]
|
|
generate_simple_password() {
|
|
local length=${1:-32}
|
|
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length"
|
|
}
|
|
|
|
# =============================================================================
|
|
# ENV FILE MANAGEMENT
|
|
# =============================================================================
|
|
|
|
# Get value from .env file
|
|
# Usage: get_env_value "KEY_NAME" ".env"
|
|
get_env_value() {
|
|
local key="$1"
|
|
local env_file="${2:-.env}"
|
|
|
|
if [ -f "$env_file" ]; then
|
|
grep "^${key}=" "$env_file" 2>/dev/null | cut -d'=' -f2- | head -1
|
|
fi
|
|
}
|
|
|
|
# Set value in .env file (preserves existing, only sets if missing or empty)
|
|
# Usage: set_env_value "KEY_NAME" "value" ".env"
|
|
set_env_value() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local env_file="${3:-.env}"
|
|
|
|
# Create file if it doesn't exist
|
|
touch "$env_file"
|
|
|
|
local existing=$(get_env_value "$key" "$env_file")
|
|
|
|
if [ -z "$existing" ]; then
|
|
# Key doesn't exist or is empty, add/update it
|
|
if grep -q "^${key}=" "$env_file" 2>/dev/null; then
|
|
# Key exists but is empty, update it
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
sed -i '' "s|^${key}=.*|${key}=${value}|" "$env_file"
|
|
else
|
|
sed -i "s|^${key}=.*|${key}=${value}|" "$env_file"
|
|
fi
|
|
else
|
|
# Key doesn't exist, append it
|
|
echo "${key}=${value}" >> "$env_file"
|
|
fi
|
|
return 0 # Secret was generated
|
|
fi
|
|
return 1 # Secret already exists
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN SECRET GENERATION
|
|
# =============================================================================
|
|
|
|
# Generate all required secrets for GT AI OS
|
|
# This function is IDEMPOTENT - it only generates missing secrets
|
|
# Usage: generate_all_secrets [env_file]
|
|
generate_all_secrets() {
|
|
local env_file="${1:-.env}"
|
|
local generated_count=0
|
|
|
|
echo "Checking and generating missing secrets..."
|
|
|
|
# JWT and Authentication Secrets
|
|
if set_env_value "JWT_SECRET" "$(generate_secret_hex 64)" "$env_file"; then
|
|
echo " Generated: JWT_SECRET"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if set_env_value "CONTROL_PANEL_JWT_SECRET" "$(generate_secret_hex 64)" "$env_file"; then
|
|
echo " Generated: CONTROL_PANEL_JWT_SECRET"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if set_env_value "RESOURCE_CLUSTER_SECRET_KEY" "$(generate_secret_hex 64)" "$env_file"; then
|
|
echo " Generated: RESOURCE_CLUSTER_SECRET_KEY"
|
|
((++generated_count))
|
|
fi
|
|
|
|
# Encryption Keys
|
|
if set_env_value "TFA_ENCRYPTION_KEY" "$(generate_fernet_key)" "$env_file"; then
|
|
echo " Generated: TFA_ENCRYPTION_KEY"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if set_env_value "API_KEY_ENCRYPTION_KEY" "$(generate_fernet_key)" "$env_file"; then
|
|
echo " Generated: API_KEY_ENCRYPTION_KEY"
|
|
((++generated_count))
|
|
fi
|
|
|
|
# Database Passwords (use simple passwords for PostgreSQL compatibility)
|
|
if set_env_value "ADMIN_POSTGRES_PASSWORD" "$(generate_simple_password 32)" "$env_file"; then
|
|
echo " Generated: ADMIN_POSTGRES_PASSWORD"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if set_env_value "TENANT_POSTGRES_PASSWORD" "$(generate_simple_password 32)" "$env_file"; then
|
|
echo " Generated: TENANT_POSTGRES_PASSWORD"
|
|
((++generated_count))
|
|
fi
|
|
|
|
# Sync TENANT_USER_PASSWORD with TENANT_POSTGRES_PASSWORD
|
|
local tenant_pass=$(get_env_value "TENANT_POSTGRES_PASSWORD" "$env_file")
|
|
if set_env_value "TENANT_USER_PASSWORD" "$tenant_pass" "$env_file"; then
|
|
echo " Set: TENANT_USER_PASSWORD (synced with TENANT_POSTGRES_PASSWORD)"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if set_env_value "TENANT_REPLICATOR_PASSWORD" "$(generate_simple_password 32)" "$env_file"; then
|
|
echo " Generated: TENANT_REPLICATOR_PASSWORD"
|
|
((++generated_count))
|
|
fi
|
|
|
|
# Other Service Passwords
|
|
if set_env_value "RABBITMQ_PASSWORD" "$(generate_simple_password 24)" "$env_file"; then
|
|
echo " Generated: RABBITMQ_PASSWORD"
|
|
((++generated_count))
|
|
fi
|
|
|
|
if [ $generated_count -eq 0 ]; then
|
|
echo " All secrets already present (no changes needed)"
|
|
else
|
|
echo " Generated $generated_count new secret(s)"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Validate that all required secrets are present (non-empty)
|
|
# Usage: validate_secrets [env_file]
|
|
validate_secrets() {
|
|
local env_file="${1:-.env}"
|
|
local missing=0
|
|
|
|
local required_secrets=(
|
|
"JWT_SECRET"
|
|
"CONTROL_PANEL_JWT_SECRET"
|
|
"RESOURCE_CLUSTER_SECRET_KEY"
|
|
"TFA_ENCRYPTION_KEY"
|
|
"API_KEY_ENCRYPTION_KEY"
|
|
"ADMIN_POSTGRES_PASSWORD"
|
|
"TENANT_POSTGRES_PASSWORD"
|
|
"TENANT_USER_PASSWORD"
|
|
"RABBITMQ_PASSWORD"
|
|
)
|
|
|
|
echo "Validating required secrets..."
|
|
|
|
for secret in "${required_secrets[@]}"; do
|
|
local value=$(get_env_value "$secret" "$env_file")
|
|
if [ -z "$value" ]; then
|
|
echo " MISSING: $secret"
|
|
((missing++))
|
|
fi
|
|
done
|
|
|
|
if [ $missing -gt 0 ]; then
|
|
echo " $missing required secret(s) missing!"
|
|
return 1
|
|
fi
|
|
|
|
echo " All required secrets present"
|
|
return 0
|
|
}
|
|
|
|
# =============================================================================
|
|
# TEMPLATE CREATION
|
|
# =============================================================================
|
|
|
|
# Create a .env.template file with placeholder values
|
|
# Usage: create_env_template [output_file]
|
|
create_env_template() {
|
|
local output_file="${1:-.env.template}"
|
|
|
|
cat > "$output_file" << 'EOF'
|
|
# GT AI OS Environment Configuration
|
|
# Copy this file to .env and customize values
|
|
# Secrets are auto-generated on first install if not provided
|
|
|
|
# =============================================================================
|
|
# AUTHENTICATION (Auto-generated if empty)
|
|
# =============================================================================
|
|
JWT_SECRET=
|
|
CONTROL_PANEL_JWT_SECRET=
|
|
RESOURCE_CLUSTER_SECRET_KEY=
|
|
|
|
# =============================================================================
|
|
# ENCRYPTION KEYS (Auto-generated if empty)
|
|
# =============================================================================
|
|
PASSWORD_RESET_ENCRYPTION_KEY=
|
|
TFA_ENCRYPTION_KEY=
|
|
API_KEY_ENCRYPTION_KEY=
|
|
|
|
# =============================================================================
|
|
# DATABASE PASSWORDS (Auto-generated if empty)
|
|
# =============================================================================
|
|
ADMIN_POSTGRES_PASSWORD=
|
|
TENANT_POSTGRES_PASSWORD=
|
|
TENANT_USER_PASSWORD=
|
|
TENANT_REPLICATOR_PASSWORD=
|
|
RABBITMQ_PASSWORD=
|
|
|
|
# =============================================================================
|
|
# API KEYS (Configure via Control Panel UI after installation)
|
|
# =============================================================================
|
|
# Note: LLM API keys (Groq, OpenAI, Anthropic) are configured through
|
|
# the Control Panel UI, not environment variables.
|
|
|
|
# =============================================================================
|
|
# SMTP (Enterprise Edition Only - Password Reset)
|
|
# =============================================================================
|
|
# Set via environment variables or configure below
|
|
# SMTP_HOST=smtp-relay.brevo.com
|
|
# SMTP_PORT=587
|
|
# SMTP_USERNAME=
|
|
# SMTP_PASSWORD=
|
|
# SMTP_FROM_EMAIL=noreply@yourdomain.com
|
|
# SMTP_FROM_NAME=GT AI OS
|
|
|
|
# =============================================================================
|
|
# DEPLOYMENT
|
|
# =============================================================================
|
|
COMPOSE_PROJECT_NAME=gentwo
|
|
ENVIRONMENT=production
|
|
EOF
|
|
|
|
echo "Created $output_file"
|
|
}
|