f7c1201da3
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
324 lines
11 KiB
Rust
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()
|
|
} |