class

KemalIdentity::EventBridge

Inherits Log::Backend < Reference < Object

Feeds SecurityEvents to a SecurityEventSink from the events the shard already emits.

Why a bridge rather than a second call at every site

The shard emits sixty-four events through Log, each with the fields that event needs. Adding a sink call beside every one of them would be sixty-four chances to drop a field, and would leave two things to keep in step forever. This translates instead: one place that knows which keys are correlation fields and which are event detail.

The Log output is untouched. An application that wants both keeps both, and one that wants only a sink still has its own logging configuration to answer to.

A broken sink cannot go quiet, and cannot take authentication down

blueprints/0025-maturity-validation-results.md (OPS-02) measured both failure modes of the obvious approach — a consumer's own Log::Backend. Under :direct dispatch a raising backend left Passwords::Authenticator#authenticate as an exception and turned every login into a 500; under :async it killed the dispatcher fiber and the trail stopped with nothing said.

So this catches, and counts. #failures is readable, and is the thing to alarm on: a sink that is failing shows up as a rising number rather than as an absence somebody notices weeks later. Nothing is logged from in here, deliberately — reporting a logging failure through the logger a sink is attached to is how a backend recurses into itself.

Authentication never fails because a sink did. The security decision was already made and recorded through Log before this ran.

Constants

CORRELATION = ["subject", "credential", "tenant", "ip", "reason"] of ::String

Keys that are correlation fields rather than event detail. Anything else lands in SecurityEvent#data.

PROBE = "sink.probe"

The name of the event KemalIdentity.event_sink_delivering? emits, which a sink will see like any other. Named rather than hidden: a heartbeat in the trail is useful, and a mysterious event nobody documented is not.

Constructors

new(sink : SecurityEventSink)

:async so a slow sink does not sit on the request fiber. The isolation below is what makes that safe -- an exception on the dispatcher fiber is what killed the trail in the measurement this exists because of.

Source

Instance methods

failures

How many events the sink raised on. Read it, alarm on it: this is what a failing SIEM feed looks like from the inside.

Source
probes

How many probes have arrived. What makes delivery checkable end to end rather than by inspecting Log's configuration, which cannot be enumerated from outside.

Source
sink
Source
write(entry : ::Log::Entry) : Nil

Writes the entry to this backend.

Source