Aller au contenu

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.


ServiceSLISLO MVPSLO V2Window
sanctions-svc (screening live)latence p95 pipeline complète≤ 200 ms≤ 100 ms30 j glissant
sanctions-svcdisponibilité 5xx-free99.5 %99.9 %30 j
sanctions-etl-svcfreshness max source publique≤ 28 h≤ 8 hpar source
sanctions-etl-svcreindex full publiques durée≤ 60 min≤ 15 minhebdo
audit-svcchain integrity rate100 %100 %strict
OpenSearchheap usage moyen≤ 75 %≤ 60 %24 h glissant
OpenSearchquery latence p99≤ 50 ms≤ 25 ms5 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.


- 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 }
- 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 }
- 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 }
- 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 }

MétriqueTypeLabelsDescription
sanctions_screening_totalcountertenantId, decisionnombre total de screenings
sanctions_screening_duration_secondshistogramtenantId, phase=broad|rerank|totallatence pipeline par phase
sanctions_screening_decision_totalcountertenantId, decisionbreakdown par typologie de hit
sanctions_match_fp_ratiogaugetenantId, window=24h|7d|30dtaux faux positifs (basé sur agent override)
sanctions_match_fn_ratiogaugetenantId, windowtaux faux négatifs (basé sur audits a posteriori)
sanctions_etl_last_success_timestamp_secondsgaugesourcetimestamp Unix du dernier sync OK
sanctions_etl_run_duration_secondshistogramsource, kind=full|deltadurée des runs ETL
sanctions_etl_entries_indexed_totalcountersource, kindnb d’entries ingérées
sanctions_etl_snapshot_failures_totalcountersourceéchecs MinIO
sanctions_audit_chain_integrity_ratiogaugetenantId1.0 si chaîne intacte, < 1.0 sinon
sanctions_audit_events_totalcountertenantId, eventTypeevents audit append-only
sanctions_cache_hit_total / _miss_totalcountertenantIdcache 24h sur (queryNorm, listVersion)
opensearch_*diversexposées par OpenSearch exporter

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) :

  1. 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
  2. Inspecter logs ETL :

    Fenêtre de terminal
    kubectl logs -n vitakyc deploy/sanctions-etl-svc --tail=500 | grep -E "OFAC_SDN|FATAL|ERROR"
  3. Vérifier l’espace disque et MinIO :

    Fenêtre de terminal
    kubectl exec -n vitakyc minio-0 -- df -h /data
    kubectl 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 logs montre XmlPullParserException → 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.


Symptômes : alerte OpenSearchHeapHigh ; UI 12.5 montre heap 87 % ; latence broad search dégrade.

Vérifications :

  1. 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
  2. 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*
  3. 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 embedded saturé : suggérer au tenant de migrer vers external (cf §5.4).

Symptômes : alerte SanctionsAuditChainBroken (P1, pager immédiat) ; UI 12.4 affiche bandeau rouge “Tampering détecté à event #N”.

Procédure CRITIQUE :

  1. 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
  2. Notifier : MLRO tenant + DSI tenant + compliance@vitakyc.io + DPO.

  3. Identifier l’event rompu :

    SELECT event_id, screening_id, occurred_at, hash, previous_event_hash
    FROM screening_audit
    WHERE tenant_id = $1 ORDER BY occurred_at;

    Reconstruire la chaîne en SQL et trouver le premier décrochage.

  4. Investigation :

    • Vérifier les logs Postgres audit_log du DBA : who/when/what sur cette table.
    • Vérifier l’intégrité MinIO snapshot : mc cat sanctions/{tenantId}/{listVersion}.tar.gz | sha256sum vs hash en screening_audit.
    • Vérifier les signatures Ed25519 par tenant key (clé privée Vault).
  5. 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)
  6. 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)
  7. Communication : rapport d’incident BCT obligatoire sous 72 h (LCB-FT 2017-08).


Symptômes : alerte SnapshotMinioFailureRecent ; UI 12.1 indique snapshot manquant pour la dernière listVersion.

Vérifications :

  1. Connectivité MinIO :

    Fenêtre de terminal
    kubectl exec -n vitakyc sanctions-etl-svc-0 -- mc ls minio/sanctions/
  2. Espace disque MinIO :

    Fenêtre de terminal
    kubectl exec -n vitakyc minio-0 -- df -h /data
  3. 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

Symptômes : alerte SanctionsCronLate ; reindex DJ qui dure > 90 min.

Vérifications :

  1. Bulk queue OpenSearch :

    Fenêtre de terminal
    kubectl exec opensearch-0 -- curl -s localhost:9200/_cat/thread_pool/write?v
  2. Refresh interval (devrait être passé à -1 pendant indexation puis remis) :

    Fenêtre de terminal
    kubectl exec opensearch-0 -- curl -s localhost:9200/sanctions_*/_settings?pretty | grep refresh_interval
  3. 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.

Symptômes : EU CFSP en stale > 24 h, attempts 503 répétés.

