Audit logs
Fabrik's immutable audit trail — every action, who did it, when, from where. Category toggles, per-category retention, CSV export, and separate login-attempt tracking.
Audit logs answer one question: who did what, and when? Every permission grant, every APIC query, every automation run, every settings change. The log is append-only — you can browse and export it, but nothing in Fabrik lets you edit or delete entries — which is the point.
The data model
Audit data is split into two models.
AuditLog
The main audit table. One row per admin or user action. Fields:
- timestamp — when it happened, indexed.
- user — FK to the user who did it, nullable.
- username — denormalized copy of the username. When the user is deleted,
userbecomesnullbutusernameremains — so the history stays readable. - ip_address — source IP, extracted from
HTTP_X_FORWARDED_FOR(rightmost trusted-proxy entry) orREMOTE_ADDR. - user_agent — client user agent, truncated to 500 chars.
- category + action — both indexed enums, used for filtering.
- resource_type / resource_id / resource_name — what the action was about (e.g.
User/42/alice). - description — human-readable summary written by the calling code.
- metadata — JSON bag for structured context (permission lists, field diffs, counters).
- content / content_size / content_truncated — optional large payload (query text, API response).
- success + error_message — whether the action succeeded.
Indexes on (timestamp, user, category, action, resource_type+id) make the common admin queries — "everything this user did last week," "every delete in this category today" — fast.
LoginAttempt
Login is high-volume and lives in its own table so retention can be aggressive without losing broader audit detail. Fields: timestamp, username, user FK, IP address, user agent, success flag, failure reason, session key.
Separating login attempts keeps AuditLog focused on meaningful actions while still capturing every login for security review — the things you search differently tend to live in different tables.
Categories and actions
The category field groups actions into broad areas — useful for filtering and for the per-category retention policy.
| Category | Covers |
|---|---|
user_management | User CRUD, activate/deactivate, password resets, MFA admin actions |
group_permission | Group CRUD, permission grants, quota updates |
login_logout | Successful and failed logins, logouts |
apic_management | APIC connection CRUD, connection tests, query executions |
query_management | Saved query CRUD, duplication |
query_execution | Interactive and background query runs |
category_management | Query category CRUD |
task_management | Scheduled task CRUD, manual runs, pause/resume |
time_machine | Snapshot captures, diffs, settings, cleanup runs |
awx_management | AWX connection and template CRUD, template validation |
awx_automation | Automation request and execution events |
awx_webhook | Webhook events received from AWX |
validation | Data validation against queries and lists |
validation_management | Validation list CRUD |
notification_management | Notification deletes |
settings_change | Settings updates (audit, notification, task management) |
system_settings | System-level settings |
api_access | Every API endpoint call (off by default, high volume) |
mim_explorer | MIM sync start/complete/fail |
The action field is the specific event within the category — e.g. user_created, password_reset, apic_query_executed, time_machine_snapshot_captured.
Browsing the logs
Settings → Administration → Audit Logs opens the paginated log viewer. Page size defaults to 50, max 100.
Filters
- Category — pick one of the 20 categories above.
- Action — pick a specific action.
- Resource type — e.g.
User,APICConnection,Query. - User — filter to one user's activity.
- Success flag — successes or failures only.
- Date range —
start_date/end_dateinclusive. - Search — free text over username, description, and resource_name.
Row detail
Clicking a row opens the full entry. The summary fields are above the fold; the metadata JSON and content blob (if any) are below, rendered pretty-printed. This is where the field-by-field diff on a user edit lives, where the permission list on a permission change lives, where the error traceback on a failed action lives.
Login attempts
A separate tab or view shows LoginAttempt rows. Same kind of filters — username, IP address, success flag, date range — optimised for questions like "how many failed logins from this IP in the last hour?" This is where you go first when you suspect credential stuffing or a brute-force attempt.
Category gating
Not every deployment wants every category captured — api_access, in particular, logs every single API request and will fill a disk fast. The AuditLogSettings singleton lets admins toggle categories on and off.
Toggles
One toggle per category (or per related category group):
user_management_enabledgroup_permission_enabledlogin_logout_enabledapic_management_enabledquery_content_enabled(covers query management, execution, categories)task_management_enabledtime_machine_enabledawx_management_enabled(covers AWX management, validation, validation management)awx_automation_enabled(covers AWX automation and webhooks)settings_changes_enabled(covers settings, system settings, notification management)mim_explorer_enabledapi_access_enabled— default off
When a category is off, AuditService.log() returns early without persisting. Code that wants to audit something doesn't need to know if the category is on — it just calls log() and the service handles the gate.
Categories not explicitly listed in the gate map default to enabled. Any new audit category works out of the box without updating settings — the opt-out is explicit, which is the safe default for a security trail.
Retention
Per-category retention policies in days. 0 means keep forever. Defaults:
| Category | Default retention |
|---|---|
| User management, group permission, AWX management, AWX automation, settings changes | 365 days |
| APIC management, task management | 180 days |
| Login/logout, query content, time machine, MIM explorer | 90 days |
| API access | 30 days |
Auto-cleanup runs daily at a configurable hour (cleanup_time_hour, default 3 AM). Set auto_cleanup_enabled=false to disable cleanup entirely — useful during a compliance review when you explicitly want nothing deleted until the review is done.
Content limits
Two settings gate the content field for large payloads (query results, API bodies):
max_content_size_mb— hard cap, default 10 MB. Anything larger is truncated and flagged withcontent_truncated=true.compress_large_content— default true. Content over 1 MB gets gzipped and base64-encoded before storage. Retrieval automatically decompresses.
Compression cuts storage for repeated JSON payloads dramatically but adds CPU cost on write and read. The default is "on, for content over 1 MB" — below that threshold compression costs more than it saves.
CSV export
The Export button downloads up to 10,000 filtered rows as CSV. Filters from the current view apply — export is essentially "download what you're looking at."
Columns: Timestamp, Username, Category, Action, Resource Type, Resource Name, Description, IP Address, Success.
The cap exists because the export is a synchronous endpoint and 10k rows is roughly where an instant download becomes a "hanging tab for a minute." If you need more, narrow the filters and export in chunks, or query the database directly.
Stats
The Stats endpoint returns counts by category and by action over a configurable window (default 30 days). The UI renders this as a pair of bar charts — useful for "what's been happening lately" dashboards, and for spotting unusual spikes (a sudden surge in login_failed entries, for example).
Auditing the audit settings
The one action audited inside the audit_logs surface itself: changing audit settings logs audit_settings_updated in the settings_change category, with the full new settings dict in metadata. So disabling audit categories doesn't hide the fact that you disabled them — the disable action is itself audited.
Turning off a category doesn't delete existing logs for that category. It only stops new ones from being written. If you turn a category back on later, the gap in coverage is visible in the timeline — a reliable signal during an investigation.
What isn't in the audit log
A few deliberate gaps:
- Read actions aren't audited by default. Viewing a query, opening a dashboard, listing users — these don't generate log entries.
api_access_enabledcovers this when it matters, but defaults off. - Notification inbox actions aren't audited. Marking a notification read isn't a security-relevant event; only deleting is.
- Background job progress isn't audited. A scheduled task logs its start and completion; each individual progress update would drown out the real events.
The guiding principle: audit the actions that change state or grant access, not every click.
Common queries
- "Who changed this user's permissions?" Filter by
resource_type=Userandresource_id=<id>andcategory=group_permission. - "What did user X do yesterday?" Filter by user, date range yesterday. Read top-to-bottom.
- "Has anyone been disabling MFA?" Filter by
action=mfa_disabled_by_admin. Should be rare; each one should have a legitimate ticket behind it. - "Brute force attempt?" Login Attempts tab, filter by IP address and
success=false, look at the failure reasons and rate. - "Who deleted this APIC connection?" Filter by
action=apic_connection_deletedandresource_name=<connection name>.
Troubleshooting
Audit issues that come up often:
- "A category has no recent entries but actions are definitely happening." Check if the category is disabled in audit settings.
- "The audit log is huge and Postgres is running out of space." Tighten retention, especially on
query_content(execution payloads are big) andapi_access(volume). Confirmauto_cleanup_enabled=true. - "Export stops at 10,000 rows." That's the hard cap — narrow your filter or pull from the database directly for bigger exports.
- "Content shows as compressed junk." The UI decompresses transparently — if you're looking at raw database rows, compressed content is base64-encoded gzip. Decompress client-side or via the API detail endpoint.
- "IP addresses all show as the proxy IP." The
HTTP_X_FORWARDED_FORheader is read rightmost-first (the trusted proxy hop). If you're behind multiple untrusted proxies, the parsing logic assumes the rightmost entry is trustworthy — wrong in some topologies. Check your reverse-proxy config. - "I need to prove the log wasn't tampered with." Fabrik's audit log is append-only by convention, not cryptographic chaining. For stricter compliance (hash chains, external WORM storage), export regularly to an append-only sink outside Fabrik.
That completes the Administration section. Users, groups and quotas, LDAP, audit logs — the four surfaces that together decide who can do what in Fabrik and what they did.
LDAP
LDAP configuration visibility, connection testing, directory browsing, and how LDAP groups map to Django flags like is_staff and is_superuser.
Settings
Per-user settings — profile, password and MFA, display preferences, and AI provider configuration. The knobs every user controls for their own account.