#!/usr/bin/env python3 """AIDE (Advanced Intrusion Detection Environment) configuration generator and check runner.""" import os import json import re import subprocess import argparse from datetime import datetime from collections import defaultdict AIDE_RULE_SETS = { "critical_binaries": { "paths": ["/bin", "/sbin", "/usr/bin", "/usr/sbin"], "rule": "p+i+n+u+g+s+b+m+c+sha256+xattrs", "description": "Monitor all attributes and SHA-256 hash for system binaries", }, "config_files": { "paths": ["/etc"], "rule": "p+i+u+g+sha256", "description": "Monitor permissions, inode, ownership, and hash for config files", }, "boot_files": { "paths": ["/boot"], "rule": "p+i+n+u+g+s+b+m+c+sha256", "description": "Full attribute monitoring for boot files and kernel images", }, "library_files": { "paths": ["/lib", "/lib64", "/usr/lib"], "rule": "p+i+n+u+g+s+b+sha256", "description": "Monitor shared libraries for unauthorized modifications", }, "log_files": { "paths": ["/var/log"], "rule": "p+u+g+i", "description": "Monitor log file permissions and ownership (not content)", }, } AIDE_EXCLUSIONS = [ "!/var/log/.*", "!/var/spool/.*", "!/var/cache/.*", "!/proc", "!/sys", "!/dev", "!/run", "!/tmp", "!/var/lib/dpkg/.*", "!/var/lib/apt/.*", ] def generate_aide_config(rule_sets=None, db_path="/var/lib/aide/aide.db", db_new_path="/var/lib/aide/aide.db.new", log_path="/var/log/aide/aide.log"): """Generate aide.conf configuration file content.""" if rule_sets is None: rule_sets = list(AIDE_RULE_SETS.keys()) lines = [ "# AIDE Configuration - Generated by agent.py", f"# Generated: {datetime.utcnow().isoformat()}", "", f"database_in=file:{db_path}", f"database_out=file:{db_new_path}", f"database_new=file:{db_new_path}", "", "# Gzip output database", "gzip_dbout=yes", "", "# Report settings", "report_url=stdout", f"report_url=file:{log_path}", "", "# Rule definitions", "NORMAL = p+i+n+u+g+s+b+m+c+sha256", "PERMS = p+i+u+g", "LOG = p+u+g+i", "FULL = p+i+n+u+g+s+b+m+c+sha256+xattrs", "", "# Exclusions", ] for excl in AIDE_EXCLUSIONS: lines.append(excl) lines.append("") lines.append("# Monitored paths") for rs_name in rule_sets: rs = AIDE_RULE_SETS.get(rs_name) if not rs: continue lines.append(f"# {rs['description']}") for path in rs["paths"]: lines.append(f"{path} {rs['rule']}") lines.append("") return "\n".join(lines) def initialize_baseline(): """Run aide --init to create the baseline database.""" result = subprocess.run( ["aide", "--init", "--config=/etc/aide/aide.conf"], capture_output=True, text=True, timeout=600 ) if result.returncode == 0: subprocess.run( ["cp", "/var/lib/aide/aide.db.new", "/var/lib/aide/aide.db"], capture_output=True, text=True ) return {"status": "success", "output": result.stdout[:500]} return {"status": "error", "stderr": result.stderr[:500]} def run_integrity_check(): """Run aide --check and parse the results.""" result = subprocess.run( ["aide", "--check", "--config=/etc/aide/aide.conf"], capture_output=True, text=True, timeout=600 ) return parse_aide_output(result.stdout, result.returncode) def parse_aide_output(output, return_code): """Parse AIDE check output into structured findings.""" findings = {"added": [], "removed": [], "changed": [], "summary": {}} section = None for line in output.splitlines(): line = line.strip() if "Added entries:" in line or "added:" in line.lower(): section = "added" elif "Removed entries:" in line or "removed:" in line.lower(): section = "removed" elif "Changed entries:" in line or "changed:" in line.lower(): section = "changed" elif line.startswith("f") or line.startswith("d") or line.startswith("l"): match = re.match(r"^[fdl!]\s+(.+)", line) if match and section: filepath = match.group(1).strip() severity = _classify_severity(filepath, section) findings[section].append({ "path": filepath, "severity": severity, "change_type": section, }) elif "Number of entries" in line: match = re.match(r"Number of entries:\s*(\d+)", line) if match: findings["summary"]["total_entries"] = int(match.group(1)) findings["summary"]["added_count"] = len(findings["added"]) findings["summary"]["removed_count"] = len(findings["removed"]) findings["summary"]["changed_count"] = len(findings["changed"]) findings["summary"]["return_code"] = return_code findings["summary"]["integrity_status"] = "clean" if return_code == 0 else "changes_detected" return findings def _classify_severity(filepath, change_type): """Classify severity based on file path and change type.""" critical_paths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/boot/", "/etc/shadow", "/etc/passwd", "/etc/sudoers", "/etc/ssh/"] high_paths = ["/etc/", "/lib/", "/lib64/", "/usr/lib/"] if change_type == "removed" and any(filepath.startswith(p) for p in critical_paths): return "critical" if any(filepath.startswith(p) for p in critical_paths): return "critical" if change_type != "added" else "high" if any(filepath.startswith(p) for p in high_paths): return "high" return "medium" def generate_cron_config(schedule="0 5 * * *", email=None): """Generate cron job for automated AIDE integrity checks.""" cron_script = "#!/bin/bash\n" cron_script += "# AIDE automated integrity check\n" cron_script += "LOGFILE=/var/log/aide/aide-check-$(date +%Y%m%d).log\n" cron_script += "aide --check --config=/etc/aide/aide.conf > $LOGFILE 2>&1\n" cron_script += "EXIT_CODE=$?\n" cron_script += "if [ $EXIT_CODE -ne 0 ]; then\n" if email: cron_script += f' mail -s "AIDE Alert: Integrity changes detected" {email} < $LOGFILE\n' cron_script += ' logger -t aide "Integrity check found changes (exit code $EXIT_CODE)"\n' cron_script += "fi\n" cron_entry = f"{schedule} root /usr/local/bin/aide-check.sh" return {"script": cron_script, "cron_entry": cron_entry, "schedule": schedule} def generate_report(config_content, baseline_status, check_findings, cron_config): """Generate AIDE deployment and check report.""" return { "report_time": datetime.utcnow().isoformat(), "config_lines": len(config_content.splitlines()), "monitored_rule_sets": list(AIDE_RULE_SETS.keys()), "exclusion_count": len(AIDE_EXCLUSIONS), "baseline_status": baseline_status, "integrity_check": check_findings, "cron_schedule": cron_config, } def main(): parser = argparse.ArgumentParser(description="AIDE File Integrity Monitoring Agent") parser.add_argument("--action", choices=["generate", "init", "check", "full"], default="generate", help="Action to perform") parser.add_argument("--config-output", default="/etc/aide/aide.conf", help="Config output path") parser.add_argument("--alert-email", help="Email for cron alert notifications") parser.add_argument("--output", default="aide_report.json") parser.add_argument("--rule-sets", nargs="+", default=None, choices=list(AIDE_RULE_SETS.keys()), help="Rule sets to enable") args = parser.parse_args() config_content = generate_aide_config(args.rule_sets) baseline_status = {} check_findings = {} if args.action in ("generate", "full"): os.makedirs(os.path.dirname(args.config_output), exist_ok=True) with open(args.config_output, "w") as f: f.write(config_content) print(f"[+] AIDE config written to {args.config_output}") if args.action in ("init", "full"): baseline_status = initialize_baseline() print(f"[+] Baseline init: {baseline_status['status']}") if args.action in ("check", "full"): check_findings = run_integrity_check() s = check_findings.get("summary", {}) print(f"[+] Check: added={s.get('added_count', 0)} removed={s.get('removed_count', 0)} " f"changed={s.get('changed_count', 0)}") cron_config = generate_cron_config(email=args.alert_email) report = generate_report(config_content, baseline_status, check_findings, cron_config) with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) print(f"[+] Report saved to {args.output}") if __name__ == "__main__": main()