Audit trail¶
FastAPIAudit, its entries and events, sinks, and the trail's verification.
In fastapi_alcazaba.audit.
In the guide: Audit trail. In an app: the example.
fastapi_alcazaba.audit.FastAPIAudit
¶
FastAPIAudit(sinks: Iterable[AuditSink] | None = None, *, signing_key: Secrets | None = None, redact: Collection[str] = (), timeout: float = 5.0, metrics: Metrics | None = None)
An audit trail: action() records what endpoints do, record() and log() the rest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sinks
|
Iterable[AuditSink] | None
|
where the events go; a LoggingAuditSink by default. |
None
|
signing_key
|
Secrets | None
|
signs and chains the events (see |
None
|
redact
|
Collection[str]
|
words that mark secrets in the keys of |
()
|
timeout
|
float
|
seconds each sink has to write an event. |
5.0
|
metrics
|
Metrics | None
|
where to count the events ( |
None
|
action
¶
action(name: str, *, resource: str | None = None) -> Callable[[HTTPConnection], AsyncIterator[AuditEntry]]
A dependency that records the action name when the endpoint ends, and gives the
endpoint its AuditEntry to fill in (actor, details...). resource is a template of
the path parameters, such as "invoice:{invoice_id}", filled in before anything runs: the
requests refused are recorded with it too.
record
async
¶
record(action: str, *, actor: str | int | UUID | None = None, resource: str | int | UUID | None = None, details: Mapping[str, Any] | None = None, request: HTTPConnection | None = None) -> AsyncIterator[AuditEntry]
Records the block as the action action, when it ends: outside endpoints (jobs,
tasks, commands), or around a part of one. request adds the request's method, route
and client.
log
async
¶
log(action: str, *, outcome: Outcome = 'success', actor: str | int | UUID | None = None, resource: str | int | UUID | None = None, reason: str | None = None, details: Mapping[str, Any] | None = None, request: HTTPConnection | None = None) -> AuditEvent
Writes one event and returns it, e.g. for a failed login. request adds the
request's method, route and client.
fastapi_alcazaba.audit.AuditEntry
¶
AuditEntry(action: str, *, actor: str | int | UUID | None = None, resource: str | int | UUID | None = None, details: Mapping[str, Any] | None = None)
The event an action writes when it ends: the endpoint fills in what it learns.
outcome is decided by how the action ends; set it ("denied", "failure") when the endpoint
returns an error response instead of raising one. reason says why it failed or was denied,
the exception's type by default.
fastapi_alcazaba.audit.AuditEvent
dataclass
¶
AuditEvent(id: str, time: datetime, action: str, outcome: Outcome, actor: str | None = None, resource: str | None = None, reason: str | None = None, status: int | None = None, request_id: str | None = None, trace_id: str | None = None, method: str | None = None, route: str | None = None, client: str | None = None, details: Mapping[str, Any] = dict(), stream: str = '', sequence: int = 0, previous: str | None = None, signature: str | None = None)
An event of the trail, as the sinks get it; to_dict() and to_json() give its JSON form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
str
|
random, unique. |
required |
time
|
datetime
|
when it was written (UTC). |
required |
action
|
str
|
what was done, e.g. "invoice.delete". |
required |
outcome
|
Outcome
|
success, failure or denied. |
required |
actor
|
str | None
|
who did it (a user's ID), if known. |
None
|
resource
|
str | None
|
what it was done to, e.g. "invoice:42". |
None
|
reason
|
str | None
|
why it failed or was denied: the exception's type, or the reason given. |
None
|
status
|
int | None
|
the HTTP status of the exception that ended it. |
None
|
request_id
|
str | None
|
the request's ID ( |
None
|
trace_id
|
str | None
|
the OpenTelemetry trace it belongs to. |
None
|
method
|
str | None
|
the request's HTTP method. |
None
|
route
|
str | None
|
the template of the route that matched, e.g. "/invoices/{invoice_id}". |
None
|
client
|
str | None
|
the client's address, as the server reports it. |
None
|
details
|
Mapping[str, Any]
|
anything else, redacted and bounded. |
dict()
|
stream
|
str
|
the writer's stream: one per process and FastAPIAudit. |
''
|
sequence
|
int
|
its number in the stream, from 1. |
0
|
previous
|
str | None
|
the signature of the stream's previous event (with a |
None
|
signature
|
str | None
|
with a |
None
|
fastapi_alcazaba.audit.AuditSink
¶
Bases: Protocol
Where events go. write gets each one, redacted and signed, when its action ends, within
the FastAPIAudit's timeout: queue it if writing takes longer. What it raises is logged.
fastapi_alcazaba.audit.LoggingAuditSink
¶
LoggingAuditSink(log: str | Logger = 'fastapi_alcazaba.audit.events', level: int = logging.INFO)
Writes each event as one line of JSON to the logger log (a name or a Logger), at
level; handlers that format their own JSON find the event in the record's audit attribute
too. When the logger outputs nothing at that level (no handler, or a higher level), it says so
once, as a warning.
fastapi_alcazaba.audit.MemoryAuditSink
¶
MemoryAuditSink(maxlen: int | None = 1000)
Keeps the last maxlen events in events: for tests, and to look at them while
developing.
fastapi_alcazaba.audit.verify_audit_trail
¶
verify_audit_trail(events: Iterable[AuditEvent | Mapping[str, Any]], signing_key: Secrets) -> list[str]
What's wrong with a trail written with signing_key (or one of its keys, when it was
rotated): an empty list when nothing is. events are AuditEvents or their to_dict() (the
JSON lines read back), of one stream or several, in any order; the same event given twice
counts once. Each stream's chain is checked from its oldest event given, since older ones may
have been rotated out of the logs.
fastapi_alcazaba.audit.SENSITIVE_WORDS
module-attribute
¶
SENSITIVE_WORDS: frozenset[str] = frozenset({'accesskey', 'apikey', 'auth', 'authorization', 'cardnumber', 'cookie', 'credential', 'cvc', 'cvv', 'otp', 'passphrase', 'passwd', 'password', 'pin', 'privatekey', 'pwd', 'secret', 'secretkey', 'session', 'signature', 'token'})