#!/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 $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 [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 Test SSH key connectivity ${YELLOW}SSL Certificate Management:${NC} generate-ssl [days] Generate self-signed SSL cert validate-ssl Validate SSL certificate ${YELLOW}File Security:${NC} encrypt [output] [password] Encrypt file with AES-256 decrypt [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