Aller au contenu principal

Pistes d'Audit Quand les Choses Tournent Mal (Freelance Partie 3)

· 13 minutes de lecture
STOA Team
The STOA Platform Team

Trois semaines après le lancement, un client envoie un email : « Nos données semblent incorrectes. Il s'est passé quelque chose mardi dernier à 3h du matin. »

Vous avez deux réponses possibles :

  • « Laissez-moi vérifier les logs » ← avec une piste d'audit
  • « Je ne sais pas, je vais devoir enquêter » ← sans piste d'audit

Il s'agit de la Partie 3. Nous allons construire la capacité d'audit qui vous permet de répondre de la première manière.

Il s'agit de la Partie 3 de la Série Sécurité API pour Freelances. Partie 1 : Vos APIs sont plus vulnérables que vous ne le pensez | Partie 2 : Stratégies de rate limiting qui fonctionnent vraiment


Ce qu'est vraiment une piste d'audit

Une piste d'audit n'est pas seulement des logs d'accès. C'est l'enregistrement structuré qui répond à :

  • Qui a fait une requête (identité du consommateur, pas seulement l'IP)
  • Quoi ils ont demandé (méthode, chemin, résumé du corps de la requête)
  • Quand c'est arrivé (timestamp, fuseau horaire)
  • Ce qui s'est passé (code de réponse, latence, message d'erreur)
  • Ce qui a changé (avant/après pour les mutations)

Les logs d'accès vous donnent QUI et QUAND. Une piste d'audit correcte vous donne les cinq.

La différence compte quand votre client demande « qui a supprimé notre clé API ? » — les logs d'accès pourraient vous dire qu'une requête DELETE s'est produite à 3h du matin. Une piste d'audit vous dit quel utilisateur, depuis quelle session, avec quels identifiants.


Couche 1 : Journalisation au niveau du gateway (Automatique)

Le gateway de STOA journalise chaque requête proxifiée automatiquement. Aucune configuration nécessaire. Chaque requête à travers le gateway génère un événement d'audit structuré :

{
"event_type": "api_request",
"timestamp": "2026-03-11T03:17:42.123Z",
"request_id": "req_abc123def456",
"consumer_id": "cons_xyz789",
"consumer_name": "my-saas-client",
"tenant_id": "tenant_abc",
"api_id": "api_123",
"method": "POST",
"path": "/api/documents/export",
"status_code": 200,
"latency_ms": 342,
"request_size_bytes": 1024,
"response_size_bytes": 45680,
"gateway_version": "0.1.0",
"datacenter": "fra1"
}

Interrogez-le :

# 50 dernières requêtes
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?limit=50" \
-H "Authorization: Bearer $TOKEN" | jq '.logs[] | {timestamp, consumer_name, method, path, status_code, latency_ms}'

# Toutes les requêtes d'un consommateur spécifique
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?consumer_id=$CONSUMER_ID&limit=100" \
-H "Authorization: Bearer $TOKEN" | jq .

# Toutes les erreurs 5xx dans les 24 dernières heures
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?status_min=500&hours=24" \
-H "Authorization: Bearer $TOKEN" | jq '.logs | length'

# Exporter en CSV pour analyse
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/export/csv?hours=168" \
-H "Authorization: Bearer $TOKEN" > last-7-days.csv

C'est votre première capacité d'audit — zéro configuration, toujours activée.


Couche 2 : Journalisation des événements de sécurité

Au-delà des logs de requêtes, STOA suit séparément les événements liés à la sécurité :

# Événements de sécurité : rotations de clés, échecs d'auth, dépassements de rate limit, changements de politique
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/security?hours=24" \
-H "Authorization: Bearer $TOKEN" | jq '.events[] | {type, timestamp, actor, target, outcome}'

Les événements de sécurité incluent :

