Перейти к содержимому

Наблюдаемость: SIEM-журнал с хеш-цепочкой и отчётность об отрисовке

Модуль Observability предоставляет реализацию, основанную на состоянии среды выполнения: устойчивый к подделке журнал событий SIEM (security information and event management) с хеш-цепочкой, агрегацию отчётов об отрисовке и пилотных запусках, журнал аудита HSM (hardware security module), а также полноценные холостые реализации метрик и трассировки, поэтому инструментирование всегда доступно для вызова.

Одна каноническая страница на каждую тему. Контракты наблюдаемости — ContextAwareExceptionInterface, SpectrumInterface, JobNotificationInterface и перечисление DegradationPolicy — описаны на странице Contracts / Observability. Эта здесь описана конкретная реализация на основе состояния среды выполнения. Эти страницы дополняют друг друга, а не дублируют: используйте страницу контрактов для интерфейса поставщика услуг (service provider interface, SPI), а эту страницу — для SIEM-журнала, отчётности и интерфейсов аудита.

Окно терминала
composer require nextpdf/core:^3

Этот модуль преобразует состояние среды выполнения движка в долговременный проверяемый вывод.

HashChainSiemEventLog — это интерфейс уровня безопасности. Он реализует контракт SiemEventEmitter и записывает журнал в формате JSON Lines (JavaScript Object Notation), где хеш каждой записи равен SHA-256(prev_hash_bytes || canonical_event_bytes). Эта линейная хеш-цепочка делает журнал устойчивым к подделке: если изменится хотя бы один байт, будет удалена строка или строки будут переставлены местами, цепочка разорвётся. verifyIntegrity() проходит по файлу и возвращает индекс первой записи с расхождением или null, если цепочка не нарушена. readAll() выдаёт записи потоково. Рекомендательная блокировка flock(LOCK_EX) на уровне процесса защищает критическую секцию “прочитать хвост, затем добавить”, поэтому параллельные процессы PHP, работающие с одним файлом, не перемешивают записи. Ограничение указано явно: это линейная хеш-цепочка, а не дерево Меркла по RFC 6962 (Request for Comments). Её достаточно для выявления подделки, но не для эффективных доказательств включения. Это указано в исходном коде. SiemEvent представляет типизированное событие с методом toCanonicalJson(). SiemEventSeverity и SiemEventType классифицируют его. CorrelationContext и CorrelationIdGenerator передают идентификатор корреляции между связанными событиями.

RenderReportBuilder, RenderReport, PilotReportAggregator и PilotSummary образуют интерфейс отчётности (@since 5.1.0). Агрегатор собирает объекты RenderReport и формирует PilotSummary, который экспортируется в виде массива, JSON или Markdown — в форме, пригодной для эксплуатационного разбора.

HsmAuditLogInterface / HsmAuditEvent фиксируют операции подписи с использованием HSM на уровне безопасности. MetricsCounterInterface, MetricsGaugeInterface, MetricsHistogramInterface и TraceSpanInterface задают формы метрик и трассировки. Реализации NoOp* предоставляют полноценный инертный резервный вариант, поэтому движок может выдавать метрики и интервалы трассировки без настроенного бэкенда.

Стабильность: экспериментальная. SIEM-журнал внутри помечается по циклам, а не имеет зафиксированный тег @since семантического версионирования (semver), а интерфейс отчётности имеет тег @since 5.1.0. Эти интерфейсы работоспособны и протестированы, но формы API (application programming interface) могут изменяться. Считайте формат журнала (канонический JSON + хеш-цепочка) стабильным контрактом, а PHP-API — ещё не устоявшимся.

