Repository navigation
Add the append-only audit event store - #2443
Merged
Merged
Conversation
Core gets AuditEvent, NewAuditEvent, IAuditLog and the IUnitOfWork seam; Infrastructure implements the unit of work over the DbContext transaction, the audit log and the AuditEvents table with migrations for both providers. On PostgreSQL the migration revokes UPDATE, DELETE and TRUNCATE from PUBLIC and from the deploying role.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2442
What
The first slice of F-AUD (#2424): the store every later epic writes through.
AuditEventwith what AUD-01 lists (id, timestamp, initiator asUserInitiatororAgentInitiator, agent credential id, model, prompt hash, sources, change set id, previous version id) plus the operation name and an optional subject; the record validates itself.NewAuditEventis the caller-supplied part.IAuditLog.AppendAsyncstamps the current initiator and time itself, so a service cannot record someone else as the actor, and throws outside a unit of work or when no initiator is known.IUnitOfWork.BeginAsync()returns a scope whoseCommitAsync()saves and commits; disposing without committing rolls back. Inner scopes join the outer transaction, and an abandoned inner scope fails the outer commit.UnitOfWorkover theDbContexttransaction, so Identity's own saves inside a scope roll back with it, and the change tracker is cleared after a rollback.AuditLogadds a row inside the current transaction. TheAuditEventstable: flat columns, sources as a primitive collection (text[]on PostgreSQL, JSON text on SQLite), indexes on timestamp, change set, agent credential and subject, and a check constraint that exactly the agent rows carry a credential id. No code path updates or deletes a row.AuditEventsfor both providers, generated with the documented commands. The PostgreSQL one also runsREVOKE UPDATE, DELETE, TRUNCATE ON "AuditEvents" FROM PUBLIC, CURRENT_USER.Why
AUD-N1 and ADR-0005: provenance is written in the same transaction as the change, and the log is append-only. The seam lands now so F-SCH and F-CNT write through it from their first commit.
The revoke and the deploying role
By default
PUBLICholds no table privileges on PostgreSQL, so revoking fromPUBLICalone changes nothing for the role that matters: the host applies migrations at startup (OPS-04), so the application role is the table's owner, and an owner holds every privilege until it revokes its own. The migration therefore also revokes fromCURRENT_USER. Two caveats, both inCONTRIBUTING.md:postgresrole is one, which is why the test below creates its own ordinary role.ALLon the table to another role hands the privileges back. GrantSELECT, INSERTonly.The retention purge (a later slice of #2424) needs a privileged path around this; that slice decides which. SQLite has no roles; there the code is the guarantee.
Tests
Unit: the event rejects an empty id, a null initiator, a blank operation and a blank source; exposes the credential id for an agent and null for a user; keeps every AUD-01 field.
Integration on SQLite: an event appended inside a unit of work is read back with the current user, timestamp and every field; an agent event records its credential; a scope disposed without commit leaves neither the event nor the user Identity had already saved; appending outside a unit of work or with no initiator throws; inner scopes flush into the outer transaction and only the outer commit makes them visible; an abandoned inner scope fails the outer commit and the next unit of work starts clean; the newest migration's
Downreverts and reapplies.Integration on PostgreSQL: the migrations apply, and a database migrated by a throwaway non-superuser role lets that role append through
IAuditLogbut rejectsUPDATE,DELETEandTRUNCATEwith42501 insufficient_privilege. The test creates and drops its own role and database, so it needs a connection that may do both; the documented local setup and CI have one. No PostgreSQL was available where this was written, so CI is its first run.Checklist
v2