perf(audit): bounded async audit writer with batched inserts, one-time bootstrap, retention sweep
This commit is contained in:
@@ -228,6 +228,31 @@ Storage recommendation:
|
||||
administrators.
|
||||
- Require TLS when the gateway is reachable off-machine.
|
||||
|
||||
## Audit Pipeline
|
||||
|
||||
Decision: audit is asynchronous, bounded, and swept.
|
||||
|
||||
The canonical `IAuditWriter` contract has always been best-effort — a failed audit write is
|
||||
logged and swallowed so it cannot abort the action that produced it. The registered writer is
|
||||
`ChannelAuditWriter`, which makes the cost of that promise explicit: a producer enqueues onto a
|
||||
4096-event bounded channel and returns, and `AuditDrainService` commits up to 64 buffered events
|
||||
per transaction. This exists because constraint denials are emitted per denied tag inside bulk
|
||||
RPC loops: a partially denied 1,000-tag request previously awaited 1,000 sequential SQLite
|
||||
inserts — each re-running `CREATE TABLE IF NOT EXISTS` — against the same database file every
|
||||
authenticated call reads. The schema bootstrap now runs once, from the drain's `StartAsync`.
|
||||
|
||||
When the channel is full the newest event is dropped and counted rather than blocking the
|
||||
producer: a stalled audit database must cost audit completeness, not gateway availability. Drops
|
||||
are logged once and reported in aggregate on each sweep. Shutdown drains what is buffered under a
|
||||
2-second cap. Where no hosted service runs — the `apikey` admin CLI — the writer falls back to the
|
||||
synchronous path, so audit is never buffered into a channel nobody drains.
|
||||
|
||||
`MxGateway:Security:AuditRetentionDays` (default 90, minimum 1) bounds the table: the drain sweeps
|
||||
at startup and hourly, deleting older rows. Retention cannot be configured off. The sweep compares
|
||||
through SQLite's `datetime()` rather than on the stored ISO-8601 text, because timestamps written
|
||||
from a non-UTC offset do not sort lexicographically against a UTC cutoff; a row whose timestamp
|
||||
cannot be parsed yields NULL and is kept rather than deleted.
|
||||
|
||||
## Authorization
|
||||
|
||||
Decision: start with scope checks by command category.
|
||||
|
||||
@@ -393,6 +393,7 @@ model requires otherwise.
|
||||
| `MxGateway:Security:ApiKeyFailureWindowSeconds` | `60` | Sliding-window length, in seconds, over which API-key verification failures are counted, for both the per-partition and the per-key-id aggregate layer. Must be greater than zero. |
|
||||
| `MxGateway:Security:ApiKeyFailureAggregateLimit` | `30` | Failed verifications for one key id counted across **all** transport peers within `ApiKeyFailureWindowSeconds` before that key id enters probe mode. This second layer bounds a distributed or source-rotating sprayer that never trips any single `(peer, key id)` partition. `0` disables the aggregate layer, leaving only per-partition counting. Must be zero or greater. |
|
||||
| `MxGateway:Security:ApiKeyFailureProbeIntervalSeconds` | `5` | Minimum interval, in seconds, between probe admissions for an over-limit partition or key-id aggregate. An over-limit state is a valve rather than a wall: one request per interval reaches the real verifier — exactly one, even when a burst arrives together at the interval boundary — so the holder of the correct secret always gets through and clears the state, while everything else is still refused before the store read. `0` blocks absolutely instead — **not recommended**, because an unauthenticated peer can then deny the key to its holder for the whole window. Must be zero or greater. |
|
||||
| `MxGateway:Security:AuditRetentionDays` | `90` | Days of canonical audit history kept in the `audit_event` table. The audit drain sweeps once at startup and hourly thereafter, deleting rows older than this window; without it the table grows without bound inside the same SQLite file the authentication hot path reads. Rows whose timestamp SQLite cannot parse are never swept. Must be greater than zero — retention can be widened but not switched off. |
|
||||
| `MxGateway:Security:ApiKeyFailureTrackedPeers` | `4096` | Maximum distinct partitions tracked by the failure counter (a bounded LRU) so a spray of unique tokens cannot grow memory without limit. It cannot be used to flush an active block either: only a validly shaped `mxgw_<keyId>_<secret>` token mints a key-id partition (everything else lands on the sender's transport-peer partition), each address may mint at most 32 key-id partitions before the overflow collapses onto that address's fallback partition, and eviction prefers fully expired windows, never removing an over-limit partition until the map exceeds twice this cap. Must be greater than zero. |
|
||||
|
||||
## Galaxy Options
|
||||
|
||||
Reference in New Issue
Block a user