КлассКлючевые членыРоль
HashChainSiemEventLogemit(SiemEvent), verifyIntegrity(): ?int, readAll(): GeneratorУстойчивый к подделке SIEM-журнал с хеш-цепочкой
SiemEventtoCanonicalJson()Типизированное событие SIEM
SiemEventSeverity / SiemEventType (перечисления)классификацияКлассифицирует серьёзность и тип события
CorrelationContext / CorrelationIdGeneratorсквозная передача корреляцииПередаёт корреляцию между связанными событиями
RenderReportBuilder / RenderReportсборка отчётаФормирует отчёты об отдельных отрисовках (@since 5.1.0)
PilotReportAggregatoraddReport(), count(), getSummary(), toJson(), toMarkdown(), exportReportsJson()Агрегирует отчёты об отрисовке (@since 5.1.0)
PilotSummarytoArray(), toJson(), toMarkdown()Готовит сводный вывод для эксплуатационного разбора (@since 5.1.0)
HsmAuditLogInterface / HsmAuditEventзапись аудита HSMФиксирует операции HSM в аудите
NoOpSiemEventEmitter, NoOpMetricsCounter, NoOpTraceSpan, …инертные резервные вариантыПредоставляет полноценные холостые реализации

Выполните composer docs:generate-api-php -- --module=Observability, чтобы сформировать полную таблицу PHPDoc.

Отправьте событие и проверьте целостность журнала.

<?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";

Оберните эмиттер так, чтобы сбой журналирования на горячем пути подписи обрабатывался локально и не превращался в неперехваченное исключение.

<?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() выбрасывает SiemEmitterException при ошибках записи. Вызывающий код на горячем пути подписи должен обернуть вызов и локально решить: подавить исключение, повторить попытку или прервать выполнение. Эмиттер не принимает решение за вас.
  • verifyIntegrity() возвращает индекс первой нарушенной записи или null. Результат, отличный от null, означает, что начиная с этой точки журнал скомпрометирован. Не доверяйте записям с этого индекса и далее.
  • Рекомендательная блокировка flock действует на уровне процесса и в пределах одного файла. Для параллельной работы между узлами требуется внеполосный приёмник, например пересылка через syslog. Не считайте, что блокировка файла обеспечивает координацию между разными машинами.
  • Это линейная хеш-цепочка, а не дерево Меркла. Она обеспечивает выявление подделки, а не эффективные доказательства включения. Не позиционируйте её как такие доказательства.
  • Резервные варианты NoOp* являются полными и инертными. Не добавляйте ветвление по доступности бэкенда ради “экономии работы”. Холостая реализация и так не требует затрат.

emit() читает хеш предыдущей записи и добавляет одну строку под блокировкой файла: O(1) на событие плюс блокировка. verifyIntegrity() имеет сложность O(n) по числу записей, поскольку проходит всю цепочку. Запускайте его по расписанию, а не на горячем пути. Агрегация отчётов линейна по числу отчётов. Профиль воспроизводимости — structural: события и отчёты содержат отметки времени и идентификаторы корреляции, поэтому два запуска различаются этими полями, тогда как структура цепочки остаётся детерминированной.

SIEM-журнал является средством контроля безопасности. Его устойчивость к подделке зависит от защиты как самого файла журнала, так и шага проверки: храните файл в хранилище с контролем доступа, поддерживающем дозапись, запускайте verifyIntegrity() по расписанию и пересылайте записи по внеполосному каналу, чтобы компрометация узла не могла незаметно переписать историю. События могут содержать конфиденциальный контекст. Выполняйте принятую в проекте обязательную очистку данных для журнала до формирования события, а не после его включения в цепочку, поскольку перезапись после очистки разорвала бы цепочку. Журнал аудита HSM фиксирует операции подписи и сам по себе значим для безопасности. Применяйте к нему те же меры защиты. См. модель угроз движка в /modules/core/security/.

Этот модуль не заявляет о нормативном соответствии спецификациям PDF. Он реализует механизмы целостности журналов и наблюдаемости, устройство которых согласуется с практиками управления журналами и проверки целостности из NIST SP 800-92. Связь с системой средств контроля задокументирована в исходном коде; это не привязанная к конкретному фрагменту цитата из PDF. Соответствие документов, которые создаёт движок, проверяется с помощью оракула и наборов эталонных файлов, описанных в /modules/core/conformance/.