Type d'événementCe qui le déclenchePourquoi c'est important
auth_failedClé API invalide ou expiréeTentatives de force brute, clés exposées
rate_limit_exceededLe consommateur atteint la limite de rateScraping, jobs incontrôlés
policy_appliedLa politique a bloqué une requêtePayload trop grand, chemin interdit
key_rotatedLa clé API a été pivotéeAudit de rotation des identifiants
key_revokedLa clé API a été révoquéeDéprovisionnement, réponse à une compromission
consumer_suspendedLe consommateur a été suspenduRéponse à un abus
admin_actionL'admin a changé la configurationPiste d'audit des changements

Filtrez par type :

# Échecs d'auth dans les 24 dernières heures — abus potentiel de clé
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/security?event_type=auth_failed&hours=24" \
-H "Authorization: Bearer $TOKEN" | jq '{
total_failures: (.events | length),
by_consumer: (.events | group_by(.target) | map({consumer: .[0].target, count: length}) | sort_by(.count) | reverse | .[0:5])
}'

Si vous voyez 5 000 échecs d'auth d'un seul consommateur en 24 heures, c'est une clé compromise qui est testée. Révoquez-la immédiatement.


Couche 3 : Journalisation au niveau applicatif (Ce que vous construisez)

Le gateway journalise la couche transport. Vous devez journaliser la couche métier. Les deux sont complémentaires.

Ce que le gateway journalise : qui a appelé quoi, quand, avec quel résultat.

Ce que votre application devrait journaliser : quelle opération métier s'est produite, quelles données ont changé, pourquoi.

Un bon log d'audit au niveau applicatif :

import logging
import json
from datetime import datetime, UTC

audit_logger = logging.getLogger("audit")

def log_audit_event(
action: str,
resource_type: str,
resource_id: str,
actor_id: str,
changes: dict | None = None,
metadata: dict | None = None,
):
"""Entrée de log d'audit structuré pour les événements métier."""
event = {
"timestamp": datetime.now(UTC).isoformat(),
"action": action, # "created", "updated", "deleted", "accessed"
"resource_type": resource_type, # "document", "user", "invoice"
"resource_id": resource_id,
"actor_id": actor_id, # Depuis la clé API / claim JWT
"changes": changes, # {"field": {"before": X, "after": Y}}
"metadata": metadata, # Tout contexte supplémentaire
}
audit_logger.info(json.dumps(event))

# Utilisation dans un gestionnaire de route :
@app.delete("/api/documents/{doc_id}")
async def delete_document(doc_id: str, consumer: Consumer = Depends(get_consumer)):
doc = await get_document(doc_id, consumer)

# Journaliser avant la suppression — vous ne pourrez pas journaliser après
log_audit_event(
action="deleted",
resource_type="document",
resource_id=doc_id,
actor_id=consumer.id,
metadata={
"document_name": doc.name,
"document_size_bytes": doc.size,
"reason": "user_requested"
}
)

await db.delete(doc)
return {"status": "deleted"}

Règle clé pour les mutations : journalisez avant d'exécuter l'opération, pas après. Si l'opération échoue, vous avez quand même l'entrée d'audit. Si vous journalisez après et que le service plante, l'événement est perdu.


Couche 4 : Patterns de requêtes structurées

Les logs bruts sont inutiles sans la capacité de les interroger. Voici les patterns que vous utiliserez vraiment.

Pattern 1 : Reconstruction de la chronologie

« Que s'est-il passé entre 3h et 4h du matin mardi ? »

# Logs du gateway pour la fenêtre
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?from=2026-03-11T03:00:00Z&to=2026-03-11T04:00:00Z&limit=500" \
-H "Authorization: Bearer $TOKEN" | jq '.logs | group_by(.path) | map({
path: .[0].path,
total_requests: length,
errors: map(select(.status_code >= 400)) | length,
consumers: [.[].consumer_name] | unique
})'

Cela vous donne une répartition par endpoint : combien d'appels, combien d'erreurs, quels consommateurs.

Pattern 2 : Profil d'activité d'un consommateur

« Que fait réellement 'user-12345' dans l'API ? »

