Initial commit - Church Music Database
This commit is contained in:
169
legacy-site/scripts/security-hardening.py
Executable file
169
legacy-site/scripts/security-hardening.py
Executable file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Security Hardening Script for Church Music Database
|
||||
Implements critical security fixes
|
||||
"""
|
||||
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def generate_secure_key():
|
||||
"""Generate cryptographically secure secret key"""
|
||||
return secrets.token_hex(32)
|
||||
|
||||
def check_env_file_security(env_path):
|
||||
"""Check if .env file has secure permissions"""
|
||||
if not os.path.exists(env_path):
|
||||
return False, "File does not exist"
|
||||
|
||||
stat_info = os.stat(env_path)
|
||||
mode = stat_info.st_mode & 0o777
|
||||
|
||||
if mode != 0o600:
|
||||
return False, f"Insecure permissions: {oct(mode)}. Should be 0600"
|
||||
return True, "OK"
|
||||
|
||||
def secure_env_file(env_path):
|
||||
"""Set secure permissions on .env file"""
|
||||
try:
|
||||
os.chmod(env_path, 0o600)
|
||||
return True
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def validate_postgresql_uri(uri):
|
||||
"""Validate PostgreSQL connection string"""
|
||||
if not uri or uri == "":
|
||||
return False, "Empty URI"
|
||||
|
||||
# Check for default/weak passwords
|
||||
weak_patterns = [
|
||||
'your_password',
|
||||
'password',
|
||||
'admin',
|
||||
'123456',
|
||||
'postgres'
|
||||
]
|
||||
|
||||
for pattern in weak_patterns:
|
||||
if pattern.lower() in uri.lower():
|
||||
return False, f"Weak/default password detected: {pattern}"
|
||||
|
||||
# Validate format
|
||||
if not uri.startswith('postgresql://'):
|
||||
return False, "Invalid PostgreSQL URI format"
|
||||
|
||||
return True, "OK"
|
||||
|
||||
def main():
|
||||
print("╔══════════════════════════════════════════════════════════════╗")
|
||||
print("║ SECURITY HARDENING - Critical Fixes ║")
|
||||
print("╚══════════════════════════════════════════════════════════════╝")
|
||||
print()
|
||||
|
||||
project_root = Path(__file__).parent
|
||||
backend_dir = project_root / "backend"
|
||||
env_file = backend_dir / ".env"
|
||||
|
||||
issues_found = []
|
||||
fixes_applied = []
|
||||
|
||||
# Check 1: .env file exists and has secure permissions
|
||||
print("🔒 Checking .env file security...")
|
||||
if env_file.exists():
|
||||
is_secure, msg = check_env_file_security(env_file)
|
||||
if not is_secure:
|
||||
issues_found.append(f".env file: {msg}")
|
||||
if secure_env_file(env_file):
|
||||
fixes_applied.append("Set .env permissions to 0600")
|
||||
print(" ✓ Fixed: Set secure permissions (0600)")
|
||||
else:
|
||||
print(f" ✗ Failed to secure .env file")
|
||||
else:
|
||||
print(" ✓ .env file has secure permissions")
|
||||
else:
|
||||
issues_found.append(".env file does not exist")
|
||||
print(" ⚠ .env file not found. Use .env.template to create one.")
|
||||
|
||||
# Check 2: SECRET_KEY strength
|
||||
print("\n🔑 Checking SECRET_KEY...")
|
||||
if env_file.exists():
|
||||
with open(env_file, 'r') as f:
|
||||
content = f.read()
|
||||
secret_match = re.search(r'SECRET_KEY=(.+)', content)
|
||||
if secret_match:
|
||||
secret_key = secret_match.group(1).strip()
|
||||
if len(secret_key) < 32:
|
||||
issues_found.append(f"SECRET_KEY is too short ({len(secret_key)} chars, need 64+)")
|
||||
print(f" ⚠ SECRET_KEY is weak (length: {len(secret_key)})")
|
||||
print(f" → Generate new key: python3 -c \"import secrets; print(secrets.token_hex(32))\"")
|
||||
else:
|
||||
print(" ✓ SECRET_KEY length is adequate")
|
||||
else:
|
||||
issues_found.append("SECRET_KEY not found in .env")
|
||||
print(" ✗ SECRET_KEY not found")
|
||||
|
||||
# Check 3: Database password strength
|
||||
print("\n🗄️ Checking database password...")
|
||||
if env_file.exists():
|
||||
with open(env_file, 'r') as f:
|
||||
content = f.read()
|
||||
uri_match = re.search(r'POSTGRESQL_URI=(.+)', content)
|
||||
if uri_match:
|
||||
uri = uri_match.group(1).strip()
|
||||
is_valid, msg = validate_postgresql_uri(uri)
|
||||
if not is_valid:
|
||||
issues_found.append(f"Database URI: {msg}")
|
||||
print(f" ✗ {msg}")
|
||||
else:
|
||||
print(" ✓ Database URI appears secure")
|
||||
else:
|
||||
issues_found.append("POSTGRESQL_URI not found")
|
||||
print(" ✗ POSTGRESQL_URI not configured")
|
||||
|
||||
# Check 4: .gitignore exists
|
||||
print("\n📝 Checking .gitignore...")
|
||||
gitignore = project_root / ".gitignore"
|
||||
if gitignore.exists():
|
||||
with open(gitignore, 'r') as f:
|
||||
content = f.read()
|
||||
if '*.env' in content or '.env' in content:
|
||||
print(" ✓ .gitignore protects .env files")
|
||||
else:
|
||||
issues_found.append(".env files not in .gitignore")
|
||||
print(" ✗ .env files not protected by .gitignore")
|
||||
else:
|
||||
issues_found.append(".gitignore does not exist")
|
||||
print(" ✗ .gitignore not found")
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*64)
|
||||
print("SUMMARY")
|
||||
print("="*64)
|
||||
|
||||
if issues_found:
|
||||
print(f"\n⚠️ {len(issues_found)} security issue(s) found:")
|
||||
for i, issue in enumerate(issues_found, 1):
|
||||
print(f" {i}. {issue}")
|
||||
else:
|
||||
print("\n✅ No critical security issues found")
|
||||
|
||||
if fixes_applied:
|
||||
print(f"\n✓ {len(fixes_applied)} fix(es) applied:")
|
||||
for fix in fixes_applied:
|
||||
print(f" • {fix}")
|
||||
|
||||
print("\n📋 NEXT STEPS:")
|
||||
print(" 1. Rotate SECRET_KEY immediately if weak")
|
||||
print(" 2. Update database password if using defaults")
|
||||
print(" 3. Never commit .env files to git")
|
||||
print(" 4. Review all environment variables")
|
||||
print(" 5. Run this script regularly")
|
||||
|
||||
return 0 if not issues_found else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user