Aller au contenu

Observabilité : journal SIEM chaîné par hachage et rapports de rendu

Le module Observability est l’implémentation de l’état d’exécution : un journal d’événements SIEM inviolable chaîné par hachage, l’agrégation des rapports de rendu et de pilote, un journal d’audit HSM et un jeu complet d’implémentations no-op des métriques et des traces, afin que l’instrumentation reste toujours invocable.

Une seule page canonique par sujet. Les contrats d’observabilité — ContextAwareExceptionInterface, SpectrumInterface, JobNotificationInterface, et l’enum DegradationPolicy — sont documentés sur Contrats / Observabilité. Cette page documente l’implémentation concrète de l’état d’exécution. Les deux sont complémentaires, et ne font pas doublon : lis la page des contrats pour la SPI, cette page pour le journal SIEM, les rapports et les surfaces d’audit.

Fenêtre de terminal
composer require nextpdf/core:^3

Ce module est l’endroit où l’état d’exécution du moteur devient une sortie durable et vérifiable.

HashChainSiemEventLog est la surface dédiée à la sécurité. Elle implémente le contrat SiemEventEmitter et écrit un journal JSON-Lines où le hachage de chaque enregistrement vaut SHA-256(prev_hash_bytes || canonical_event_bytes). Cette chaîne de hachage linéaire rend le journal inviolable : modifier un seul octet, supprimer une ligne ou réordonner des lignes brise la chaîne. verifyIntegrity() parcourt le fichier et renvoie l’index du premier enregistrement incohérent, ou null si la chaîne est intacte. readAll() diffuse les enregistrements. Un flock(LOCK_EX) consultatif par processus encadre la section critique lire-la-fin-puis-ajouter, afin que des processus PHP concurrents sur le même fichier n’entrelacent pas leurs enregistrements. La conception assume clairement sa limite : c’est une chaîne de hachage linéaire, pas un arbre de Merkle RFC 6962 — suffisant pour l’inviolabilité, pas pour des preuves d’inclusion efficaces. Le code source l’indique. SiemEvent porte l’événement typé avec toCanonicalJson(). SiemEventSeverity et SiemEventType le classent. CorrelationContext et CorrelationIdGenerator propagent un identifiant de corrélation à travers les événements liés.

RenderReportBuilder, RenderReport, PilotReportAggregator, et PilotSummary constituent la surface de reporting (@since 5.1.0). L’agrégateur collecte des RenderReport et produit un PilotSummary rendu sous forme de tableau, de JSON ou de Markdown — le format consommé par une revue d’exploitation.

HsmAuditLogInterface / HsmAuditEvent consignent les opérations de signature adossées au HSM pour la couche de sécurité. Les interfaces MetricsCounterInterface, MetricsGaugeInterface, MetricsHistogramInterface, et TraceSpanInterface définissent les formes des metrics/trace. Les implémentations NoOp* fournissent une solution de repli complète et inerte, afin que le moteur puisse émettre des métriques et des spans sans backend configuré.

Stabilité : expérimentale. Le journal SIEM est étiqueté par cycle en interne plutôt que de porter un @since semver figé, et la surface de rapport est @since 5.1.0. Les surfaces sont fonctionnelles et testées, mais les formes de l’API peuvent évoluer. Considère le format du journal (JSON canonique + chaîne de hachage) comme le contrat stable, et l’API PHP comme encore en cours de stabilisation.

ClasseMembres clésRôle
HashChainSiemEventLogemit(SiemEvent), verifyIntegrity(): ?int, readAll(): GeneratorJournal SIEM chaîné par hachage et inviolable
SiemEventtoCanonicalJson()Événement SIEM typé
SiemEventSeverity / SiemEventType (enums)classificationGravité et type d’événement
CorrelationContext / CorrelationIdGeneratorfil de corrélationCorrèle les événements liés
RenderReportBuilder / RenderReportassemblage du rapportRapport par rendu (@since 5.1.0)
PilotReportAggregatoraddReport(), count(), getSummary(), toJson(), toMarkdown(), exportReportsJson()Agrège les rapports de rendu (@since 5.1.0)
PilotSummarytoArray(), toJson(), toMarkdown()Synthèse pour la revue d’exploitation (@since 5.1.0)
HsmAuditLogInterface / HsmAuditEventEnregistrement d’audit HSMJournal d’audit des opérations HSM
NoOpSiemEventEmitter, NoOpMetricsCounter, NoOpTraceSpan, …solutions de repli inertesImplémentations no-op complètes