CONSUMER_ID="cons_xyz789"
START="2026-03-01T00:00:00Z"
END="2026-03-15T23:59:59Z"

curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?consumer_id=$CONSUMER_ID&from=$START&to=$END&limit=1000" \
-H "Authorization: Bearer $TOKEN" | jq '{
total_requests: (.logs | length),
methods: (.logs | group_by(.method) | map({method: .[0].method, count: length})),
top_paths: (.logs | group_by(.path) | map({path: .[0].path, count: length}) | sort_by(.count) | reverse | .[0:10]),
error_rate: (.logs | map(select(.status_code >= 400)) | length) / (.logs | length) * 100,
peak_hour: (.logs | group_by(.timestamp[0:13]) | map({hour: .[0].timestamp[0:13], count: length}) | sort_by(.count) | last)
}'

Ce profil répond à : ce consommateur utilise-t-il l'API comme prévu ? Accède-t-il à des chemins qu'il ne devrait pas ? Son taux d'erreur est-il inhabituellement élevé ?

Pattern 3 : Détection d'anomalies

« S'est-il passé quelque chose d'inhabituel récemment ? »

# Trouver les consommateurs avec des taux d'erreur inhabituellement élevés
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?hours=24&limit=5000" \
-H "Authorization: Bearer $TOKEN" | jq '.logs | group_by(.consumer_id) | map({
consumer: .[0].consumer_name,
total: length,
errors: map(select(.status_code >= 400)) | length,
error_rate: (map(select(.status_code >= 400)) | length) / length * 100
}) | map(select(.error_rate > 10)) | sort_by(.error_rate) | reverse'

Un consommateur avec >10% de taux d'erreur est soit :

  • En train d'intégrer incorrectement (innocent — contactez-le et aidez-le)
  • En train de tester des patterns d'attaque (pas innocent — enquêtez)

La distinction est généralement claire à partir des types d'erreurs : les erreurs 400 sont généralement une mauvaise intégration ; les erreurs 401/403 en volume sont généralement des sondes.

Pattern 4 : Export de données pour la conformité

Certains clients ou industries exigent des exports de pistes d'audit (demandes RGPD, audits SOC 2, investigation d'incidents clients) :

# Exporter l'audit complet sur 30 jours pour un consommateur spécifique
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/export/json?consumer_id=$CONSUMER_ID&days=30" \
-H "Authorization: Bearer $TOKEN" > audit-export-$(date +%Y%m%d).json

# Format CSV pour analyse en tableur
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/export/csv?consumer_id=$CONSUMER_ID&days=30" \
-H "Authorization: Bearer $TOKEN" > audit-export-$(date +%Y%m%d).csv

Réponse aux incidents minimale viable

Vous êtes un développeur solo. Vous n'avez pas de SIEM, de SOC ou d'équipe de réponse aux incidents. Voici ce que vous AVEZ, et comment l'utiliser.

Le script d'investigation en 5 minutes

Quand quelque chose tourne mal, exécutez ceci :

#!/bin/bash
# investigate.sh — triage rapide d'incident API
# Usage : ./investigate.sh [consumer_id] [hours=24]

CONSUMER_ID="${1:-}"
HOURS="${2:-24}"

echo "=== Triage d'incident API (${HOURS}h) ==="
echo "Timestamp : $(date -u)"
echo ""

# 1. Taux d'erreur récent
echo "--- Taux d'erreur ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?hours=$HOURS&limit=5000" \
-H "Authorization: Bearer $TOKEN" | jq '{
total: (.logs | length),
errors_4xx: (.logs | map(select(.status_code >= 400 and .status_code < 500)) | length),
errors_5xx: (.logs | map(select(.status_code >= 500)) | length),
rate_limited: (.logs | map(select(.status_code == 429)) | length)
}'

# 2. Top consommateurs par volume
echo ""
echo "--- Top consommateurs (${HOURS}h) ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?hours=$HOURS&limit=5000" \
-H "Authorization: Bearer $TOKEN" | jq '.logs | group_by(.consumer_id) | map({consumer: .[0].consumer_name, requests: length}) | sort_by(.requests) | reverse | .[0:5]'

