Files
michaelschiemer/deployment/lib/security-tools.sh
Michael Schiemer 9b74ade5b0 feat: Fix discovery system critical issues
Resolved multiple critical discovery system issues:

## Discovery System Fixes
- Fixed console commands not being discovered on first run
- Implemented fallback discovery for empty caches
- Added context-aware caching with separate cache keys
- Fixed object serialization preventing __PHP_Incomplete_Class

## Cache System Improvements
- Smart caching that only caches meaningful results
- Separate caches for different execution contexts (console, web, test)
- Proper array serialization/deserialization for cache compatibility
- Cache hit logging for debugging and monitoring

## Object Serialization Fixes
- Fixed DiscoveredAttribute serialization with proper string conversion
- Sanitized additional data to prevent object reference issues
- Added fallback for corrupted cache entries

## Performance & Reliability
- All 69 console commands properly discovered and cached
- 534 total discovery items successfully cached and restored
- No more __PHP_Incomplete_Class cache corruption
- Improved error handling and graceful fallbacks

## Testing & Quality
- Fixed code style issues across discovery components
- Enhanced logging for better debugging capabilities
- Improved cache validation and error recovery

Ready for production deployment with stable discovery system.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 12:04:17 +02:00

649 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Security Tools for Custom PHP Framework Deployment
# Password generation, credential management, and security validation
# Domain: michaelschiemer.de | Email: kontakt@michaelschiemer.de | PHP: 8.4
set -euo pipefail
# Security tools constants
DEPLOYMENT_DIR="${DEPLOYMENT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)}"
SECURITY_DIR="${DEPLOYMENT_DIR}/.security"
KEYSTORE_DIR="${SECURITY_DIR}/keystore"
AUDIT_LOG="${SECURITY_DIR}/audit.log"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m'
# Logging
log() { echo -e "${GREEN}[SECURITY] ✅ $1${NC}"; }
warn() { echo -e "${YELLOW}[SECURITY] ⚠️ $1${NC}"; }
error() { echo -e "${RED}[SECURITY] ❌ $1${NC}"; }
info() { echo -e "${BLUE}[SECURITY] $1${NC}"; }
debug() { echo -e "${CYAN}[SECURITY] 🔍 $1${NC}"; }
# Security audit logging
audit_log() {
local message="$1"
local user="${USER:-unknown}"
local timestamp=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
echo "[$timestamp] $user: $message" >> "$AUDIT_LOG"
}
# Initialize security directories
init_security_dirs() {
mkdir -p "$SECURITY_DIR" "$KEYSTORE_DIR"
chmod 700 "$SECURITY_DIR" "$KEYSTORE_DIR"
# Create audit log if it doesn't exist
touch "$AUDIT_LOG"
chmod 600 "$AUDIT_LOG"
audit_log "Security tools initialized"
}
# Generate cryptographically secure password
generate_secure_password() {
local length=${1:-32}
local charset=${2:-"mixed"}
local exclude_ambiguous=${3:-true}
local chars=""
case $charset in
"alphanumeric")
chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
;;
"alpha")
chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
;;
"numeric")
chars="0123456789"
;;
"mixed"|"strong")
chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?"
;;
"base64")
# Use openssl for base64 passwords
openssl rand -base64 "$length"
return
;;
"hex")
# Use openssl for hex passwords
openssl rand -hex "$length"
return
;;
*)
error "Unknown charset: $charset"
return 1
;;
esac
# Remove ambiguous characters if requested
if [[ "$exclude_ambiguous" == "true" ]]; then
chars=$(echo "$chars" | tr -d "0O1lI")
fi
# Generate password using /dev/urandom
local password=""
for ((i=0; i<length; i++)); do
local random_byte
random_byte=$(od -An -N1 -tu1 /dev/urandom | tr -d ' ')
local char_index=$((random_byte % ${#chars}))
password+="${chars:$char_index:1}"
done
echo "$password"
audit_log "Password generated (length: $length, charset: $charset)"
}
# Check password strength
check_password_strength() {
local password="$1"
local min_length=${2:-12}
local score=0
local feedback=()
# Length check
if [[ ${#password} -ge $min_length ]]; then
score=$((score + 20))
else
feedback+=("Password too short (minimum $min_length characters)")
fi
# Character variety checks
if [[ $password =~ [a-z] ]]; then
score=$((score + 10))
else
feedback+=("Add lowercase letters")
fi
if [[ $password =~ [A-Z] ]]; then
score=$((score + 10))
else
feedback+=("Add uppercase letters")
fi
if [[ $password =~ [0-9] ]]; then
score=$((score + 10))
else
feedback+=("Add numbers")
fi
if [[ $password =~ [^a-zA-Z0-9] ]]; then
score=$((score + 15))
else
feedback+=("Add special characters")
fi
# Additional strength checks
if [[ ${#password} -ge 20 ]]; then
score=$((score + 10))
fi
# Penalize common patterns
if [[ $password =~ 123456|password|qwerty|admin ]]; then
score=$((score - 20))
feedback+=("Contains common weak patterns")
fi
# Determine strength level
local strength="WEAK"
local color="$RED"
if [[ $score -ge 80 ]]; then
strength="VERY STRONG"
color="$GREEN"
elif [[ $score -ge 60 ]]; then
strength="STRONG"
color="$GREEN"
elif [[ $score -ge 40 ]]; then
strength="MODERATE"
color="$YELLOW"
elif [[ $score -ge 20 ]]; then
strength="WEAK"
color="$YELLOW"
else
color="$RED"
fi
echo -e "${color}Password Strength: $strength ($score/100)${NC}"
if [[ ${#feedback[@]} -gt 0 ]]; then
echo -e "${YELLOW}Recommendations:${NC}"
printf " • %s\n" "${feedback[@]}"
fi
return $((100 - score))
}
# Generate SSH key pair
generate_ssh_key() {
local key_name="$1"
local key_type=${2:-"ed25519"}
local comment=${3:-"deploy@michaelschiemer.de"}
local passphrase=${4:-""}
local key_path="${KEYSTORE_DIR}/${key_name}"
info "Generating SSH key pair: $key_name"
if [[ -f "$key_path" ]]; then
warn "SSH key already exists: $key_path"
printf "Overwrite existing key? [y/N]: "
read -r overwrite
if [[ ! $overwrite =~ ^[Yy]$ ]]; then
info "SSH key generation cancelled"
return 0
fi
fi
case $key_type in
"ed25519")
ssh-keygen -t ed25519 -C "$comment" -f "$key_path" -N "$passphrase"
;;
"rsa")
ssh-keygen -t rsa -b 4096 -C "$comment" -f "$key_path" -N "$passphrase"
;;
"ecdsa")
ssh-keygen -t ecdsa -b 521 -C "$comment" -f "$key_path" -N "$passphrase"
;;
*)
error "Unsupported key type: $key_type"
return 1
;;
esac
# Set proper permissions
chmod 600 "$key_path"
chmod 644 "${key_path}.pub"
success "SSH key pair generated:"
info "Private key: $key_path"
info "Public key: ${key_path}.pub"
# Show public key for easy copying
cat << EOF
${CYAN}📋 PUBLIC KEY (add to server's ~/.ssh/authorized_keys):${NC}
$(cat "${key_path}.pub")
EOF
audit_log "SSH key generated: $key_name ($key_type)"
}
# Test SSH key
test_ssh_key() {
local key_path="$1"
local server="$2"
local port=${3:-22}
if [[ ! -f "$key_path" ]]; then
error "SSH key not found: $key_path"
return 1
fi
info "Testing SSH key: $key_path -> $server:$port"
if ssh -i "$key_path" -p "$port" -o ConnectTimeout=10 -o BatchMode=yes \
"$server" "echo 'SSH key test successful'" 2>/dev/null; then
success "SSH key test passed"
audit_log "SSH key test successful: $server"
return 0
else
error "SSH key test failed"
audit_log "SSH key test failed: $server"
return 1
fi
}
# Generate SSL certificate (self-signed for development)
generate_ssl_cert() {
local domain="$1"
local key_path="${KEYSTORE_DIR}/${domain}.key"
local cert_path="${KEYSTORE_DIR}/${domain}.crt"
local days=${2:-365}
info "Generating self-signed SSL certificate for $domain"
# Generate private key
openssl genrsa -out "$key_path" 2048
# Generate certificate
openssl req -new -x509 -key "$key_path" -out "$cert_path" -days "$days" \
-subj "/C=DE/ST=Bavaria/L=Munich/O=Custom PHP Framework/OU=Development/CN=$domain"
# Set permissions
chmod 600 "$key_path"
chmod 644 "$cert_path"
success "SSL certificate generated:"
info "Private key: $key_path"
info "Certificate: $cert_path"
info "Valid for: $days days"
audit_log "SSL certificate generated for $domain"
}
# Validate SSL certificate
validate_ssl_cert() {
local cert_path="$1"
if [[ ! -f "$cert_path" ]]; then
error "Certificate not found: $cert_path"
return 1
fi
info "Validating SSL certificate: $(basename "$cert_path")"
# Check certificate details
local cert_info
cert_info=$(openssl x509 -in "$cert_path" -text -noout)
# Extract key information
local subject
subject=$(echo "$cert_info" | grep "Subject:" | sed 's/.*Subject: //')
local issuer
issuer=$(echo "$cert_info" | grep "Issuer:" | sed 's/.*Issuer: //')
local not_before
not_before=$(echo "$cert_info" | grep "Not Before:" | sed 's/.*Not Before: //')
local not_after
not_after=$(echo "$cert_info" | grep "Not After:" | sed 's/.*Not After: //')
echo "Subject: $subject"
echo "Issuer: $issuer"
echo "Valid from: $not_before"
echo "Valid until: $not_after"
# Check if certificate is still valid
if openssl x509 -in "$cert_path" -checkend 0 -noout >/dev/null 2>&1; then
success "Certificate is valid"
else
error "Certificate has expired"
return 1
fi
audit_log "SSL certificate validated: $(basename "$cert_path")"
}
# Secure file encryption
encrypt_file() {
local input_file="$1"
local output_file="${2:-${input_file}.enc}"
local password="$3"
if [[ ! -f "$input_file" ]]; then
error "Input file not found: $input_file"
return 1
fi
if [[ -z "$password" ]]; then
printf "Enter encryption password: "
read -rs password
echo
fi
info "Encrypting file: $(basename "$input_file")"
openssl enc -aes-256-cbc -salt -in "$input_file" -out "$output_file" -pass pass:"$password"
success "File encrypted: $output_file"
audit_log "File encrypted: $(basename "$input_file")"
}
# Secure file decryption
decrypt_file() {
local input_file="$1"
local output_file="$2"
local password="$3"
if [[ ! -f "$input_file" ]]; then
error "Encrypted file not found: $input_file"
return 1
fi
if [[ -z "$password" ]]; then
printf "Enter decryption password: "
read -rs password
echo
fi
info "Decrypting file: $(basename "$input_file")"
if openssl enc -aes-256-cbc -d -in "$input_file" -out "$output_file" -pass pass:"$password"; then
success "File decrypted: $output_file"
audit_log "File decrypted: $(basename "$input_file")"
else
error "Decryption failed - check password"
return 1
fi
}
# Security scan for common vulnerabilities
security_scan() {
local target_dir="${1:-$DEPLOYMENT_DIR}"
info "Running security scan on: $target_dir"
local issues_found=0
# Check for hardcoded secrets
info "Scanning for hardcoded secrets..."
local secret_patterns=(
"password.*=.*['\"][^'\"]{8,}['\"]"
"api_key.*=.*['\"][^'\"]{20,}['\"]"
"secret.*=.*['\"][^'\"]{16,}['\"]"
"token.*=.*['\"][^'\"]{20,}['\"]"
"private_key"
"BEGIN.*PRIVATE.*KEY"
)
for pattern in "${secret_patterns[@]}"; do
local matches
matches=$(grep -r -i "$pattern" "$target_dir" --exclude-dir=.git --exclude="*.log" 2>/dev/null || true)
if [[ -n "$matches" ]]; then
warn "Potential hardcoded secrets found:"
echo "$matches"
((issues_found++))
fi
done
# Check file permissions
info "Checking file permissions..."
local sensitive_files
sensitive_files=$(find "$target_dir" -name "*.env*" -o -name "*.key" -o -name "*password*" 2>/dev/null || true)
for file in $sensitive_files; do
if [[ -f "$file" ]]; then
local perms
perms=$(stat -c "%a" "$file")
if [[ "$perms" != "600" && "$perms" != "400" ]]; then
warn "Sensitive file has loose permissions: $file ($perms)"
((issues_found++))
fi
fi
done
# Check for default passwords
info "Scanning for default passwords..."
local default_passwords=("password" "123456" "admin" "root" "changeme")
for password in "${default_passwords[@]}"; do
local matches
matches=$(grep -r -i "$password" "$target_dir" --include="*.env*" 2>/dev/null || true)
if [[ -n "$matches" ]]; then
warn "Default/weak password found: $password"
((issues_found++))
fi
done
# Summary
if [[ $issues_found -eq 0 ]]; then
success "Security scan completed - no issues found"
else
warn "Security scan found $issues_found potential issues"
fi
audit_log "Security scan completed on $target_dir ($issues_found issues)"
return $issues_found
}
# Generate security report
generate_security_report() {
local environment=${1:-"production"}
local report_file="${SECURITY_DIR}/security_report_${environment}_$(date +%Y%m%d_%H%M%S).txt"
info "Generating security report for $environment environment..."
cat > "$report_file" << EOF
# Security Report: Custom PHP Framework
Environment: $environment
Generated: $(date)
Server: ${SERVER_IP:-"N/A"}
Domain: ${DOMAIN:-"N/A"}
## Security Status Overview
EOF
# Password strength analysis
echo "## Password Strength Analysis" >> "$report_file"
local env_file="${DEPLOYMENT_DIR}/applications/environments/.env.${environment}"
if [[ -f "$env_file" ]]; then
local db_password
db_password=$(grep "^DB_PASSWORD=" "$env_file" | cut -d'=' -f2 | tr -d '"' || echo "")
if [[ -n "$db_password" ]]; then
echo "Database password strength:" >> "$report_file"
check_password_strength "$db_password" 16 >> "$report_file" 2>&1
fi
fi
# SSH key status
echo -e "\n## SSH Key Status" >> "$report_file"
if [[ -f "${KEYSTORE_DIR}/production" ]]; then
echo "Production SSH key: ✅ Present" >> "$report_file"
local key_type
key_type=$(ssh-keygen -l -f "${KEYSTORE_DIR}/production.pub" | awk '{print $4}' 2>/dev/null || echo "unknown")
echo "Key type: $key_type" >> "$report_file"
else
echo "Production SSH key: ❌ Missing" >> "$report_file"
fi
# Security scan results
echo -e "\n## Security Scan Results" >> "$report_file"
security_scan "$DEPLOYMENT_DIR" >> "$report_file" 2>&1
# Configuration security
echo -e "\n## Configuration Security" >> "$report_file"
if [[ -f "$env_file" ]]; then
local debug_mode
debug_mode=$(grep "^APP_DEBUG=" "$env_file" | cut -d'=' -f2 | tr -d '"' || echo "unknown")
echo "Debug mode: $debug_mode" >> "$report_file"
local ssl_enabled
ssl_enabled=$(grep "^SSL_ENABLED=" "$env_file" | cut -d'=' -f2 | tr -d '"' || echo "unknown")
echo "SSL enabled: $ssl_enabled" >> "$report_file"
fi
# Recent security events
echo -e "\n## Recent Security Events" >> "$report_file"
if [[ -f "$AUDIT_LOG" ]]; then
tail -n 20 "$AUDIT_LOG" >> "$report_file"
else
echo "No audit log found" >> "$report_file"
fi
success "Security report generated: $report_file"
audit_log "Security report generated for $environment"
}
# Clean up old security files
cleanup_security() {
local days=${1:-30}
info "Cleaning up security files older than $days days..."
# Clean up old reports
find "$SECURITY_DIR" -name "security_report_*.txt" -mtime +$days -delete 2>/dev/null || true
# Clean up old backups
find "$SECURITY_DIR" -name "*.backup.*" -mtime +$days -delete 2>/dev/null || true
# Rotate audit log if it's too large (>10MB)
if [[ -f "$AUDIT_LOG" && $(stat -f%z "$AUDIT_LOG" 2>/dev/null || stat -c%s "$AUDIT_LOG") -gt 10485760 ]]; then
local rotated_log="${AUDIT_LOG}.$(date +%Y%m%d_%H%M%S)"
mv "$AUDIT_LOG" "$rotated_log"
touch "$AUDIT_LOG"
chmod 600 "$AUDIT_LOG"
info "Audit log rotated: $(basename "$rotated_log")"
fi
success "Security cleanup completed"
audit_log "Security cleanup performed (${days} day retention)"
}
# Command-line interface
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
# Initialize
init_security_dirs
case "${1:-help}" in
"generate-password")
length=${2:-32}
charset=${3:-"mixed"}
password=$(generate_secure_password "$length" "$charset")
echo "$password"
;;
"check-password")
if [[ -z "${2:-}" ]]; then
printf "Enter password to check: "
read -rs password
echo
else
password="$2"
fi
check_password_strength "$password"
;;
"generate-ssh")
generate_ssh_key "${2:-production}" "${3:-ed25519}"
;;
"test-ssh")
test_ssh_key "${2:-${KEYSTORE_DIR}/production}" "$3"
;;
"generate-ssl")
generate_ssl_cert "${2:-localhost}" "${3:-365}"
;;
"validate-ssl")
validate_ssl_cert "$2"
;;
"encrypt")
encrypt_file "$2" "$3" "${4:-}"
;;
"decrypt")
decrypt_file "$2" "$3" "${4:-}"
;;
"scan")
security_scan "${2:-$DEPLOYMENT_DIR}"
;;
"report")
generate_security_report "${2:-production}"
;;
"cleanup")
cleanup_security "${2:-30}"
;;
"help"|*)
cat << EOF
${PURPLE}Security Tools for Custom PHP Framework${NC}
${YELLOW}Usage:${NC} $0 <command> [options]
${YELLOW}Password Management:${NC}
generate-password [length] [charset] Generate secure password
check-password [password] Check password strength
${YELLOW}SSH Key Management:${NC}
generate-ssh [name] [type] Generate SSH key pair
test-ssh <key-path> <server> Test SSH key connectivity
${YELLOW}SSL Certificate Management:${NC}
generate-ssl <domain> [days] Generate self-signed SSL cert
validate-ssl <cert-path> Validate SSL certificate
${YELLOW}File Security:${NC}
encrypt <file> [output] [password] Encrypt file with AES-256
decrypt <encrypted-file> <output> [password] Decrypt file
${YELLOW}Security Auditing:${NC}
scan [directory] Run security vulnerability scan
report [environment] Generate security report
cleanup [days] Clean up old security files
${YELLOW}Examples:${NC}
$0 generate-password 32 mixed # Strong 32-char password
$0 check-password mypassword123 # Check password strength
$0 generate-ssh production ed25519 # Generate ED25519 SSH key
$0 test-ssh ~/.ssh/production user@server # Test SSH connectivity
$0 scan /path/to/deployment # Security scan
$0 report production # Security report
${YELLOW}Charset Options:${NC}
alphanumeric, alpha, numeric, mixed, strong, base64, hex
EOF
;;
esac
fi