Files
aegisaur/src/hook.rs
T
Thuumate 👻 f7c1201da3 feat: v2.1.0 — Systemd Timer + Performance Optimierungen
Neue Features:
- install-timer / remove-timer Befehle
- Tägliche automatische AUR-Scans via systemd
- Cache-Status-Anzeige mit Alter und TTL
- AUR-Score aus Paket-Info (Votes, Popularität, Maintainer)
- Performance: ~7x schneller durch gecachte AUR-Prüfung
- PKGBUILD-Analyse nur bei verbose oder IOC-Match

Verbesserungen:
- Keine False-Positives für offizielle Repo-Pakete
- HedgeDoc 1931 IOCs live geladen
- Journal-Logging für Timer-Scans
2026-06-15 19:59:47 +02:00

324 lines
11 KiB
Rust

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()
}