# 3. Événements de sécurité
echo ""
echo "--- Événements de sécurité (${HOURS}h) ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/security?hours=$HOURS" \
-H "Authorization: Bearer $TOKEN" | jq '.events | group_by(.event_type) | map({type: .[0].event_type, count: length})'

# 4. Si le consommateur est spécifié, son activité
if [ -n "$CONSUMER_ID" ]; then
echo ""
echo "--- Activité du consommateur $CONSUMER_ID ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?consumer_id=$CONSUMER_ID&hours=$HOURS&limit=500" \
-H "Authorization: Bearer $TOKEN" | jq '{
total_requests: (.logs | length),
error_rate: ((.logs | map(select(.status_code >= 400)) | length) / (.logs | length) * 100 | floor),
top_paths: (.logs | group_by(.path) | map({path: .[0].path, count: length}) | sort_by(.count) | reverse | .[0:5])
}'
fi

Ce script en 5 minutes répond aux questions « que s'est-il passé ? » les plus courantes sans investigation approfondie.

Playbook de réponse aux incidents

Scénario : Possible violation de données

  1. Exécutez ./investigate.sh "" 48 — aperçu des 48 dernières heures
  2. Vérifiez les consommateurs inhabituels : grands volumes de requêtes, taux d'erreur élevés, accès aux chemins sensibles
  3. Exportez les logs pour la fenêtre suspecte : GET /v1/audit/$TENANT_ID/export/json?from=...&to=...
  4. Croisez avec les logs applicatifs (Couche 3)
  5. Si violation confirmée : révoquez immédiatement les clés affectées (POST /v1/subscriptions/$ID/revoke)
  6. Notifiez les consommateurs affectés (RGPD : exigence de notification de 72h dans l'UE)
  7. Documentez la chronologie à partir de la piste d'audit pour votre notification

Scénario : Le client signale une corruption de données

  1. Demandez le timestamp et l'ID de la ressource affectée
  2. GET /v1/audit/$TENANT_ID?hours=72&consumer_id=$THEIR_CONSUMER_ID — leur historique de requêtes
  3. Cherchez l'événement de mutation (DELETE, PUT, POST) près du timestamp signalé
  4. Croisez avec les logs d'audit applicatifs (Couche 3) pour les valeurs before/after
  5. Répondez : « À 03:17:42 UTC, une requête DELETE a été effectuée depuis votre clé API pour la ressource /documents/12345. Voici l'enregistrement d'audit. »

Scénario : Pic inattendu des coûts API

  1. Vérifiez le volume quotidien : GET /v1/quotas/$TENANT_ID/stats
  2. Trouvez les meilleurs consommateurs par volume sur la période de pic
  3. Vérifiez si un consommateur avait un job incontrôlé (taux de requêtes élevé soutenu sans pause)
  4. Appliquez un plafond de volume quotidien si pas encore configuré (voir Partie 2)

Développer l'habitude

Une piste d'audit n'est utile que si vous la consultez régulièrement. Intégrez-la à votre routine hebdomadaire :

# Ajoutez à votre weekly-stoa-review.sh (depuis le runbook Semaine 1)

echo ""
echo "--- Événements de sécurité cette semaine ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID/security?hours=168" \
-H "Authorization: Bearer $TOKEN" | jq '{
auth_failures: (.events | map(select(.event_type == "auth_failed")) | length),
rate_limit_hits: (.events | map(select(.event_type == "rate_limit_exceeded")) | length),
key_rotations: (.events | map(select(.event_type == "key_rotated")) | length),
admin_actions: (.events | map(select(.event_type == "admin_action")) | length)
}'

