feat: opt-in structured access-audit logging - #62
Conversation
Adds telemetry.audit.enabled (default: false). When enabled, every HTTP request emits one JSON line on stdout recording who accessed what: identity from the X-Auth-Request-Email/-User headers an authenticating reverse proxy injects (DICOM-RST itself has no auth, see #15/#42), source from X-Forwarded-For, plus method, full path+query, extracted aet/study/series/instance path parameters, status and duration. Delivery is fail-open by design: records flow through a bounded channel to a stdout writer task; a full buffer drops the record and logs a rate-limited warning instead of ever blocking request handling. Also: log the Study Instance UID on C-MOVE completion so DIMSE-side retrievals are attributable too, and disable ANSI colors when stdout is not a terminal so container logs stay machine-parseable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9PMSu4fFsV5dSNojTTQaG
|
I think it would make sense to integrate OIDC authorization first. So we could track the subject from the JWT. Reading the user & mail from some HTTP header fields is some special case when deployed behind a reverse proxy that is not really the default setup. |
|
We have in Bonn developed a RUST based LDAP Audit Microservices with JWT with Access Logging to a PostGres. Would you be interested in recycling this? |
|
I'd absolutely upvote a native oidc support 👍 |
chrono is already in the dependency graph via dicom-core (with the clock feature enabled), so the hand-rolled RFC 3339 formatter bought nothing. Declared directly (default-features = false, clock only) rather than leaning on the transitive edge. Output format unchanged: 2026-08-17T17:16:55Z. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9PMSu4fFsV5dSNojTTQaG
What
Adds an opt-in access-audit log:
telemetry.audit.enabled(default:false— zero behavior change unless enabled). When on, every HTTP request emits one self-contained JSON line on stdout:{"audit":"http-access","ts":"2026-08-17T17:16:55Z","user":"alex@example.com", "subject":"5939ae08-…","source":"10.249.9.1","method":"GET", "path":"/aets/GEPACS/studies/1.2.840…","aet":"GEPACS","study":"1.2.840…", "status":200,"duration_ms":4886}Motivation: in clinical deployments, "who accessed which study, when" is a legal requirement. Log pipelines (Loki, ELK, …) can select these lines by the
auditdiscriminator and ship them to WORM storage.Design
X-Auth-Request-Email/X-Auth-Request-User— the headers oauth2-proxy (set_xauthrequest) and friends inject. Documented caveat: trustworthy only when the proxy is the sole ingress. Records are emitted even without identity — an anonymous access is itself audit-relevant.warn!with a running drop counter. A slow collector can never block or fail request handling; deployments with stricter needs alert on the drop warnings.aet/study/series/instancepath parameters viaRawPathParams, firstX-Forwarded-Forhop, status, duration, user agent. Absent fields are omitted from the JSON.chrono(to_rfc3339_opts(SecondsFormat::Secs, true)), which was already a transitive dependency viadicom-core— now declared directly withdefault-features = false, features = ["clock"].Also included
movescu: log the Study Instance UID on successful C-MOVE completion, so DIMSE-side retrievals are attributable in the same log stream.with_ansi(is_terminal)on the fmt layer — container logs lose the escape codes, TTY output keeps colors.docs/topics/configuration.mdgained atelemetry.audit.enabledsection including the trust-boundary note.Testing
cargo fmt --check,cargo clippy --all-targets(0 warnings),cargo test(19/19, incl. new unit tests for the RFC 3339 formatter and record serialization). Running in our prod/dev clusters behind oauth2-proxy; sample line above is real (redacted).🤖 Generated with Claude Code
https://claude.ai/code/session_01Q9PMSu4fFsV5dSNojTTQaG