feat(audit): durable tenant-scoped audit trail with query API - #61
Merged
Conversation
Implement SIEM-exportable audit trail with persistent storage, scope-based access control, and comprehensive filtering. Upgrade @audit_log decorator to write audit_events rows (success/failure paths), emit structured JSON logs with no PII, and safely handle write failures (fail-open). Add GET /api/v1/audit-events endpoint gated on audit:read scope with filters for action, actor_id, resource_type/id, outcome, date ranges, and pagination. Database schema follows penguin-dal+Alembic pattern; @audit_log decorator works in-place at all call sites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Critical fixes to address review violations: 1. **LEAST-PRIVILEGE**: Remove audit:read from _READ_SCOPES; add only to SystemAdmin bundle. Viewer/OrgAdmin/UserManager now get 403 on /api/v1/audit-events. 2. **TENANT SCOPING**: Add tenant column (indexed) to audit_event table. Decorator populates tenant from JWT claims. Endpoint supports tenant filtering. 3. **Tests**: Add scope enforcement tests (403 for non-SystemAdmin) and tenant column/filter coverage. Migration updated to add idx_audit_event_tenant_created index for efficient tenant+timestamp queries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🧙 Sourcery is reviewing your pull request! Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Sorry @PenguinzTech, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
PenguinzTech
added a commit
that referenced
this pull request
Jul 27, 2026
Combines both sides' additions rather than picking one: - app/__init__.py: register both machine_clients_bp/oidc_trust_anchors_bp (this branch) AND audit_bp (already-merged #61) — both blueprint sets now registered, neither dropped. - app/schema.py: both machine_client/oidc_trust_anchor tables (this branch) and audit_event table (#61) now defined side by side. - tests/test_schema.py: expected-tables set includes all five new tables from both branches. - alembic: 008_machine_clients and 008_audit_events both claimed down_revision=007_revoked_token (two branches minting migration "008" independently). Re-chained machine_clients after audit_events (008_audit_events -> 008_machine_clients -> 009_oidc_trust_anchors) so the migration graph has a single linear head instead of two. - .pre-commit-config.yaml: also fixed the hadolint v2.13.0 pin (never a real tag -> v2.13.1), the unquoted flake8 --select YAML flow sequence (commas split it into bogus positional filename args), and switched gitleaks to `protect --staged` (scans only what's committed, matching PR #77's approach — the standard pre-commit pattern, no allowlist needed) instead of a full-tree scan. v2.1.x doesn't have these fixes yet since #77/#78 haven't merged; this converges toward the same fix ahead of that. Full manager suite: 200/200 passing after the combine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PenguinzTech
added a commit
that referenced
this pull request
Jul 27, 2026
008_add_mfa_fields (#62) and 008_machine_clients/008_audit_events (already-merged #61/#63) both independently claimed down_revision=007_revoked_token, creating two divergent heads (009_oidc_trust_anchors and 008_add_mfa_fields). Git's merge saw no textual conflict since they're different files, so this surfaced only after the merge completed. Re-chained 008_add_mfa_fields after 009_oidc_trust_anchors so the migration graph has a single linear head again. Full manager suite: 229/229 passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PenguinzTech
added a commit
that referenced
this pull request
Jul 27, 2026
- app/blueprints/auth.py: both grant handlers (client_credentials and token-exchange) needed to combine THIS branch's dpop_jkt binding with #71's already-merged allowed_domains DB-fetch, both passed to create_machine_access_token. - app/services/auth_service.py: create_machine_access_token's signature/docstring/body combined to accept and apply both dpop_jkt (RFC 9449 cnf claim) and allowed_domains (dns_domains claim). - app/schema.py + tests/test_schema.py: dpop_replay table (this branch) combined with audit_event (#61, already merged) -- this branch predates that merge so git saw it as new. - alembic: two real chain issues, both from this branch predating later merges: 1. 011_dpop_replays.py had NO revision/down_revision identifiers at all (a genuine bug -- alembic could never have discovered this migration). Added revision="011_dpop_replays", down_revision="010_dns_domain_allowlists" (010 is #71's migration, merged after this branch was authored; the file's stale docstring said "Depends on: 009"). 2. 008_add_mfa_fields (re-chained onto 009 in an earlier standalone v2.1.x fix, before #71 existed) and 010_dns_domain_allowlists both forked from 009, creating two heads. Re-chained 008_add_mfa_fields -> 011_dpop_replays so the graph is linear again with 008_add_mfa_fields as the single head. Full manager suite: 293/293 passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Replaces the log-line-only
@audit_logwith a durable audit trail:audit_eventtable (actor_id/tenant/action/resource/outcome/status/request_id/source_ip — no PII), fail-open DB write + single-line JSON log (SIEM-ready) on success and failure paths, andGET /api/v1/audit-eventswith filters (action, actor_id, tenant, resource_type, outcome, since/until) + pagination.audit:readgranted to SystemAdmin only (least privilege — Viewer/OrgAdmin get 403, tested).Tests: 164 green (26 audit-specific). All 8 existing @audit_log call sites unchanged.
Merge note: contains alembic migration
008— collides with 008 in the MFA and NHI branches; renumber whichever merges later.Stack note: bases on
chore/dedup-reusable-code(top of the #53–#59 chain); auto-retargets towardv2.1.xas the stack merges bottom-up.🤖 Generated with Claude Code
Summary by Sourcery
Introduce a durable, queryable audit trail with a new audit_event table and API, replacing the previous log-only audit decorator.
New Features:
Enhancements:
Build:
Tests: