Skip to content

Conversation

@JPLachance
Copy link

Summary

This PR adds the foundational plugin infrastructure to enable streaming Access audit events to external systems (SIEM, logging platforms, analytics tools). This addresses the core need from #343 for exporting audit logs to destinations like S3, Splunk, Datadog, etc.

What's included

This PR provides the infrastructure layer that enables plugin developers to capture audit events. It does not include any specific destination plugins (S3, Kinesis, etc.) — those will come in follow-up PRs.

Core changes:

  • Plugin system: New AuditEventsPluginSpec using Python pluggy framework
  • Event envelope: AuditEventEnvelope dataclass with complete WHO/WHAT/WHEN/WHY context
  • Event emission: All 18 state-changing operations now emit audit events after DB commit
  • Error isolation: Plugin failures cannot crash Access operations (try/catch around all hook calls)
  • Tests: Comprehensive test coverage for plugin infrastructure
  • Documentation: Updated README with audit events plugin documentation

How it works

After any state-changing operation (approve access, add user to group, delete app, etc.):

  1. Access commits the change to the database
  2. An AuditEventEnvelope is created with full context
  3. The pluggy hook calls all registered plugins
  4. Plugins receive the event and can stream it wherever needed (S3, Splunk, etc.)

Plugins are discovered via setuptools entrypoints, following the same pattern as existing notification and conditional access plugins.

Event coverage

Events emitted for: access requests, role requests, apps, groups, tags, group memberships, role assignments (20 distinct event types covering all state changes)

Additional changes

  • Import organization: Ran isort to alphabetize imports across all modified files for consistency
  • Circular import fixes: Fixed circular import issues in api/operations/delete_user.py and constraint files that isort uncovered
  • Schema bug fix: Fixed missing comma in api/views/schemas/core_schemas.py tuple (line 323)
  • README typo: Fixed incorrect link reference in plugin documentation (requests.pyconditional_access.py)

Next steps

Follow-up PR will add an example S3 plugin implementation demonstrating how to use this infrastructure.

Testing

  • ✅ All existing tests pass
  • ✅ New tests verify plugin hook functionality
  • ✅ Tests confirm system works with zero plugins installed
  • ✅ Event envelope structure validated for all event types

Closes #343

Add pluggy-based plugin system for streaming audit events to external systems (SIEM, logging platforms, analytics).

- Add AuditEventsPluginSpec with audit_event_logged hook
- Add AuditEventEnvelope dataclass with WHO/WHAT/WHEN/WHY context
- Emit audit events from all 18 state-changing operations after DB commit
- Add error isolation to prevent plugin failures from breaking operations
- Add comprehensive test coverage for plugin infrastructure
- Update README to document audit events plugin capability

Signed-off-by: Jean-Philippe Lachance <[email protected]>
Copilot AI review requested due to automatic review settings December 6, 2025 05:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces the foundational plugin infrastructure for streaming Access audit events to external systems (SIEM, logging platforms, analytics tools). It establishes a pluggy-based plugin system with a comprehensive event envelope containing WHO/WHAT/WHEN/WHY context, and emits audit events after all state-changing operations complete.

Key changes:

  • New AuditEventsPluginSpec and AuditEventEnvelope classes for the plugin system
  • Audit event emission integrated into 18 state-changing operations across access requests, role requests, apps, groups, tags, and role assignments
  • Comprehensive error isolation with try-catch blocks to prevent plugin failures from affecting core operations
  • Import organization improvements and circular import fixes discovered during integration

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
api/plugins/audit_events.py Core plugin specification defining AuditEventsPluginSpec, AuditEventEnvelope dataclass, and hook initialization
api/plugins/init.py Exports audit events plugin hooks and markers for external plugin implementations
tests/test_audit_events.py Comprehensive tests for envelope structure, hook functionality, and plugin infrastructure
api/operations/approve_access_request.py Emits access_approve audit event after access request approval
api/operations/reject_access_request.py Emits access_reject audit event after access request rejection
api/operations/create_access_request.py Emits access_create audit event after access request creation
api/operations/approve_role_request.py Emits role_request_approve audit event after role request approval
api/operations/reject_role_request.py Emits role_request_reject audit event after role request rejection
api/operations/create_role_request.py Emits role_request_create audit event after role request creation
api/operations/modify_group_users.py Emits 4 event types for member/owner additions and removals
api/operations/modify_role_groups.py Emits 2 event types for role group assignments and unassignments
api/operations/create_group.py Emits group_create audit event after group creation
api/operations/delete_group.py Emits group_delete audit event after group deletion
api/operations/modify_group_tags.py Emits tag_update audit event when group tags are modified
api/operations/create_app.py Emits app_create audit event after app creation
api/operations/delete_app.py Emits app_delete audit event after app deletion
api/operations/modify_app_tags.py Emits tag_update audit event when app tags are modified
api/operations/create_tag.py Emits tag_create audit event after tag creation
api/operations/delete_tag.py Emits tag_delete audit event after tag deletion
api/operations/modify_group_type.py Import reorganization only (isort)
api/operations/unmanage_group.py Import reorganization and log message formatting improvement
api/operations/delete_user.py Fixed circular import by using explicit import path
api/operations/constraints/check_for_self_add.py Import reorganization (isort)
api/operations/constraints/check_for_reason.py Import reorganization (isort)
api/operations/init.py Alphabetized imports for consistency
api/views/schemas/core_schemas.py Fixed missing comma in tuple definition (bug fix)
README.md Fixed incorrect plugin filename reference from requests.py to conditional_access.py

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -1,9 +1,7 @@
import asyncio
from datetime import UTC, datetime
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent import style: This file imports UTC from datetime and uses datetime.now(UTC) for timestamps (lines 587, 620, 652, 685), while most other files in this PR import timezone and use datetime.now(timezone.utc). While both are functionally equivalent, consider using timezone.utc consistently across all files for better maintainability and code uniformity.

Copilot uses AI. Check for mistakes.
@@ -1,9 +1,7 @@
import asyncio
from datetime import UTC, datetime
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent import style: This file imports UTC from datetime and uses datetime.now(UTC) for timestamps (lines 520, 553, 587, 619), while most other files in this PR import timezone and use datetime.now(timezone.utc). While both are functionally equivalent, consider using timezone.utc consistently across all files for better maintainability and code uniformity.

Suggested change
from datetime import UTC, datetime
from datetime import datetime, timezone

Copilot uses AI. Check for mistakes.
await asyncio.wait(async_tasks)

# Emit audit events to plugins (after DB commit) - 2 event types
try:
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

[nitpick] For consistency and robustness, consider redefining context = has_request_context() inside this try block (similar to line 574 in modify_group_users.py). While context is currently available from line 168, re-declaring it here makes the code more self-contained and resilient to future refactoring.

Suggested change
try:
try:
context = has_request_context()

Copilot uses AI. Check for mistakes.
@JPLachance
Copy link
Author

And yes, I tested it end to end:

  • Created a plugin that sends audits to SQS, following the OCSF schemas
  • SQS ← Logstash → OpenSearch
  • OpenSearch Dashboards

This made it much easier to visualize what’s changing, when, why, and by whom.

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.

Feature Request: Stream Audit Logs to AWS S3 (or similar destinations)

1 participant