Procédure :

  1. 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
  2. Tester avec curl directement :

    Fenêtre de terminal
    curl -m 10 -H "Authorization: Bearer $EU_TOKEN" https://webgate.ec.europa.eu/fsd/fsf
  3. 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_AGG est bien healthy.

  4. Si > 72 h sans EU CFSP direct, prévenir compliance pour qu’il documente le fallback OpenSanctions dans son registre LCB-FT.


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 :

  1. Vault disponible :

    Fenêtre de terminal
    kubectl exec sanctions-svc-0 -- vault status
  2. Auth path actif pour le tenant :

    Fenêtre de terminal
    vault read auth/oidc/role/sanctions-publisher-{tenantId}
  3. Logs sanctions-svc au 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)

Symptômes : pod sanctions-etl-svc redémarre en OOMKilled ; logs montrent OutOfMemoryError.

Vérifications :

  1. Source en cause (logs avant kill) :

    Fenêtre de terminal
    kubectl logs sanctions-etl-svc-0 --previous | tail -200
  2. 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_OPTS
    value: "-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 OfacFeedParser qui historiquement a été DOM)
  • Si reproductible : mettre Heap Dump on OOM + analyser avec MAT

Fenêtre de terminal
# 1. Lister les snapshots disponibles
mc ls minio/sanctions/{tenantId}/
# 2. Télécharger le snapshot
mc cp minio/sanctions/{tenantId}/v2026-04-26.tar.gz /tmp/
# 3. Restaurer dans un index temporaire
kubectl 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 atomique
kubectl exec sanctions-svc-0 -- /scripts/switch-alias.sh \
--alias sanctions_{tenantId} \
--to sanctions_{tenantId}_v2026-04-26
Fenêtre de terminal
# Lister les versions
curl -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/audit

Le reactivate exige aussi dual-control. Pas de bypass.

Fenêtre de terminal
# 1. Stop l'ingestion temporairement
kubectl scale deploy sanctions-etl-svc --replicas=0
# 2. Drop l'index corrompu
kubectl exec opensearch-0 -- curl -X DELETE \
localhost:9200/sanctions_{tenantId}_{listVersion}
# 3. Re-déclencher full reindex
kubectl scale deploy sanctions-etl-svc --replicas=1
kubectl exec sanctions-etl-svc-0 -- /scripts/trigger-full-reindex.sh {tenantId}
# 4. Surveiller
watch -n 5 'kubectl exec opensearch-0 -- curl -s localhost:9200/_cat/indices/sanctions_{tenantId}_*?v'

Si un tenant veut bouger d’un OpenSearch embedded vers un cluster existant :

  1. Provisionner le cluster external : 3 nœuds, 16 GB heap chacun, plugins ICU + phonetic installés.

  2. Configurer la connectivité : mTLS entre sanctions-svc et le nouveau cluster (cert pinning).

  3. Snapshot complet depuis embedded :

    Fenêtre de terminal
    kubectl exec sanctions-svc-embedded-0 -- /scripts/full-snapshot.sh
  4. 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
  5. 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
  6. Vérifier : screen un cas test connu, comparer le verdict (doit être identique).

  7. Décommissionner l’embedded après 7 jours sans incident.


DashboardURL interneAudience
Sanctions overview (KPIs métier)/d/sanctions-overviewDSI tenant + compliance + SRE
Sanctions latency drilldown (par phase)/d/sanctions-latencySRE
OpenSearch cluster health/d/opensearch-{tenantId}SRE
ETL run history (par source)/d/sanctions-etlSRE + compliance
Audit chain integrity/d/sanctions-auditDSI tenant + compliance + DPO
False positives / negatives trend (30 j)/d/sanctions-fp-fncompliance + product

JSON des dashboards sourcés dans vitakyc-grafana-dashboards repo, déployés via Helm chart.


SévéritéRéponse ciblePagerEscalation L2
P1 critique< 15 minPagerDuty SRE on-call (24/7)si non-résolu en 30 min : VP Engineering
P2 élevé< 1 hPagerDuty SRE on-call (heures ouvrées + nuit semaine)si non-résolu en 4 h : Engineering Lead
P3 medium< 4 hSlack/Teams #sre-alertssi non-résolu en 24 h : ticket Jira normal
P4 info< 24 hlog onlyreview hebdo équipe

Pour incident BCT-relevant (audit chain rompue, tampering, fuite) : escalation immédiate parallèle vers DPO + compliance@vitakyc.io.


Tout incident P1/P2 fait l’objet d’un post-mortem dans les 5 jours ouvrés :

  1. Résumé : 2-3 phrases, durée, impact (nb screenings dégradés, nb tenants touchés)
  2. Timeline : événements précis avec timestamps UTC
  3. Cause racine : technique + processus
  4. Détection : comment l’incident a été détecté (alerte, user report) ; était-il détectable avant impact ?
  5. Mitigation : étapes prises pour résoudre
  6. Action items : tâches préventives (ouvrir ticket Jira) + observabilité (ajouter alerte si manquante)
  7. Lessons learned : non-blameless, technique uniquement

Template Markdown : vitakyc-runbooks/templates/postmortem.md.



Runbook SRE Sanctions screening — version 1.0 (2026-04-27). Mises à jour à chaque incident significatif.