#!/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 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, timeout=120, ) 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()