Runbook SRE — Sanctions screening
Audience : SRE on-call VitaKYC (T1/T2), DSI banque tenant, support compliance.
Modules surveillés :
sanctions-svc,sanctions-etl-svc,audit-svc, OpenSearch (embedded ou external), MinIO (snapshots).Pages liées : Sanctions screening, Sanctions admin UI, ADR-030.
Ce runbook couvre les 8 incidents les plus fréquents sur le module sanctions screening avec procédures pas-à-pas. Lecture obligatoire avant prise on-call.
1. SLO / SLI
Section intitulée « 1. SLO / SLI »| Service | SLI | SLO MVP | SLO V2 | Window |
|---|---|---|---|---|
sanctions-svc (screening live) | latence p95 pipeline complète | ≤ 200 ms | ≤ 100 ms | 30 j glissant |
sanctions-svc | disponibilité 5xx-free | 99.5 % | 99.9 % | 30 j |
sanctions-etl-svc | freshness max source publique | ≤ 28 h | ≤ 8 h | par source |
sanctions-etl-svc | reindex full publiques durée | ≤ 60 min | ≤ 15 min | hebdo |
audit-svc | chain integrity rate | 100 % | 100 % | strict |
| OpenSearch | heap usage moyen | ≤ 75 % | ≤ 60 % | 24 h glissant |
| OpenSearch | query latence p99 | ≤ 50 ms | ≤ 25 ms | 5 min |
Error budget mensuel pour latence pipeline 200 ms p95 : 0.5 % du trafic, soit max ~360 screenings sur 8400 / mois en dépassement avant violation SLO.
2. Alertes Prometheus (config rules.yaml)
Section intitulée « 2. Alertes Prometheus (config rules.yaml) »2.1 Critique — pager direct (P1)
Section intitulée « 2.1 Critique — pager direct (P1) »- alert: SanctionsAuditChainBroken expr: sanctions_audit_chain_integrity_ratio < 1.0 for: 1m labels: { severity: critical, team: sre } annotations: summary: "Audit chain rompue — tampering possible" runbook: "https://vitakyc.docs.e-vitalis.com/operations/sanctions-monitoring/#43-audit-chain-rompue"
- alert: SanctionsServiceDown expr: up{job="sanctions-svc"} == 0 for: 2m labels: { severity: critical, team: sre }
- alert: OpenSearchClusterRed expr: opensearch_cluster_status{color="red"} == 1 for: 1m labels: { severity: critical, team: sre }2.2 Élevé — pager différé 15 min (P2)
Section intitulée « 2.2 Élevé — pager différé 15 min (P2) »- alert: SanctionsLatencyHigh expr: histogram_quantile(0.95, rate(sanctions_screening_duration_seconds_bucket[5m])) > 0.500 for: 10m labels: { severity: high, team: sre }
- alert: SanctionsSourceErrorMoreThan6h expr: time() - sanctions_etl_last_success_timestamp_seconds > 21600 for: 5m labels: { severity: high, team: sre } annotations: summary: "Source {{ $labels.source }} en erreur depuis > 6 h"
- alert: OpenSearchHeapHigh expr: opensearch_jvm_memory_heap_used_percent > 85 for: 5m labels: { severity: high, team: sre }2.3 Medium — Slack/Teams uniquement (P3)
Section intitulée « 2.3 Medium — Slack/Teams uniquement (P3) »- alert: SanctionsSourceStale expr: time() - sanctions_etl_last_success_timestamp_seconds > {cadence_max_threshold} for: 30m labels: { severity: medium, team: sre }
- alert: SanctionsFalsePositiveRateAboveTarget expr: sanctions_match_fp_ratio_30d > 0.05 for: 1h labels: { severity: medium, team: compliance } annotations: summary: "Taux FP > 5 % sur 30 j — recalibration recommandée"
- alert: SnapshotMinioFailureRecent expr: increase(sanctions_etl_snapshot_failures_total[24h]) > 0 labels: { severity: medium, team: sre }
- alert: SanctionsCronLate expr: time() - sanctions_etl_run_last_attempt_timestamp_seconds{result="success"} > sanctions_etl_run_expected_interval_seconds * 1.5 for: 15m labels: { severity: medium, team: sre }2.4 Info — log only (P4)
Section intitulée « 2.4 Info — log only (P4) »- alert: SanctionsHighThroughput expr: rate(sanctions_screening_total[5m]) > 200 labels: { severity: info, team: sre }
- alert: SanctionsCacheHitRateLow expr: rate(sanctions_cache_miss_total[15m]) / rate(sanctions_screening_total[15m]) > 0.30 labels: { severity: info, team: sre }3. Métriques Prometheus exposées
Section intitulée « 3. Métriques Prometheus exposées »| Métrique | Type | Labels | Description |
|---|---|---|---|
sanctions_screening_total | counter | tenantId, decision | nombre total de screenings |
sanctions_screening_duration_seconds | histogram | tenantId, phase=broad|rerank|total | latence pipeline par phase |
sanctions_screening_decision_total | counter | tenantId, decision | breakdown par typologie de hit |
sanctions_match_fp_ratio | gauge | tenantId, window=24h|7d|30d | taux faux positifs (basé sur agent override) |
sanctions_match_fn_ratio | gauge | tenantId, window | taux faux négatifs (basé sur audits a posteriori) |
sanctions_etl_last_success_timestamp_seconds | gauge | source | timestamp Unix du dernier sync OK |
sanctions_etl_run_duration_seconds | histogram | source, kind=full|delta | durée des runs ETL |
sanctions_etl_entries_indexed_total | counter | source, kind | nb d’entries ingérées |
sanctions_etl_snapshot_failures_total | counter | source | échecs MinIO |
sanctions_audit_chain_integrity_ratio | gauge | tenantId | 1.0 si chaîne intacte, < 1.0 sinon |
sanctions_audit_events_total | counter | tenantId, eventType | events audit append-only |
sanctions_cache_hit_total / _miss_total | counter | tenantId | cache 24h sur (queryNorm, listVersion) |
opensearch_* | divers | — | exposées par OpenSearch exporter |
4. Troubleshoot guide — 8 incidents typiques
Section intitulée « 4. Troubleshoot guide — 8 incidents typiques »4.1 Cron OFAC SDN échec ou retard
Section intitulée « 4.1 Cron OFAC SDN échec ou retard »Symptômes : alerte SanctionsSourceErrorMoreThan6h{source="OFAC_SDN"} ; sur l’UI 12.1 source en error ou stale ; last_success_timestamp_seconds > 6h.
Vérifications (en ordre) :
-
Tester l’URL source manuellement :
Fenêtre de terminal curl -I -m 10 https://www.treasury.gov/ofac/downloads/sdn.xml- 200 : la source est joignable, problème côté ETL
- 503 / 504 : OFAC est temporairement down (occasionnel le week-end)
- timeout : firewall sortant tenant ou DNS
-
Inspecter logs ETL :
Fenêtre de terminal kubectl logs -n vitakyc deploy/sanctions-etl-svc --tail=500 | grep -E "OFAC_SDN|FATAL|ERROR" -
Vérifier l’espace disque et MinIO :
Fenêtre de terminal kubectl exec -n vitakyc minio-0 -- df -h /datakubectl exec -n vitakyc minio-0 -- mc admin info local
Procédure de résolution :
- Source 503 OFAC : attendre le retry suivant (auto-retry 6h plus tard). Si > 24h sans amélioration, escalader équipe data engineering.
- Erreur parsing XML :
kubectl logsmontreXmlPullParserException→ l’ETL a parsé un XML mal formé. Restaurer le snapshot précédent depuis MinIO :Fenêtre de terminal kubectl exec -n vitakyc sanctions-etl-svc-0 -- /scripts/restore-snapshot.sh OFAC_SDN v2026-04-26 - Disque saturé : nettoyer les anciens snapshots > 10 ans (rétention WORM minimum) :
Fenêtre de terminal kubectl exec -n vitakyc sanctions-etl-svc-0 -- /scripts/cleanup-snapshots.sh --older-than 3650d --dry-run - Force refresh manuel depuis UI 12.1 ou :
Fenêtre de terminal curl -X POST -H "Authorization: Bearer $TOKEN" \https://vitakyc-api.tenant.tn/v1/sanctions/lists/OFAC_SDN/refresh
Communication : si > 12 h, prévenir le compliance officer du tenant via email automatique (déjà configuré). Si > 48 h, statut page interne.
4.2 OpenSearch heap saturé > 85 %
Section intitulée « 4.2 OpenSearch heap saturé > 85 % »Symptômes : alerte OpenSearchHeapHigh ; UI 12.5 montre heap 87 % ; latence broad search dégrade.
Vérifications :
-
Identifier la cause :
Fenêtre de terminal kubectl exec -n vitakyc opensearch-0 -- curl -s localhost:9200/_nodes/stats/jvm | jq '.nodes[].jvm.mem'kubectl exec -n vitakyc opensearch-0 -- curl -s localhost:9200/_cat/segments?v -
Vérifier les requêtes lourdes en cours :
Fenêtre de terminal kubectl exec -n vitakyc opensearch-0 -- curl -s localhost:9200/_tasks?detailed=true&actions=*search* -
Vérifier l’indexation :
Fenêtre de terminal kubectl exec -n vitakyc opensearch-0 -- curl -s localhost:9200/_cat/indices?v&s=ss:desc | head -10
Procédure :
- Pic d’indexation en cours (reindex hebdo) : laisser finir (max 60 min). Surveiller. Pas d’action sauf si > 90 min.
- Mémoire fuite (heap monte sans baisse) : redémarrer le pod (rolling restart si cluster) :
Fenêtre de terminal kubectl rollout restart -n vitakyc statefulset/opensearch - Trop d’index actifs : supprimer les index
sanctions_replay_*orphelins (> 24h) :Fenêtre de terminal kubectl exec -n vitakyc sanctions-svc-0 -- /scripts/cleanup-replay-indices.sh - Mode
embeddedsaturé : suggérer au tenant de migrer versexternal(cf §5.4).
4.3 Audit chain rompue ⚠ CRITIQUE
Section intitulée « 4.3 Audit chain rompue ⚠ CRITIQUE »Symptômes : alerte SanctionsAuditChainBroken (P1, pager immédiat) ; UI 12.4 affiche bandeau rouge “Tampering détecté à event #N”.
Procédure CRITIQUE :
-
Verrouiller immédiatement le tenant (read-only sanctions-svc) :
Fenêtre de terminal curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \https://vitakyc-internal/admin/tenants/{tenantId}/lock?reason=audit_tampering -
Notifier : MLRO tenant + DSI tenant + compliance@vitakyc.io + DPO.
-
Identifier l’event rompu :
SELECT event_id, screening_id, occurred_at, hash, previous_event_hashFROM screening_auditWHERE tenant_id = $1 ORDER BY occurred_at;Reconstruire la chaîne en SQL et trouver le premier décrochage.
-
Investigation :
- Vérifier les logs Postgres
audit_logdu DBA :who/when/whatsur cette table. - Vérifier l’intégrité MinIO snapshot :
mc cat sanctions/{tenantId}/{listVersion}.tar.gz | sha256sumvs hash enscreening_audit. - Vérifier les signatures Ed25519 par tenant key (clé privée Vault).
- Vérifier les logs Postgres
-
Causes possibles :
- Modification directe DB par opérateur → faute opérationnelle, escalation RH + compliance
- Bug applicatif (très rare, déjà arrivé sur upgrade) → rollback version
sanctions-svc - Corruption disque Postgres → restore from PITR (point-in-time recovery)
-
Restoration : ne jamais réécrire l’event rompu. Soit :
- On accepte le tampering documenté (rapport BCT obligatoire) et on annote
tampering_acknowledged_at - On restaure Postgres depuis PITR au moment précédant la rupture (perte des screenings entre temps, à rejouer manuellement avec audit complet)
- On accepte le tampering documenté (rapport BCT obligatoire) et on annote
-
Communication : rapport d’incident BCT obligatoire sous 72 h (LCB-FT 2017-08).
4.4 Snapshot MinIO échec
Section intitulée « 4.4 Snapshot MinIO échec »Symptômes : alerte SnapshotMinioFailureRecent ; UI 12.1 indique snapshot manquant pour la dernière listVersion.
Vérifications :
-
Connectivité MinIO :
Fenêtre de terminal kubectl exec -n vitakyc sanctions-etl-svc-0 -- mc ls minio/sanctions/ -
Espace disque MinIO :
Fenêtre de terminal kubectl exec -n vitakyc minio-0 -- df -h /data -
Permissions :
Fenêtre de terminal kubectl exec -n vitakyc sanctions-etl-svc-0 -- mc admin policy attach minio sanctions-etl --user sanctions-etl-svc
Procédure :
- Disque saturé : appliquer la rétention agressive (10 ans = minimum WORM, on conserve plus en pratique → trim à 12 ans pour libérer)
- Permissions : re-attacher policy
- MinIO down : rolling restart, vérifier replica si cluster
- Re-snapshoter manuellement :
kubectl exec sanctions-etl-svc-0 -- /scripts/snapshot.sh OFAC_SDN v2026-04-27.a
4.5 Indexation OpenSearch pending / lente
Section intitulée « 4.5 Indexation OpenSearch pending / lente »Symptômes : alerte SanctionsCronLate ; reindex DJ qui dure > 90 min.
Vérifications :
-
Bulk queue OpenSearch :
Fenêtre de terminal kubectl exec opensearch-0 -- curl -s localhost:9200/_cat/thread_pool/write?v -
Refresh interval (devrait être passé à
-1pendant indexation puis remis) :Fenêtre de terminal kubectl exec opensearch-0 -- curl -s localhost:9200/sanctions_*/_settings?pretty | grep refresh_interval -
Logs ETL :
Fenêtre de terminal kubectl logs sanctions-etl-svc-0 | grep -i "bulk"
Procédure :
- Si refresh_interval pas réglé sur
-1: appliquer manuellement, attendre fin indexation, remettre à1s. - Si Dow Jones > 90 min : vérifier shards (devrait être 1 primary, 1 replica) et compute :
Fenêtre de terminal kubectl top pod opensearch-0 - Si CPU saturé : passer à 2 shards primary + reindex (downtime ~ heures) — décision à prendre avec product.
4.6 Source EU CFSP retourne 503
Section intitulée « 4.6 Source EU CFSP retourne 503 »Symptômes : EU CFSP en stale > 24 h, attempts 503 répétés.
Procédure :
-
Vérifier que le token API EU est valide (renouvelable annuellement à webgate.ec.europa.eu) :
Fenêtre de terminal kubectl get secret eu-cfsp-token -o yaml | base64 -d -
Tester avec curl directement :
Fenêtre de terminal curl -m 10 -H "Authorization: Bearer $EU_TOKEN" https://webgate.ec.europa.eu/fsd/fsf -
Si 503 persistant côté EU (occasionnel) : pas d’action, fallback acceptable est l’OpenSanctions agrégateur qui inclut EU CFSP — vérifier que
OPEN_SANCTIONS_AGGest bienhealthy. -
Si > 72 h sans EU CFSP direct, prévenir compliance pour qu’il documente le fallback OpenSanctions dans son registre LCB-FT.
4.7 Vault sig manquante (publish policy bloqué)
Section intitulée « 4.7 Vault sig manquante (publish policy bloqué) »Symptômes : agent essaie de publier policy via UI 12.3, le 2e signataire ne reçoit pas la notification ou la signature échoue.
Vérifications :
-
Vault disponible :
Fenêtre de terminal kubectl exec sanctions-svc-0 -- vault status -
Auth path actif pour le tenant :
Fenêtre de terminal vault read auth/oidc/role/sanctions-publisher-{tenantId} -
Logs
sanctions-svcau moment de la tentative :Fenêtre de terminal kubectl logs sanctions-svc-0 --since=10m | grep -i "vault\|sign"
Procédure :
- Vault down : escalation infra, rolling restart Vault + unseal
- Permissions tenant manquantes : ré-appliquer la policy
vault policy write sanctions-publisher-{tenantId} ... - Signataire inaccessible : escalation produit (pas de bypass admin, dual-control est obligatoire)
4.8 OOM ETL (build OOM Java heap exceeded)
Section intitulée « 4.8 OOM ETL (build OOM Java heap exceeded) »Symptômes : pod sanctions-etl-svc redémarre en OOMKilled ; logs montrent OutOfMemoryError.
Vérifications :
-
Source en cause (logs avant kill) :
Fenêtre de terminal kubectl logs sanctions-etl-svc-0 --previous | tail -200 -
Volume entries traité :
Fenêtre de terminal kubectl exec sanctions-etl-svc-0 -- du -sh /tmp/ingest/
Procédure :
- Augmenter heap JVM dans
Deployment:env:- name: JAVA_OPTSvalue: "-Xms4g -Xmx8g" - Si Dow Jones (3.8 M entries) : augmenter à 12-16 GB heap
- Streamer le parsing XML au lieu de DOM-load (déjà en place pour les feeds DJ — vérifier la classe
OfacFeedParserqui historiquement a été DOM) - Si reproductible : mettre Heap Dump on OOM + analyser avec MAT
5. Procédures de récupération
Section intitulée « 5. Procédures de récupération »5.1 Restaurer un snapshot OpenSearch depuis MinIO
Section intitulée « 5.1 Restaurer un snapshot OpenSearch depuis MinIO »# 1. Lister les snapshots disponiblesmc ls minio/sanctions/{tenantId}/
# 2. Télécharger le snapshotmc cp minio/sanctions/{tenantId}/v2026-04-26.tar.gz /tmp/
# 3. Restaurer dans un index temporairekubectl exec sanctions-svc-0 -- /scripts/restore-snapshot.sh \ --from /tmp/v2026-04-26.tar.gz \ --to-index sanctions_{tenantId}_v2026-04-26 \ --no-replicas
# 4. Switch alias atomiquekubectl exec sanctions-svc-0 -- /scripts/switch-alias.sh \ --alias sanctions_{tenantId} \ --to sanctions_{tenantId}_v2026-04-265.2 Rollback policy à la version précédente
Section intitulée « 5.2 Rollback policy à la version précédente »# Lister les versionscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ https://vitakyc-api.tenant.tn/v1/sanctions/policies?status=ARCHIVED
# Réactiver v1.3 (était la version précédente)curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ https://vitakyc-api.tenant.tn/v1/sanctions/policies/v1.3/reactivate
# Vérifier l'audit log signécurl -H "Authorization: Bearer $ADMIN_TOKEN" \ https://vitakyc-api.tenant.tn/v1/sanctions/policies/v1.3/auditLe reactivate exige aussi dual-control. Pas de bypass.
5.3 Force-rebuild d’un index OpenSearch
Section intitulée « 5.3 Force-rebuild d’un index OpenSearch »# 1. Stop l'ingestion temporairementkubectl scale deploy sanctions-etl-svc --replicas=0
# 2. Drop l'index corrompukubectl exec opensearch-0 -- curl -X DELETE \ localhost:9200/sanctions_{tenantId}_{listVersion}
# 3. Re-déclencher full reindexkubectl scale deploy sanctions-etl-svc --replicas=1kubectl exec sanctions-etl-svc-0 -- /scripts/trigger-full-reindex.sh {tenantId}
# 4. Surveillerwatch -n 5 'kubectl exec opensearch-0 -- curl -s localhost:9200/_cat/indices/sanctions_{tenantId}_*?v'5.4 Migration embedded → external
Section intitulée « 5.4 Migration embedded → external »Si un tenant veut bouger d’un OpenSearch embedded vers un cluster existant :
-
Provisionner le cluster external : 3 nœuds, 16 GB heap chacun, plugins ICU + phonetic installés.
-
Configurer la connectivité : mTLS entre
sanctions-svcet le nouveau cluster (cert pinning). -
Snapshot complet depuis embedded :
Fenêtre de terminal kubectl exec sanctions-svc-embedded-0 -- /scripts/full-snapshot.sh -
Restore dans le cluster external :
Fenêtre de terminal curl -X POST -H "Authorization: Bearer $TOKEN" \https://external-cluster:9200/_snapshot/sanctions/snap_2026-04-27/_restore -
Switch feature flag tenant :
Fenêtre de terminal curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN" \-d '{"deployment":"external","cluster_url":"https://external-cluster:9200"}' \https://vitakyc-api.tenant.tn/v1/sanctions/config -
Vérifier : screen un cas test connu, comparer le verdict (doit être identique).
-
Décommissionner l’embedded après 7 jours sans incident.
6. Dashboards Grafana
Section intitulée « 6. Dashboards Grafana »| Dashboard | URL interne | Audience |
|---|---|---|
| Sanctions overview (KPIs métier) | /d/sanctions-overview | DSI tenant + compliance + SRE |
| Sanctions latency drilldown (par phase) | /d/sanctions-latency | SRE |
| OpenSearch cluster health | /d/opensearch-{tenantId} | SRE |
| ETL run history (par source) | /d/sanctions-etl | SRE + compliance |
| Audit chain integrity | /d/sanctions-audit | DSI tenant + compliance + DPO |
| False positives / negatives trend (30 j) | /d/sanctions-fp-fn | compliance + product |
JSON des dashboards sourcés dans vitakyc-grafana-dashboards repo, déployés via Helm chart.
7. Escalation matrix
Section intitulée « 7. Escalation matrix »| Sévérité | Réponse cible | Pager | Escalation L2 |
|---|---|---|---|
| P1 critique | < 15 min | PagerDuty SRE on-call (24/7) | si non-résolu en 30 min : VP Engineering |
| P2 élevé | < 1 h | PagerDuty SRE on-call (heures ouvrées + nuit semaine) | si non-résolu en 4 h : Engineering Lead |
| P3 medium | < 4 h | Slack/Teams #sre-alerts | si non-résolu en 24 h : ticket Jira normal |
| P4 info | < 24 h | log only | review hebdo équipe |
Pour incident BCT-relevant (audit chain rompue, tampering, fuite) : escalation immédiate parallèle vers DPO + compliance@vitakyc.io.
8. Post-mortem template
Section intitulée « 8. Post-mortem template »Tout incident P1/P2 fait l’objet d’un post-mortem dans les 5 jours ouvrés :
- Résumé : 2-3 phrases, durée, impact (nb screenings dégradés, nb tenants touchés)
- Timeline : événements précis avec timestamps UTC
- Cause racine : technique + processus
- Détection : comment l’incident a été détecté (alerte, user report) ; était-il détectable avant impact ?
- Mitigation : étapes prises pour résoudre
- Action items : tâches préventives (ouvrir ticket Jira) + observabilité (ajouter alerte si manquante)
- Lessons learned : non-blameless, technique uniquement
Template Markdown : vitakyc-runbooks/templates/postmortem.md.
9. Liens internes
Section intitulée « 9. Liens internes »- Sanctions screening — moteur
- Sanctions admin UI
- POC sanctions matcher
- ADR-030
- Runbooks on-call
- Dashboard exécutif
Runbook SRE Sanctions screening — version 1.0 (2026-04-27). Mises à jour à chaque incident significatif.