#!/usr/bin/env bash set -u BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$BASE" FAILURES=0 pass(){ printf 'PASS %s\n' "$1"; } warn(){ printf 'WARN %s\n' "$1"; } fail(){ printf 'FAIL %s\n' "$1"; FAILURES=$((FAILURES+1)); } echo "=== Identité et système ===" echo "Date UTC: $(date -u +%FT%TZ)" echo "Utilisateur: $(id -un) uid=$(id -u)" [[ "$(id -u)" != 0 ]] && pass "processus d'audit non-root" || fail "audit exécuté en root" uname -a echo "=== Ports réellement en écoute ===" ss -tulpn 2>/dev/null || { warn "ss indisponible"; netstat -tulpn 2>/dev/null || true; } PROXY_PORT="$(sed -n 's/^CODEXPI_PROXY_PORT=//p' .env 2>/dev/null | tail -n 1)" PROXY_PORT="${PROXY_PORT:-8765}" if ss -lnt 2>/dev/null | grep -Eq "(:|\\])${PROXY_PORT}([[:space:]]|$)"; then pass "nginx CodexPi écoute sur le port réseau ${PROXY_PORT}"; else fail "reverse proxy absent du port ${PROXY_PORT}"; fi if ss -lnt 2>/dev/null | grep -Eq "(^|[[:space:]])(0\\.0\\.0\\.0|\\*|\\[::\\]):(8000|8001)"; then fail "FastAPI ou Django est publié directement"; else pass "ports internes 8000/8001 non publiés"; fi docker compose ps 2>/dev/null || warn "stack Docker non démarrée" echo "=== Tailscale ===" if command -v tailscale >/dev/null 2>&1; then tailscale version | head -n 1 tailscale ip -4 2>/dev/null || warn "aucune IP Tailscale" tailscale status 2>/dev/null || warn "Tailscale non connecté" tailscale serve status 2>/dev/null || true tailscale funnel status 2>/dev/null || true else warn "Tailscale absent"; fi echo "=== Firewall et fail2ban (lecture seule) ===" if command -v ufw >/dev/null 2>&1; then sudo -n ufw status verbose 2>/dev/null || ufw status verbose 2>/dev/null || warn "UFW présent mais état lisible seulement avec sudo"; else warn "UFW absent"; fi if command -v fail2ban-client >/dev/null 2>&1; then sudo -n fail2ban-client status 2>/dev/null || fail2ban-client status 2>/dev/null || warn "fail2ban présent mais état non lisible"; else warn "fail2ban absent"; fi echo "=== HTTP et protections applicatives ===" HEADERS="$(mktemp)" trap 'rm -f "$HEADERS"' EXIT if curl -fsS --max-time 5 -D "$HEADERS" -o /dev/null "http://127.0.0.1:${PROXY_PORT}/"; then grep -Eqi '^content-security-policy:' "$HEADERS" && pass "CSP" || fail "CSP absente" grep -Eqi '^x-frame-options:[[:space:]]*DENY' "$HEADERS" && pass "X-Frame-Options" || fail "X-Frame-Options" grep -Eqi '^x-content-type-options:[[:space:]]*nosniff' "$HEADERS" && pass "nosniff" || fail "nosniff" grep -Eqi '^referrer-policy:[[:space:]]*no-referrer' "$HEADERS" && pass "Referrer-Policy" || fail "Referrer-Policy" CODE="$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PROXY_PORT}/api/auth/me")" [[ "$CODE" == 401 ]] && pass "endpoint privé refuse une session anonyme" || fail "endpoint privé anonyme: HTTP $CODE" else fail "endpoint HTTP local inaccessible"; fi echo "=== Fichiers, secrets et mode debug ===" stat -c '%A %a %U:%G %n' data data/secret_key data/django_secret_key data/security.log 2>/dev/null || warn "certains fichiers de données n'existent pas encore" [[ ! -e data/secret_key || "$(stat -c '%a' data/secret_key)" == 600 ]] && pass "secret_key en 0600" || fail "permissions secret_key" [[ ! -e data/security.log || "$(stat -c '%a' data/security.log)" == 600 ]] && pass "security.log en 0600" || fail "permissions security.log" [[ ! -e data/django_secret_key || "$(stat -c '%a' data/django_secret_key)" == 600 ]] && pass "django_secret_key en 0600" || fail "permissions django_secret_key" if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then if ./scripts/check-secrets.sh --worktree >/dev/null; then pass "secrets, clés et fichiers confidentiels absents des sources publiables" else fail "secret, clé ou fichier confidentiel détecté dans les sources publiables" fi fi grep -RInE 'debug[[:space:]]*=[[:space:]]*true|DEBUG[[:space:]]*=[[:space:]]*True|--reload' backend django_ui scripts --exclude-dir=__pycache__ && fail "mode debug/reload détecté" || pass "modes debug Django/FastAPI désactivés" echo "=== Tests ciblés ===" docker compose exec -T django-web python3 manage.py check --deploy --fail-level ERROR 2>/dev/null && pass "Django system check" || fail "Django system check" docker compose exec -T api python3 -m compileall -q /app/backend 2>/dev/null && pass "FastAPI compileall" || fail "FastAPI compileall" echo "=== Résultat ===" if [[ "$FAILURES" -eq 0 ]]; then echo "AUDIT PASS (les WARN nécessitent une revue humaine)"; exit 0; fi echo "AUDIT FAIL: $FAILURES contrôle(s) en échec" exit 1