Structured Audit Event Logging
- Status: Accepted
- Date: 2026-04-06
Context and Problem Statement
NAAS emits structured audit events for compliance and security tracking via
emit_audit_event() in naas/library/audit.py. The system grew organically from
job lifecycle events and now needs to cover authentication, authorization, and key
management. Design decisions about log routing, identity representation, schema
enforcement, and data privacy need to be recorded before the event catalog grows
further.
Decision Drivers
- Audit events must be machine-parseable for SIEM ingestion
- Sensitive data (credentials, command output) must never appear in audit logs
- Events must have consistent identity representation across auth methods
- Schema enforcement must prevent silent data loss in audit trails
- The pattern must be easy to extend for future features (webhooks, OpenTelemetry)
Decision Outcome
Structured JSON events emitted via Python's logging module with schema validation
at emit time. All events flow through the existing NAAS logger to stdout. Routing,
retention, and tamper-evidence are deployment concerns, not application concerns.
Design
Log Routing
All audit events use the shared NAAS logger at INFO level. Audit events are
distinguished from operational logs by the presence of an event_type field in the
structured JSON output.
Operators who need separate audit log files or syslog forwarding configure this at
the deployment layer (additional Python logging handler, or log shipper filter on
event_type). The application does not manage log destinations.
Rationale: A separate NAAS.audit logger was considered but rejected — it adds
configuration complexity with no application-level benefit. The structured
event_type field is sufficient for filtering.
Schema Validation
Every event type has a required field set defined in _EVENT_SCHEMAS. Calling
emit_audit_event() with a missing field raises ValueError immediately rather
than emitting an incomplete record.
Rationale: Audit trails with missing fields are worse than no audit trail — they create false confidence. Fail-loud at emit time catches bugs in development rather than discovering gaps during incident review.
Identity Representation
| Auth method | identity value |
Example |
|---|---|---|
| Basic auth | Username from Authorization header | "admin" |
| Bearer JWT | Key ID from sub claim |
"k-a1b2c3d4e5f6" |
| Unauthenticated | "anonymous" |
"anonymous" |
Job-level events continue to use user_hash (salted SHA512 of credentials) for
backward compatibility and because job events correlate with device connections, not
API identity.
Data Privacy
Audit events MUST NOT contain:
- Passwords, enable secrets, or API key tokens
- Command output or device responses
- Full request/response bodies
Audit events MAY contain:
- Usernames and key IDs (identity, not credentials)
- Host/IP being targeted
- Context, platform, port
- Command count (not command content)
- Job IDs and status
Event Catalog
Authentication
| Event | Fields | Description |
|---|---|---|
auth.success |
method, identity |
Successful authentication |
auth.failure |
method, reason |
Failed authentication attempt |
Authorization
| Event | Fields | Description |
|---|---|---|
auth.context_denied |
identity, context, allowed_contexts |
Context authorization failure |
auth.rbac_denied |
identity, role, required_role, endpoint |
Role check failure |
API Key Management
| Event | Fields | Description |
|---|---|---|
apikey.created |
key_id, role, contexts, created_by |
New API key created |
apikey.revoked |
key_id, revoked_by |
API key revoked |
Job Lifecycle (existing)
| Event | Fields | Description |
|---|---|---|
job.submitted |
host, platform, port, command_count, user_hash, request_id |
Job enqueued |
job.completed |
request_id, status, duration_ms |
Job finished |
job.cancelled |
request_id, cancelled_by_hash |
Job cancelled |
job.orphaned |
request_id, worker_name |
Orphaned job reaped |
Device (existing)
| Event | Fields | Description |
|---|---|---|
device.locked_out |
host, failure_count |
Device locked out |
circuit.opened |
host |
Circuit breaker opened |
circuit.closed |
host |
Circuit breaker closed |
Adding New Events
- Add the event type and required fields to
_EVENT_SCHEMAS - Call
emit_audit_event()at the appropriate point - Add a unit test verifying the event is emitted with correct fields
- Update this ADR's event catalog
Deployment Guidance
- SIEM integration: Filter structured JSON logs on
event_typefield - Separate audit file: Add a second
logging.FileHandlerto theNAASlogger with a filter onevent_type - Tamper evidence: Ship logs to an append-only store (S3, CloudWatch Logs)
- Retention: Configure via log rotation or cloud lifecycle policies
- Alerting: Key events to alert on:
auth.failure(brute force),auth.rbac_denied(privilege escalation),apikey.created/apikey.revoked(key management)