FabrikFabrik
FabrikAdministration

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, user becomes null but username remains — so the history stays readable.
  • ip_address — source IP, extracted from HTTP_X_FORWARDED_FOR (rightmost trusted-proxy entry) or REMOTE_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.

CategoryCovers
user_managementUser CRUD, activate/deactivate, password resets, MFA admin actions
group_permissionGroup CRUD, permission grants, quota updates
login_logoutSuccessful and failed logins, logouts
apic_managementAPIC connection CRUD, connection tests, query executions
query_managementSaved query CRUD, duplication
query_executionInteractive and background query runs
category_managementQuery category CRUD
task_managementScheduled task CRUD, manual runs, pause/resume
time_machineSnapshot captures, diffs, settings, cleanup runs
awx_managementAWX connection and template CRUD, template validation
awx_automationAutomation request and execution events
awx_webhookWebhook events received from AWX
validationData validation against queries and lists
validation_managementValidation list CRUD
notification_managementNotification deletes
settings_changeSettings updates (audit, notification, task management)
system_settingsSystem-level settings
api_accessEvery API endpoint call (off by default, high volume)
mim_explorerMIM 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 rangestart_date / end_date inclusive.
  • 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_enabled
  • group_permission_enabled
  • login_logout_enabled
  • apic_management_enabled
  • query_content_enabled (covers query management, execution, categories)
  • task_management_enabled
  • time_machine_enabled
  • awx_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_enabled
  • api_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:

CategoryDefault retention
User management, group permission, AWX management, AWX automation, settings changes365 days
APIC management, task management180 days
Login/logout, query content, time machine, MIM explorer90 days
API access30 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 with content_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_enabled covers 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=User and resource_id=<id> and category=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_deleted and resource_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) and api_access (volume). Confirm auto_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_FOR header 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.