feat(defenseurs): GET /defenseurs/state serves findings-state.json #28
2 changed files with 27 additions and 1 deletions
|
|
@ -67,7 +67,7 @@ curl -H "Authorization: Bearer $(cat ~/.coolify-token)" \
|
||||||
- `__tests__/hosts.test.js` — matrice exhaustive des endpoints postes (155 cas : regex de route comme controle anti-traversee, 401 avant 403, cloisonnement des deux tokens, 503 fail-closed, reconstruction par liste blanche, 413, `neverSeen`, fraicheur serveur sous horloge figee, degradation de lecture)
|
- `__tests__/hosts.test.js` — matrice exhaustive des endpoints postes (155 cas : regex de route comme controle anti-traversee, 401 avant 403, cloisonnement des deux tokens, 503 fail-closed, reconstruction par liste blanche, 413, `neverSeen`, fraicheur serveur sous horloge figee, degradation de lecture)
|
||||||
- `__tests__/agent.test.js` — agent poste (50 cas : contrat de payload verifie contre `sanitizeSnapshot()`, une seule tentative sans rejeu, timeout, codes de sortie, wrapper shell, et **le token absent de toute sortie d'echec, fragments de 5 caracteres compris**)
|
- `__tests__/agent.test.js` — agent poste (50 cas : contrat de payload verifie contre `sanitizeSnapshot()`, une seule tentative sans rejeu, timeout, codes de sortie, wrapper shell, et **le token absent de toute sortie d'echec, fragments de 5 caracteres compris**)
|
||||||
- Runtime reste 0-dep ; vitest en devDep uniquement
|
- Runtime reste 0-dep ; vitest en devDep uniquement
|
||||||
- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (41 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis.
|
- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (45 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, le fichier servi octet pour octet par `/defenseurs/state`, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
|
|
|
||||||
26
test-curl.sh
26
test-curl.sh
|
|
@ -126,11 +126,18 @@ JSON
|
||||||
# be writable — the same requirement the /data/hosts mount carries in prod.
|
# be writable — the same requirement the /data/hosts mount carries in prod.
|
||||||
mkdir -p "$TMP_DIR/hosts"
|
mkdir -p "$TMP_DIR/hosts"
|
||||||
|
|
||||||
|
# Sergent findings state, served verbatim by GET /defenseurs/state. The spacing
|
||||||
|
# is deliberately non-canonical: a server that parsed and re-serialised the file
|
||||||
|
# would not reproduce it byte for byte.
|
||||||
|
printf '%s\n' '{ "schemaVersion":1, "findings": {"a-deps-1": {"firstSeen":"2026-09-01T00:00:00.000Z"}},"history":[] }' \
|
||||||
|
> "$TMP_DIR/findings-state.json"
|
||||||
|
|
||||||
# Boot the server with the temp REPORTS_DIR and HOSTS_DIR.
|
# Boot the server with the temp REPORTS_DIR and HOSTS_DIR.
|
||||||
PORT=3099 \
|
PORT=3099 \
|
||||||
HEALTH_TOKEN="$TOKEN" \
|
HEALTH_TOKEN="$TOKEN" \
|
||||||
REPORTS_DIR="$TMP_DIR/reports" \
|
REPORTS_DIR="$TMP_DIR/reports" \
|
||||||
HOSTS_DIR="$TMP_DIR/hosts" \
|
HOSTS_DIR="$TMP_DIR/hosts" \
|
||||||
|
DEFENSEURS_FINDINGS_STATE_PATH="$TMP_DIR/findings-state.json" \
|
||||||
HOSTS_ALLOWED_IDS="thinkpad,workbench" \
|
HOSTS_ALLOWED_IDS="thinkpad,workbench" \
|
||||||
HOSTS_INGEST_TOKEN="$INGEST_TOKEN" \
|
HOSTS_INGEST_TOKEN="$INGEST_TOKEN" \
|
||||||
LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \
|
LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \
|
||||||
|
|
@ -145,6 +152,7 @@ PORT=3098 \
|
||||||
HEALTH_TOKEN="$TOKEN" \
|
HEALTH_TOKEN="$TOKEN" \
|
||||||
REPORTS_DIR="$TMP_DIR/reports" \
|
REPORTS_DIR="$TMP_DIR/reports" \
|
||||||
HOSTS_DIR="$TMP_DIR/hosts-noingest" \
|
HOSTS_DIR="$TMP_DIR/hosts-noingest" \
|
||||||
|
DEFENSEURS_FINDINGS_STATE_PATH="$TMP_DIR/absent-findings-state.json" \
|
||||||
HOSTS_ALLOWED_IDS="thinkpad" \
|
HOSTS_ALLOWED_IDS="thinkpad" \
|
||||||
LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \
|
LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \
|
||||||
node "$(dirname "$0")/index.js" >/dev/null 2>&1 &
|
node "$(dirname "$0")/index.js" >/dev/null 2>&1 &
|
||||||
|
|
@ -444,6 +452,24 @@ code=$(http_code -X POST -H "Authorization: Bearer $INGEST_TOKEN" -H "Content-Ty
|
||||||
code=$(http_code -H "Authorization: Bearer $TOKEN" "$NOINGEST_URL/hosts")
|
code=$(http_code -H "Authorization: Bearer $TOKEN" "$NOINGEST_URL/hosts")
|
||||||
[[ "$code" == "200" ]] && pass "no HOSTS_INGEST_TOKEN -> GET /hosts still 200" || fail "read path on fail-closed instance -> got $code"
|
[[ "$code" == "200" ]] && pass "no HOSTS_INGEST_TOKEN -> GET /hosts still 200" || fail "read path on fail-closed instance -> got $code"
|
||||||
|
|
||||||
|
# Case 32 : GET /defenseurs/state serves findings-state.json byte for byte.
|
||||||
|
# Compared through files so the trailing newline is part of the comparison
|
||||||
|
# (command substitution would strip it).
|
||||||
|
http_body -H "Authorization: Bearer $TOKEN" "$BASE_URL/defenseurs/state" > "$TMP_DIR/state-served.json"
|
||||||
|
cmp -s "$TMP_DIR/state-served.json" "$TMP_DIR/findings-state.json" \
|
||||||
|
&& pass "GET /defenseurs/state -> file served byte for byte" \
|
||||||
|
|| fail "GET /defenseurs/state -> body differs from the file"
|
||||||
|
# Absent file -> 200 no_data, the same contract as /defenseurs.
|
||||||
|
body=$(http_body -H "Authorization: Bearer $TOKEN" "$NOINGEST_URL/defenseurs/state")
|
||||||
|
[[ "$body" == '{"status":"no_data"}' ]] \
|
||||||
|
&& pass "GET /defenseurs/state, file absent -> no_data" \
|
||||||
|
|| fail "GET /defenseurs/state absent -> $body"
|
||||||
|
code=$(http_code "$BASE_URL/defenseurs/state")
|
||||||
|
[[ "$code" == "401" ]] && pass "GET /defenseurs/state no-auth -> 401" || fail "GET /defenseurs/state no-auth -> got $code"
|
||||||
|
# A read route: the ingest token must not open it.
|
||||||
|
code=$(http_code -H "Authorization: Bearer $INGEST_TOKEN" "$BASE_URL/defenseurs/state")
|
||||||
|
[[ "$code" == "401" ]] && pass "GET /defenseurs/state with ingest token -> 401" || fail "GET /defenseurs/state ingest token -> got $code"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||||
[[ "$FAIL" == "0" ]] || exit 1
|
[[ "$FAIL" == "0" ]] || exit 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue