Skip to content

feat(audit): durable tenant-scoped audit trail with query API - #61

Merged
PenguinzTech merged 2 commits into
v2.1.xfrom
feature/durable-audit-trail
Jul 27, 2026
Merged

feat(audit): durable tenant-scoped audit trail with query API#61
PenguinzTech merged 2 commits into
v2.1.xfrom
feature/durable-audit-trail

Conversation

@PenguinzTech

@PenguinzTech PenguinzTech commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Replaces the log-line-only @audit_log with a durable audit trail: audit_event table (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, and GET /api/v1/audit-events with filters (action, actor_id, tenant, resource_type, outcome, since/until) + pagination. audit:read granted 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 toward v2.1.x as 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:

  • Add tenant-scoped audit_event table with indices for actor, action, tenant, and resource for durable audit logging.
  • Expose GET /api/v1/audit-events API with filtering (tenant, actor, action, resource, outcome, date range) and pagination, gated by audit:read scope for SystemAdmin.
  • Extend the audit_log decorator to persist audit events to the database and emit structured JSON logs without PII.

Enhancements:

  • Propagate tenant information into the auth middleware context to support tenant-scoped auditing.

Build:

  • Add Alembic migration to create the audit_event table and related indices.

Tests:

  • Add comprehensive tests for the audit events API, including auth/scope enforcement, filtering, pagination, and response format.
  • Extend schema tests to assert creation of the new audit_event table.

PenguinzTech and others added 2 commits July 25, 2026 15:23
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>
@PenguinzTech PenguinzTech self-assigned this Jul 25, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 25, 2026

Copy link
Copy Markdown

🧙 Sourcery is reviewing your pull request!


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @PenguinzTech, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@PenguinzTech
PenguinzTech changed the base branch from chore/dedup-reusable-code to v2.1.x July 27, 2026 00:54
@PenguinzTech
PenguinzTech merged commit e5b4610 into v2.1.x Jul 27, 2026
3 of 4 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant