Analyse Permission Audit Logs¶
This tutorial explains how to enable the permission audit logger, capture events, and analyse them for policy verification. It builds on the infrastructure provided in the audit logging API.
1. Enable logging in development¶
Choose an audit logger implementation. FileAuditLogger writes newline-delimited JSON, while DatabaseAuditLogger stores entries in a Django-managed table.
# settings.py
from general_manager.permission.audit import FileAuditLogger, configure_audit_logger
configure_audit_logger(
FileAuditLogger(path="/var/log/general_manager/permission.log")
)
Alternatively, configure via Django settings:
# settings.py
GENERAL_MANAGER = {
"AUDIT_LOGGER": {
"class": "general_manager.permission.audit.DatabaseAuditLogger",
"options": {"using": "default", "table_name": "gm_permission_log"},
}
}
configure_audit_logger_from_settings() resolves those settings during app startup. GENERAL_MANAGER["AUDIT_LOGGER"] takes precedence over a top-level AUDIT_LOGGER; if neither is present, logging is disabled by installing the no-op logger.
2. Trigger and inspect events¶
Each call to BasePermission.check_* or mutation permissions emits a PermissionAuditEvent when logging is active. Entries contain:
| Field | Meaning |
|---|---|
timestamp | Recorded when the event is serialised. |
action | "create", "read", "update", "delete", or "mutation". |
attributes | Tuple of attribute names that were evaluated. |
granted | True when the check passed. |
bypassed | True when the request user was a superuser. |
manager | Name of the manager class, if known. |
user_id | Primary key of the user when available. |
user_repr | repr(user) when no primary key is available. |
permissions | All expressions evaluated, including inherited rules from __based_on__. |
metadata | JSON-compatible custom context. |
Load events into your preferred analysis tool:
jq '. | select(.action == "update" and (.granted == false))' permission.log
For database logging, run Django ORM queries:
from general_manager.permission.audit import DatabaseAuditLogger
logger = DatabaseAuditLogger(table_name="gm_permission_log", using="default")
denied = logger.model.objects.filter(granted=False).order_by("-created_at")
3. Build dashboards and alerts¶
- Aggregate denied events by
managerandattributesto spot missing rules. - Track bypasses (
bypassed=True) to ensure superuser usage stays intentional. - Correlate with request metadata by attaching extra data via the
metadatafield. When calling permission checks manually, pass context:
emit_permission_audit_event(
PermissionAuditEvent(
action="update",
attributes=("status",),
granted=False,
user=request_user,
manager="Project",
permissions=("isSelf",),
metadata={"request_id": request_id},
)
)
- Automate regression detection by running a nightly query that compares denied counts across deployments.
4. Test your logging setup¶
- In unit tests, inject a stub logger implementing
record()to capture events. - Assert that permission checks produce both success and failure entries.
- Call
close()orflush()on buffered loggers (FileAuditLogger, andDatabaseAuditLoggeron non-SQLite databases) in teardown hooks to ensure the worker thread finishes processing events. After a logger is closed, laterrecord()calls are ignored.
With logging enabled and observed, you can prove your permission model behaves as intended and quickly diagnose unexpected access results.