Exécute composer docs:generate-api-php -- --module=Observability pour obtenir la table PHPDoc complète.

Émets un événement et vérifie l’intégrité du journal.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Observability\Siem\HashChainSiemEventLog;
use NextPDF\Observability\Siem\SiemEvent;
$log = new HashChainSiemEventLog('/var/log/nextpdf/siem.jsonl');
$log->emit(new SiemEvent(/* type, severity, payload */));
$firstBroken = $log->verifyIntegrity();
echo $firstBroken === null
? "SIEM chain intact.\n"
: "Tamper detected at record {$firstBroken}.\n";

Encapsule l’émetteur, afin qu’une défaillance de journalisation dans le chemin critique de la signature reste une décision locale, et non une exception non rattrapée.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Observability\Siem\HashChainSiemEventLog;
use NextPDF\Observability\Siem\Exception\SiemEmitterException;
use NextPDF\Observability\Siem\SiemEvent;
use Psr\Log\LoggerInterface;
final readonly class AuditedSiemSink
{
public function __construct(
private HashChainSiemEventLog $log,
private LoggerInterface $fallback,
) {}
public function record(SiemEvent $event): void
{
try {
$this->log->emit($event);
} catch (SiemEmitterException $e) {
// Do not let SIEM I/O abort the signing path; record and continue.
$this->fallback->critical('SIEM emit failed; event not chained.', [
'error' => $e->getMessage(),
]);
}
}
}
  • emit() lève SiemEmitterException en cas d’erreur d’écriture. Un appelant dans le chemin critique de la signature doit l’encapsuler et décider localement de l’ignorer, de réessayer ou d’abandonner. L’émetteur ne décide pas à ta place.
  • verifyIntegrity() renvoie l’index du premier enregistrement rompu, ou null. Un résultat non null signifie que le journal est compromis à partir de ce point — ne fais pas confiance aux enregistrements situés à cet index ou au-delà.
  • Le flock consultatif s’applique par processus et sur le même fichier. La concurrence inter-hôtes requiert un puits hors-bande (transfert vers syslog). Ne suppose pas que le verrou de fichier coordonne entre les machines.
  • C’est une chaîne de hachage linéaire, pas un arbre de Merkle. Elle prouve l’inviolabilité, pas des preuves d’inclusion efficaces. Ne la présente pas comme cette dernière.
  • Les solutions de repli NoOp* sont complètes et inertes. Ne conditionne pas l’émission à la disponibilité du backend pour « économiser du travail » — le no-op ne coûte déjà rien.

emit() lit le hachage de l’enregistrement précédent et ajoute une ligne sous un verrou de fichier — O(1) par événement plus le verrou. verifyIntegrity() est en O(n) sur le nombre d’enregistrements, car elle parcourt toute la chaîne. Exécute-la de façon planifiée, pas dans le chemin critique. L’agrégation des rapports est linéaire en nombre de rapports. Le profil de reproductibilité est structural : les événements et les rapports portent des horodatages et des identifiants de corrélation, si bien que deux exécutions diffèrent sur ces champs tandis que la structure de la chaîne reste déterministe.

Le journal SIEM est un contrôle de sécurité. Son inviolabilité dépend de la protection du fichier de journal et de l’étape de vérification : stocke le fichier sur un stockage append-only à accès contrôlé, exécute verifyIntegrity() de façon planifiée, et transfère les enregistrements hors-bande, afin qu’une compromission d’hôte ne puisse pas réécrire l’historique en silence. Les événements peuvent contenir un contexte sensible. Applique l’obligation d’épuration des journaux du projet avant que l’événement ne soit construit, et non après qu’il a été chaîné, car une réécriture épurée briserait la chaîne. Le journal d’audit HSM consigne les opérations de signature et est lui-même pertinent pour la sécurité. Traite-le avec les mêmes protections. Consulte le modèle de menace du moteur dans /modules/core/security/.

Ce module n’affirme aucune revendication normative de la spécification PDF. Il implémente des mécanismes d’intégrité de journal et d’observabilité dont la conception s’aligne sur les pratiques de gestion des journaux et de vérification d’intégrité du NIST SP 800-92 — un alignement sur un cadre de contrôle documenté dans le code source, pas une citation PDF épinglée à un chunk. La conformité des documents que le moteur produit est validée par l’oracle et les suites de référence décrits dans /modules/core/conformance/.