echo ""
echo "--- Tendance du taux d'erreur (par jour) ---"
curl -s "${STOA_API_URL}/v1/audit/$TENANT_ID?hours=168&limit=10000" \
-H "Authorization: Bearer $TOKEN" | jq '.logs | group_by(.timestamp[0:10]) | map({
date: .[0].timestamp[0:10],
total: length,
errors: map(select(.status_code >= 400)) | length,
error_pct: (map(select(.status_code >= 400)) | length) / length * 100 | floor
})'

Cinq minutes, une fois par semaine. La plupart des problèmes sont visibles dans les données de tendance avant qu'ils ne deviennent des incidents.


L'angle conformité

Si vous construisez pour des clients européens, dans la santé ou la fintech — vous pouvez avoir des exigences de conformité concernant les pistes d'audit.

L'Article 30 du RGPD exige un registre des activités de traitement. Une piste d'audit API qui capture qui a accédé à quelles données personnelles, quand, et à quelle fin est une part significative de votre documentation RGPD.

DORA (Digital Operational Resilience Act) pour les services financiers exige la journalisation et le reporting des incidents TIC. Votre piste d'audit gateway fournit le journal d'événements TIC.

SOC 2 Type II (si vous vendez à des entreprises) exige de démontrer des contrôles d'audit. Votre evidence de piste d'audit (logs exportés) est un artefact direct pour l'audit.

Vous n'avez pas besoin de configurer quoi que ce soit de supplémentaire pour cela. La piste d'audit décrite dans cet article est l'artefact. L'habitude de la consulter hebdomadairement, la capacité de l'exporter à la demande, et le playbook de réponse aux incidents ci-dessus sont ce qui transforme les logs en evidence de conformité.


FAQ

Combien de temps dois-je conserver les logs d'audit ?

RGPD : les logs d'accès aux données devraient être conservés aussi longtemps que vous traitez ces données (typiquement 12-24 mois minimum). DORA : 5 ans pour les logs d'incidents TIC. Recommandation pragmatique : 90 jours chaud (interrogeable via API), 12 mois froid (exports CSV archivés).

Puis-je utiliser la piste d'audit de STOA comme seul enregistrement de conformité ?

Elle couvre la couche gateway. Pour une conformité complète, combinez-la avec les logs d'audit au niveau applicatif (Couche 3). Le gateway vous dit « qui a appelé quoi ». Votre application vous dit « ce qui a changé en conséquence ».

Mon client veut un téléchargement d'audit en libre-service. Comment faire ?

Exposez un endpoint d'administration sécurisé dans votre application qui appelle GET /v1/audit/$TENANT_ID/export/csv?consumer_id={their_id} et retourne le CSV. Le client obtient ses propres enregistrements d'activité ; il n'accède pas aux données des autres consommateurs.

Quelle est la différence entre les logs d'accès et les logs d'audit ?

Les logs d'accès = enregistrements bruts de requêtes/réponses (format Apache/nginx). Les logs d'audit = enregistrements d'événements métier structurés. STOA fournit les deux. Cet article se concentre sur les logs d'audit car ce sont eux qui comptent pour l'investigation de sécurité et la conformité.

Puis-je transmettre les événements d'audit de STOA vers mon SIEM existant ?

STOA émet des événements d'audit vers un topic Kafka (si Kafka est configuré). Vous pouvez consommer depuis ce topic et transmettre vers Datadog, Splunk, OpenSearch ou tout SIEM. Pour la plupart des configurations de freelance, l'approche de requêtes API dans cet article est suffisante sans un pipeline SIEM complet.


Vous avez complété la série

Partie 1 a couvert le paysage des menaces et le 80/20 de ce contre quoi se protéger.

Partie 2 a construit la stratégie de rate limiting qui arrête les attaques les plus courantes.

Partie 3 (cet article) a construit la capacité d'audit qui vous permet d'enquêter quand quelque chose passe quand même.

La combinaison — baseline de sécurité gateway + rate limiting étagé + piste d'audit structurée — voilà à quoi ressemble la sécurité API en production pour les développeurs solo et les petites équipes. Pas d'équipe de sécurité d'entreprise requise.

Prochaines étapes :