Initial Debian security mirror

This commit is contained in:
root
2026-11-10 21:52:04 +01:00
commit 3009b4977a
9 changed files with 238 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
name: Debian Security Tracker Mirror
on:
workflow_dispatch:
schedule:
- cron: "43 */6 * * *"
permissions:
contents: write
concurrency:
group: vuln-debian-mirror
cancel-in-progress: false
jobs:
mirror:
runs-on: vuln-runner
timeout-minutes: 120
steps:
- name: Sync official Debian data using persistent checkout
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
CACHE_ROOT: /var/lib/act_runner/cache/vuln-debian
REPO_URL: https://git.apex-project.de/VULN/vuln-debian.git
run: |
set -Eeuo pipefail
umask 077
mkdir -p "$CACHE_ROOT"
if [ ! -f "$CACHE_ROOT/repo/scripts/run-cached.sh" ]; then
ASKPASS="$(mktemp)"
trap 'rm -f "$ASKPASS"' EXIT
cat > "$ASKPASS" <<'EOF'
#!/bin/sh
case "$1" in
*Username*) printf 'oauth2\n' ;;
*Password*) printf '%s\n' "$GITEA_TOKEN" ;;
*) exit 1 ;;
esac
EOF
chmod 700 "$ASKPASS"
GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git clone --depth 1 --branch main "$REPO_URL" "$CACHE_ROOT/repo"
rm -f "$ASKPASS"
trap - EXIT
fi
bash "$CACHE_ROOT/repo/scripts/run-cached.sh"
+5
View File
@@ -0,0 +1,5 @@
__pycache__/
*.pyc
*.tmp
*.lock
.env
+57
View File
@@ -0,0 +1,57 @@
# vuln-debian – Gitea Security Tracker Mirror
Spiegelt den **offiziellen Debian Security Tracker JSON-Export** als Rohdatei nach Gitea.
Quelle: https://security-tracker.debian.org/tracker/data/json
## Funktionsumfang
- Vollständiger JSON-Export, unverändert unter `data/tracker.json`.
- Paketnamen, CVEs, Releases, Status, `fixed_version`, `urgency` bleiben erhalten.
- `ETag` / `Last-Modified` werden verwendet, falls der Debian-Server sie liefert.
- SHA256-Vergleich verhindert unnötige Commits.
- Dauerhaftes Git-Arbeitsverzeichnis auf demselben Host wie Gitea.
- Alle sechs Stunden zur Minute 43 UTC, plus manueller Workflow-Start.
- Gitea-Job-Token: `${{ secrets.GITEA_TOKEN }}`; kein eigener PAT nötig, sofern Push-Berechtigung vorhanden.
## Installation
1. Leeres Repository `VULN/vuln-debian` erstellen.
2. Dateien dieses Verzeichnisses hochladen:
```bash
git init
git branch -M main
git add .
git commit -m 'Initial Debian mirror'
git remote add origin https://git.apex-project.de/VULN/vuln-debian.git
git push -u origin main
```
3. Auf der Gitea-VM:
```bash
sudo apt update && sudo apt install -y git python3 util-linux ca-certificates
sudo install -d -o act_runner -g act_runner -m 700 /var/lib/act_runner/cache/vuln-debian
```
4. In Gitea die Actions für das Repository aktivieren und den Schreibzugriff des Job-Tokens erlauben.
5. Unter `VULN/vuln-debian/actions` den Workflow manuell starten.
## Cache
`/var/lib/act_runner/cache/vuln-debian/repo` enthält einen persistenten Checkout, `/var/lib/act_runner/cache/vuln-debian/.sync.lock` verhindert gleichzeitige Synchronisationen. Das Repository wird nur beim ersten Mal vollständig geklont, danach mit `git fetch --depth 1` aktualisiert.
## Quelle und Grenzen
Das Debian-JSON ist **kein Archiv nur ab 2017**. Es enthält auch ältere CVEs. Das ist beabsichtigt, da ein Filter die Rohdaten verändern würde; die spätere SQLite-Buildstufe filtert nach Jahr und gewünschter Release-Unterstützung.
Der Feed wird als eine große JSON-Datei versioniert. Das ist vollständig und verlustfrei, kann die Git-Historie aber bei häufigen Änderungen vergrößern. Die Inhalte werden nicht zu separaten CVE-Dateien aufgeteilt.
Der Feed ist eine abgeleitete Ansicht der Debian-Sicherheitsdaten. Das Schema ist nicht garantiert stabil; bei strukturellen Änderungen bricht die Validierung ab statt stillschweigend Daten zu verlieren. Hinweise zu Redistributon/Nutzungsbedingungen vor öffentlicher Veröffentlichung prüfen.
## Diagnose
```bash
sudo journalctl -u act_runner -f
sudo -u act_runner git -C /var/lib/act_runner/cache/vuln-debian/repo status --short
sudo du -sh /var/lib/act_runner/cache/vuln-debian/repo
```
Getestet: Python-Syntax, Shell-Syntax, lokale Testdaten. Live-Gitea-Push und großer Debian-Download sind nicht durchgetestet.
View File
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
: "${GITEA_TOKEN:?GITEA_TOKEN fehlt}"
CACHE_ROOT="${CACHE_ROOT:-/var/lib/act_runner/cache/vuln-debian}"
REPO_URL="${REPO_URL:-https://git.apex-project.de/VULN/vuln-debian.git}"
mkdir -p "$CACHE_ROOT"
exec 9>"$CACHE_ROOT/.sync.lock"
flock -w 300 9 || { echo 'Ein anderer Lauf ist noch aktiv' >&2; exit 1; }
# Credentials are supplied only on demand; never embedded in the remote URL.
ASKPASS="$(mktemp)"
trap 'rm -f "$ASKPASS"' EXIT
cat > "$ASKPASS" <<'EOF'
#!/bin/sh
case "$1" in
*Username*) printf 'oauth2\n' ;;
*Password*) printf '%s\n' "$GITEA_TOKEN" ;;
*) exit 1 ;;
esac
EOF
chmod 700 "$ASKPASS"
export GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0
if [[ ! -d "$CACHE_ROOT/repo/.git" ]]; then
git clone --depth 1 --branch main "$REPO_URL" "$CACHE_ROOT/repo"
fi
cd "$CACHE_ROOT/repo"
git remote set-url origin "$REPO_URL"
git fetch --depth 1 origin main
git reset --hard FETCH_HEAD
python3 scripts/sync.py
python3 scripts/validate.py
git add data/tracker.json state/sync.json
if git diff --cached --quiet; then
echo 'Keine neuen Daten für Commit'; exit 0
fi
git config user.name 'Vulnerability Bot'
git config user.email 'vulnerability-bot@users.noreply.local'
git commit -m "Debian Security Tracker update $(date -u +%FT%TZ)"
git push origin HEAD:main
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Mirror the official Debian Security Tracker JSON export without changing its contents."""
import argparse
import hashlib
import json
import os
from pathlib import Path
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from urllib.error import HTTPError
URL = 'https://security-tracker.debian.org/tracker/data/json'
def sync(root: Path, url: str, timeout: int = 240):
data = root / 'data' / 'tracker.json'
state = root / 'state' / 'sync.json'
meta = {}
if state.exists():
meta = json.loads(state.read_text(encoding='utf-8'))
headers = {'User-Agent': 'apex-project-vuln-debian-mirror/1.0', 'Accept': 'application/json'}
if data.exists():
if meta.get('etag'): headers['If-None-Match'] = meta['etag']
if meta.get('last_modified'): headers['If-Modified-Since'] = meta['last_modified']
try:
with urlopen(Request(url, headers=headers), timeout=timeout) as response:
raw = response.read()
etag = response.headers.get('ETag')
modified = response.headers.get('Last-Modified')
except HTTPError as exc:
if exc.code == 304 and data.exists():
print('HTTP 304: keine Änderungen am Debian-Export')
return False
raise
if len(raw) < 100: raise RuntimeError('Antwort ist unerwartet kurz')
parsed = json.loads(raw)
validate(parsed)
digest = hashlib.sha256(raw).hexdigest()
if data.exists() and hashlib.sha256(data.read_bytes()).hexdigest() == digest:
print('Inhalt unverändert; kein Commit erforderlich')
return False
data.parent.mkdir(parents=True, exist_ok=True)
state.parent.mkdir(parents=True, exist_ok=True)
temp = data.with_suffix('.json.tmp')
temp.write_bytes(raw)
os.replace(temp, data)
state_temp = state.with_suffix('.json.tmp')
state_temp.write_text(json.dumps({'source': url, 'sha256': digest, 'etag':etag,
'last_modified':modified, 'downloaded_at':datetime.now(timezone.utc).isoformat(),
'package_count':len(parsed)}, indent=2, ensure_ascii=False)+'\n', encoding='utf-8')
os.replace(state_temp, state)
print(f'Debian Export gespeichert: {len(raw):,} Bytes / {len(parsed):,} Pakete')
return True
def validate(parsed):
if not isinstance(parsed, dict) or not parsed:
raise ValueError('Erwartet nichtleeres JSON-Objekt mit Paketnamen als Keys')
for package, cves in list(parsed.items())[:200]:
if not isinstance(package, str) or not isinstance(cves, dict):
raise ValueError(f'Unerwartetes Debian-JSON-Schema bei {package!r}')
def main():
p = argparse.ArgumentParser()
p.add_argument('--root', type=Path, default=Path(__file__).resolve().parent.parent)
p.add_argument('--url', default=URL)
p.add_argument('--timeout', type=int, default=240)
args = p.parse_args()
sync(args.root, args.url, args.timeout)
if __name__ == '__main__': main()
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
import hashlib
import json
from pathlib import Path
from sync import validate
root = Path(__file__).resolve().parent.parent
p = root / 'data/tracker.json'
s = root / 'state/sync.json'
raw = p.read_bytes()
parsed = json.loads(raw)
validate(parsed)
meta = json.loads(s.read_text())
assert hashlib.sha256(raw).hexdigest() == meta['sha256'], 'SHA256 stimmt nicht'
print(f'OK: {len(parsed):,} Debian-Pakete; SHA256 geprüft')
View File
+13
View File
@@ -0,0 +1,13 @@
import json
import tempfile
import unittest
from pathlib import Path
from sys import path
path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from sync import validate
class TestSync(unittest.TestCase):
def test_example(self):
validate({'openssl': {'CVE-2024-0001': {'releases': {'bookworm': {'status': 'resolved', 'fixed_version': '1.2'}}}}})
def test_bad(self):
with self.assertRaises(ValueError): validate([])
if __name__ == '__main__': unittest.main()