Audit module
Source: modules/audit/ (ce module); the licence gate overlay lives in this repo’s modules/audit/.
At a glance
Section titled “At a glance”| CLI | shc events --audit (owned by the events module) |
| Subcommands | 0 |
| HTTP endpoints | see the route reference |
| Events emitted | — |
| Error codes | see the error code reference |
| Service classes | AuditService |
| Cross-module deps | — |
Source layout
Section titled “Source layout”modules/audit/├── command.go├── errors.go├── events.go├── router.go├── service.go├── models.go└── commands/CLI entry points
Section titled “CLI entry points”shc events --audit— view audit logs (the standaloneshc auditcommand was absorbed into the events module)
HTTP API
Section titled “HTTP API”Routes are listed in the generated route reference; request/response notes live in the API reference.
Events emitted
Section titled “Events emitted”The audit module emits no topics of its own — it persists and serves
the events other modules publish. The auth failure/permission topics
this page previously tabulated are registered by the auth module; see
the generated event reference.
Errors
Section titled “Errors”Typed errors are catalogued in the error code reference with HTTP statuses and message templates.
Responsibilities
Section titled “Responsibilities”The audit module owns persistent, tamper-evident history of every
significant action in SHC. It writes to the AuditModel table, one row
per event, in a hash chain: each row stores prev_hash + its own
computed hash over (code, message, payload, prev_hash, …). Breaking
the chain is detectable after the fact.
The single public class is AuditService in service.go:
Persist(event, ...)— accepts an event, resolves thecodeand the format-stringmsg, captures the current actor (OS user or authenticated principal viagetActor), captures the OpenTelemetry trace/span IDs viagetTraceInfo, reads the previous row’s hash, computes the new row’s hash, and commits. Tenant / stack / environment from context are recorded for scoped queries.List(...)/Count(...)— scoped reads filtered by tenant, stack, environment, event code, actor, and time range. Used by the CLI (shc events --audit) and the HTTP endpoints under/api/method/shc.audit.*.
Audit rows are also republished on the event bus (see
events) so the UI can render them live without polling,
and so multi-node clusters can correlate audit events across nodes by
trace ID.
What the audit module does not own: the event types
themselves — those are defined in each owning module’s events.go and
use event code constants. This module only records the fact that an event
happened. It also does not own broadcasting to SSE clients (that is
events).
Schema
Section titled “Schema”AuditModel first-class columns (in addition to event code, message,
payload, hash chain):
| Field | Description | Resolution order |
|---|---|---|
actor_id | Who performed the action | JWT sub → CLI os.UserLookup → server user |
action | The operation performed | Mnemonic from event (e.g. STKSCL) |
origin | Connection method | cli / api / socket / system |
client_ip | Originating IP | leftmost X-Forwarded-For → X-Real-IP → socket IP; UDS = local |
client_host | Originating hostname | resolvable hostname or local for UDS |
server_host | Server host | hostname where the SHC server is running |
trace_id | Distributed trace ID | OpenTelemetry trace context |
span_id | Distributed span ID | OpenTelemetry trace context |
Actor resolution
Section titled “Actor resolution”Three sources, in priority order:
- Authenticated Keycloak session — every TCP request carries a
Bearer token that the auth middleware resolves to a
User; the token’spreferred_usernamebecomesactor_id. - Unix-domain-socket superadmin — UDS requests are tagged by
UDSMarkerMiddlewareand treated asLocalUser()— they bypass Keycloak entirely and are recorded as a local superadmin actor. - System fallback — for system-triggered tasks (crons, internal
reconciliation),
actor_idis the OS user running the SHC server.
Vault auditing
Section titled “Vault auditing”Vault operations are first-class audit events:
Aprefix —set,update,delete,export,import, andget(read).Vprefix —list(listing keys is telemetry, not audit).
Audit rows must never contain secret values, only the keys accessed.
When to emit
Section titled “When to emit”All mutable API endpoints emit an audit event on success. The event is emitted in the service layer after the mutation succeeds, not in the router:
func (s *StackService) Scale(ctx context.Context, ...) (*ScaleResult, error) { // ... perform scaling ... log.Event(ctx, events.A200400STKSCL, map[string]any{ "service": service, "replicas": replicas, "stack": stackName, }) return &ScaleResult{...}, nil}This guarantees an audit row exists for every observed state change, regardless of whether the trigger was a CLI call, HTTP request, or internal cron.