Configuration Audit Scripts

Configuration Audit Scripts

Comprehensive configuration auditing:

#!/usr/bin/env python3
# /usr/local/bin/config-audit.py

import os
import re
import json
import subprocess
from datetime import datetime

class WebServerAuditor:
    def __init__(self):
        self.findings = []
        self.apache_config = '/etc/apache2/apache2.conf'
        self.nginx_config = '/etc/nginx/nginx.conf'
        
    def audit_apache(self):
        """Audit Apache configuration"""
        if not os.path.exists(self.apache_config):
            return
            
        print("Auditing Apache configuration...")
        
        # Read main config
        with open(self.apache_config, 'r') as f:
            config = f.read()
            
        # Security checks
        checks = [
            {
                'name': 'ServerTokens',
                'pattern': r'ServerTokens\s+(\w+)',
                'expected': 'Prod',
                'severity': 'Medium'
            },
            {
                'name': 'ServerSignature',
                'pattern': r'ServerSignature\s+(\w+)',
                'expected': 'Off',
                'severity': 'Low'
            },
            {
                'name': 'TraceEnable',
                'pattern': r'TraceEnable\s+(\w+)',
                'expected': 'Off',
                'severity': 'Medium'
            }
        ]
        
        for check in checks:
            match = re.search(check['pattern'], config)
            if match:
                if match.group(1) != check['expected']:
                    self.findings.append({
                        'type': 'Apache Configuration',
                        'severity': check['severity'],
                        'finding': f"{check['name']} is set to {match.group(1)}, should be {check['expected']}"
                    })
            else:
                self.findings.append({
                    'type': 'Apache Configuration',
                    'severity': check['severity'],
                    'finding': f"{check['name']} is not configured"
                })
                
        # Check loaded modules
        dangerous_modules = ['mod_status', 'mod_info', 'mod_userdir', 'mod_autoindex']
        try:
            result = subprocess.run(['apache2ctl', '-M'], capture_output=True, text=True)
            for module in dangerous_modules:
                if module in result.stdout:
                    self.findings.append({
                        'type': 'Apache Modules',
                        'severity': 'Medium',
                        'finding': f"Potentially dangerous module loaded: {module}"
                    })
        except:
            pass
            
    def audit_nginx(self):
        """Audit Nginx configuration"""
        if not os.path.exists(self.nginx_config):
            return
            
        print("Auditing Nginx configuration...")
        
        with open(self.nginx_config, 'r') as f:
            config = f.read()
            
        # Security checks
        if 'server_tokens off' not in config:
            self.findings.append({
                'type': 'Nginx Configuration',
                'severity': 'Medium',
                'finding': 'server_tokens is not set to off'
            })
            
        if 'client_max_body_size' not in config:
            self.findings.append({
                'type': 'Nginx Configuration',
                'severity': 'Low',
                'finding': 'client_max_body_size is not configured'
            })
            
        # Check for weak SSL protocols
        if re.search(r'ssl_protocols.*TLSv1(?:\.0)?[\s;]', config):
            self.findings.append({
                'type': 'Nginx SSL',
                'severity': 'High',
                'finding': 'Weak TLS v1.0 protocol enabled'
            })
            
    def audit_ssl_certificates(self):
        """Audit SSL certificate configuration"""
        print("Auditing SSL certificates...")
        
        cert_paths = []
        
        # Find certificate paths
        for config_dir in ['/etc/apache2/sites-enabled', '/etc/nginx/sites-enabled']:
            if os.path.exists(config_dir):
                for file in os.listdir(config_dir):
                    filepath = os.path.join(config_dir, file)
                    with open(filepath, 'r') as f:
                        content = f.read()
                        # Extract certificate paths
                        certs = re.findall(r'(?:SSLCertificateFile|ssl_certificate)\s+([^\s;]+)', content)
                        cert_paths.extend(certs)
                        
        # Check each certificate
        for cert_path in set(cert_paths):
            if os.path.exists(cert_path):
                try:
                    result = subprocess.run(
                        ['openssl', 'x509', '-enddate', '-noout', '-in', cert_path],
                        capture_output=True,
                        text=True
                    )
                    if 'notAfter=' in result.stdout:
                        expiry_str = result.stdout.split('=')[1].strip()
                        # Check if certificate expires soon
                        # Simple check - production should parse the date properly
                        self.findings.append({
                            'type': 'SSL Certificate',
                            'severity': 'Info',
                            'finding': f"Certificate {cert_path} expires on {expiry_str}"
                        })
                except:
                    pass
                    
    def audit_file_permissions(self):
        """Audit file and directory permissions"""
        print("Auditing file permissions...")
        
        critical_paths = [
            ('/etc/apache2', '755', 'Apache config directory'),
            ('/etc/nginx', '755', 'Nginx config directory'),
            ('/var/www', '755', 'Web root directory'),
            ('/etc/ssl/private', '700', 'SSL private keys directory')
        ]
        
        for path, expected_perms, description in critical_paths:
            if os.path.exists(path):
                stat_info = os.stat(path)
                actual_perms = oct(stat_info.st_mode)[-3:]
                
                if actual_perms != expected_perms:
                    self.findings.append({
                        'type': 'File Permissions',
                        'severity': 'High' if 'private' in path else 'Medium',
                        'finding': f"{description} has permissions {actual_perms}, should be {expected_perms}"
                    })
                    
    def audit_security_headers(self):
        """Audit security header configuration"""
        print("Auditing security headers configuration...")
        
        required_headers = [
            'Strict-Transport-Security',
            'X-Content-Type-Options',
            'X-Frame-Options',
            'X-XSS-Protection',
            'Content-Security-Policy'
        ]
        
        # Check Apache configs
        apache_sites = '/etc/apache2/sites-enabled'
        if os.path.exists(apache_sites):
            for site in os.listdir(apache_sites):
                with open(os.path.join(apache_sites, site), 'r') as f:
                    content = f.read()
                    for header in required_headers:
                        if f'Header.*{header}' not in content:
                            self.findings.append({
                                'type': 'Security Headers',
                                'severity': 'Medium',
                                'finding': f"{header} not configured in Apache site {site}"
                            })
                            
    def generate_report(self):
        """Generate audit report"""
        report = {
            'audit_date': datetime.now().isoformat(),
            'total_findings': len(self.findings),
            'findings_by_severity': {
                'High': len([f for f in self.findings if f['severity'] == 'High']),
                'Medium': len([f for f in self.findings if f['severity'] == 'Medium']),
                'Low': len([f for f in self.findings if f['severity'] == 'Low']),
                'Info': len([f for f in self.findings if f['severity'] == 'Info'])
            },
            'findings': self.findings
        }
        
        return report
        
    def run_audit(self):
        """Run all audit checks"""
        print("Starting web server security audit...")
        
        self.audit_apache()
        self.audit_nginx()
        self.audit_ssl_certificates()
        self.audit_file_permissions()
        self.audit_security_headers()
        
        report = self.generate_report()
        
        # Save report
        with open(f'/var/log/security-audit-{datetime.now().strftime("%Y%m%d")}.json', 'w') as f:
            json.dump(report, f, indent=2)
            
        # Print summary
        print(f"\nAudit Complete:")
        print(f"Total Findings: {report['total_findings']}")
        print(f"High Severity: {report['findings_by_severity']['High']}")
        print(f"Medium Severity: {report['findings_by_severity']['Medium']}")
        print(f"Low Severity: {report['findings_by_severity']['Low']}")
        
        return report

if __name__ == '__main__':
    auditor = WebServerAuditor()
    auditor.run_audit()