use anyhow::{Context, Result}; use std::io::Write; use std::path::Path; use tracing::{info, warn}; const ALPM_HOOK_PATH: &str = "/usr/share/libalpm/hooks/aegisaur-pre-install.hook"; const HOOK_SCRIPT_PATH: &str = "/usr/share/libalpm/hooks/aegisaur-check.sh"; const SYSTEMD_SERVICE_PATH: &str = "/etc/systemd/system/aegisaur-scan.service"; const SYSTEMD_TIMER_PATH: &str = "/etc/systemd/system/aegisaur-scan.timer"; const SYSTEMD_SCRIPT_PATH: &str = "/usr/local/bin/aegisaur-scan-daily"; /// Installiert den ALPM-Hook für Pre-Install-Checks pub fn install_alpm_hook() -> Result<()> { // Hook-Definition let hook_content = r#"[Trigger] Operation = Install Operation = Upgrade Type = Package Target = * [Action] Description = AegisAUR Security Scan When = PreTransaction Exec = /usr/share/libalpm/hooks/aegisaur-check.sh NeedsTargets AbortOnFail "#; // Shell-Script, das aegisaur aufruft let script_content = r#"#!/bin/bash # AegisAUR Pre-Install Hook # Prüft Pakete vor der Installation AUR_SCANNER="/usr/bin/aegisaur" TMPFILE=$(mktemp) # Alle zu installierenden Pakete durch aegisaur prüfen while read -r package; do # Nur AUR-Pakete prüfen (Foreign packages) if pacman -Qi "$package" >/dev/null 2>&1; then # Paket ist bereits installiert (Upgrade) continue fi # Prüfe ob es ein AUR/Foreign Paket ist if pacman -Si "$package" >/dev/null 2>&1; then # Offizielles Repo-Paket, immer OK continue fi # AUR Paket gefunden - scanne es if [[ -x "$AUR_SCANNER" ]]; then RESULT=$($AUR_SCANNER scan "$package" --json 2>/devnull) SCORE=$(echo "$RESULT" | grep -oP '"score":\s*\K\d+') STATUS=$(echo "$RESULT" | grep -oP '"status":\s*"\K[^"]+') if [[ "$STATUS" == "IOCDetected" ]] || [[ "$STATUS" == "Dangerous" ]]; then echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ 🚨 AEGISAUR SECURITY ALERT 🚨 ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" echo "Paket: $package" echo "Status: $STATUS" echo "Score: $SCORE/100" echo "" echo "⚠️ DIESES PAKET IST ALS GEFÄHRLICH EINGESTUFT!" echo "" echo "Möchtest du die Installation abbrechen? (Ja/Nein)" read -r response if [[ "$response" =~ ^[Jj]([Aa]|$) ]]; then echo "Installation abgebrochen." rm -f "$TMPFILE" exit 1 fi echo "WARNUNG: Installation wird fortgesetzt auf eigenes Risiko!" echo "$package ($STATUS - Score: $SCORE)" >> "$TMPFILE" elif [[ "$STATUS" == "Suspicious" ]] || [[ "$STATUS" == "Warning" ]]; then echo "" echo "⚠️ AegisAUR Warnung für $package: $STATUS (Score: $SCORE/100)" echo "$package ($STATUS - Score: $SCORE)" >> "$TMPFILE" fi fi done # Zusammenfassung anzeigen falls Warnungen vorhanden if [[ -s "$TMPFILE" ]]; then echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ AegisAUR Scan Zusammenfassung ║" echo "╚══════════════════════════════════════════════════════════════╝" cat "$TMPFILE" echo "" fi rm -f "$TMPFILE" exit 0 "#; // Hook-Datei schreiben info!("Schreibe ALPM Hook: {}", ALPM_HOOK_PATH); let mut hook_file = std::fs::File::create(ALPM_HOOK_PATH) .context("Konnte ALPM Hook nicht erstellen (Root-Rechte nötig)")?; hook_file.write_all(hook_content.as_bytes())?; // Script schreiben info!("Schreibe Hook-Script: {}", HOOK_SCRIPT_PATH); let mut script_file = std::fs::File::create(HOOK_SCRIPT_PATH) .context("Konnte Hook-Script nicht erstellen")?; script_file.write_all(script_content.as_bytes())?; // Script executable machen #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = std::fs::metadata(HOOK_SCRIPT_PATH)?.permissions(); perms.set_mode(0o755); std::fs::set_permissions(HOOK_SCRIPT_PATH, perms)?; } info!("ALPM Hook erfolgreich installiert"); Ok(()) } /// Entfernt den ALPM-Hook pub fn remove_alpm_hook() -> Result<()> { info!("Entferne ALPM Hook..."); if Path::new(ALPM_HOOK_PATH).exists() { std::fs::remove_file(ALPM_HOOK_PATH)?; info!("Hook-Datei entfernt: {}", ALPM_HOOK_PATH); } else { warn!("Hook-Datei nicht gefunden: {}", ALPM_HOOK_PATH); } if Path::new(HOOK_SCRIPT_PATH).exists() { std::fs::remove_file(HOOK_SCRIPT_PATH)?; info!("Script entfernt: {}", HOOK_SCRIPT_PATH); } else { warn!("Script nicht gefunden: {}", HOOK_SCRIPT_PATH); } info!("ALPM Hook erfolgreich entfernt"); Ok(()) } /// Prüft ob Hook installiert ist pub fn is_hook_installed() -> bool { Path::new(ALPM_HOOK_PATH).exists() && Path::new(HOOK_SCRIPT_PATH).exists() } /// Installiert den Systemd-Timer für tägliche AUR-Scans pub fn install_systemd_timer() -> Result<()> { // Service-Datei: Führt aegisaur aus let service_content = r#"[Unit] Description=AegisAUR Daily AUR Security Scan After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/aegisaur-scan-daily StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target "#; // Timer-Datei: Täglich um 03:00 Uhr let timer_content = r#"[Unit] Description=AegisAUR Daily Scan Timer [Timer] OnCalendar=daily Persistent=true RandomizedDelaySec=3600 Unit=aegisaur-scan.service [Install] WantedBy=timers.target "#; // Wrapper-Script: Führt scan-all aus und loggt Ergebnisse let script_content = r#"#!/bin/bash # AegisAUR Daily Scan Script # Wird vom systemd-timer aufgerufen set -e TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') AUR_SCANNER="/usr/bin/aegisaur" if [[ ! -x "$AUR_SCANNER" ]]; then echo "[$TIMESTAMP] AegisAUR nicht gefunden unter $AUR_SCANNER" exit 1 fi echo "[$TIMESTAMP] Starte AegisAUR täglichen Scan..." # Cache aktualisieren echo "[$TIMESTAMP] Aktualisiere IOC-Cache..." $AUR_SCANNER check-ioc >/dev/null 2>&1 # Alle AUR-Pakete scannen echo "[$TIMESTAMP] Scanne alle AUR-Pakete..." RESULTS=$($AUR_SCANNER scan-all 2>&1) # Zusammenfassung loggen THREAT_COUNT=$(echo "$RESULTS" | grep -c "IOC ERKANNT!" || true) WARN_COUNT=$(echo "$RESULTS" | grep -c "WARNUNG" || true) DANGER_COUNT=$(echo "$RESULTS" | grep -c "DANGEROUS" || true) echo "[$TIMESTAMP] Scan abgeschlossen: $THREAT_COUNT IOCs, $WARN_COUNT Warnungen, $DANGER_COUNT Gefahren" if [[ $THREAT_COUNT -gt 0 ]] || [[ $DANGER_COUNT -gt 0 ]]; then echo "[$TIMESTAMP] KRITISCHE BEDROHUNGEN GEFUNDEN:" echo "$RESULTS" | grep -E "🔴|🚨|DANGEROUS|IOC" || true # Optional: Desktop-Benachrichtigung if command -v notify-send >/dev/null 2>&1; then notify-send -u critical "AegisAUR Alert" "$THREAT_COUNT IOC(s) und $DANGER_COUNT Gefahr(en) in AUR-Paketen gefunden!" fi fi echo "[$TIMESTAMP] Täglicher Scan abgeschlossen." "#; // Service-Datei schreiben info!("Schreibe Systemd Service: {}", SYSTEMD_SERVICE_PATH); let mut service_file = std::fs::File::create(SYSTEMD_SERVICE_PATH) .context("Konnte Systemd Service nicht erstellen (Root-Rechte nötig)")?; service_file.write_all(service_content.as_bytes())?; // Timer-Datei schreiben info!("Schreibe Systemd Timer: {}", SYSTEMD_TIMER_PATH); let mut timer_file = std::fs::File::create(SYSTEMD_TIMER_PATH) .context("Konnte Systemd Timer nicht erstellen")?; timer_file.write_all(timer_content.as_bytes())?; // Script schreiben info!("Schreibe Daily-Scan Script: {}", SYSTEMD_SCRIPT_PATH); let mut script_file = std::fs::File::create(SYSTEMD_SCRIPT_PATH) .context("Konnte Scan-Script nicht erstellen")?; script_file.write_all(script_content.as_bytes())?; // Script executable machen #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = std::fs::metadata(SYSTEMD_SCRIPT_PATH)?.permissions(); perms.set_mode(0o755); std::fs::set_permissions(SYSTEMD_SCRIPT_PATH, perms)?; } // Systemd neu laden und Timer starten info!("Aktiviere Systemd Timer..."); let status = std::process::Command::new("systemctl") .args(["daemon-reload"]) .status() .context("systemctl daemon-reload fehlgeschlagen")?; if !status.success() { anyhow::bail!("systemctl daemon-reload fehlgeschlagen"); } let status = std::process::Command::new("systemctl") .args(["enable", "--now", "aegisaur-scan.timer"]) .status() .context("systemctl enable fehlgeschlagen")?; if !status.success() { anyhow::bail!("Konnte Timer nicht aktivieren"); } info!("Systemd Timer erfolgreich installiert und aktiviert"); Ok(()) } /// Entfernt den Systemd-Timer pub fn remove_systemd_timer() -> Result<()> { info!("Entferne Systemd Timer..."); // Timer stoppen und deaktivieren let _ = std::process::Command::new("systemctl") .args(["stop", "aegisaur-scan.timer"]) .status(); let _ = std::process::Command::new("systemctl") .args(["disable", "aegisaur-scan.timer"]) .status(); // Dateien löschen if Path::new(SYSTEMD_SERVICE_PATH).exists() { std::fs::remove_file(SYSTEMD_SERVICE_PATH) .context("Konnte Service-Datei nicht löschen")?; info!("Service entfernt: {}", SYSTEMD_SERVICE_PATH); } if Path::new(SYSTEMD_TIMER_PATH).exists() { std::fs::remove_file(SYSTEMD_TIMER_PATH) .context("Konnte Timer-Datei nicht löschen")?; info!("Timer entfernt: {}", SYSTEMD_TIMER_PATH); } if Path::new(SYSTEMD_SCRIPT_PATH).exists() { std::fs::remove_file(SYSTEMD_SCRIPT_PATH) .context("Konnte Script nicht löschen")?; info!("Script entfernt: {}", SYSTEMD_SCRIPT_PATH); } // Systemd neu laden let _ = std::process::Command::new("systemctl") .args(["daemon-reload"]) .status(); info!("Systemd Timer erfolgreich entfernt"); Ok(()) } /// Prüft ob Timer installiert ist pub fn is_timer_installed() -> bool { Path::new(SYSTEMD_TIMER_PATH).exists() && Path::new(SYSTEMD_SERVICE_PATH).exists() }