Skip to content

Phase 1: Local audit logging #936

Description

@HatemMn

This issue covers Phase 1 only: the audit event pipeline and local storage. SIEM export (OTel log signal, Splunk HEC, syslog/CEF) is tracked separately.

What gets built

  1. AuditEvent struct
pub struct AuditEvent {
    pub id:           u64,
    pub timestamp:    OffsetDateTime,
    pub operation:    String,       // e.g. "Encrypt", "Create", "Destroy"
    pub user:         String,       // from AuthenticatedUser or "unauthenticated"
    pub object_uid:   Option<String>,
    pub algorithm:    Option<String>,
    pub client_ip:    Option<String>,
    pub result:       AuditResult,  // Success | Failure(code)
    pub duration_ms:  u64,
    pub prev_hash:    [u8; 32],     // SHA-256 of previous event canonical bytes
    pub row_hash:     [u8; 32],     // SHA-256 of this event (excluding row_hash)
}
  1. Audit middleware: crate/server/src/middlewares/audit.rs
  • Follows the EnsureAuth structural pattern (Transform + Service)
  • Registered as the outermost .wrap() on default_scope in start_kms_server.rs
  • Reads AuthenticatedUser from request extensions (falls back to "unauthenticated" for 401s)
  • Captures response status after service.call(req).await to determine outcome
  • Feeds completed AuditEvent into the configured sinks
  1. [audit] config section :
text
[audit]
enabled = true

[audit.file]
enabled = true
path = "/var/log/cosmian_kms/audit.log"  # default if not set

[audit.db]
enabled = false
url = "postgresql://audit_writer:secret@localhost/kms_audit"

[audit.otlp], [audit.splunk_hec], [audit.syslog] slots are reserved but not implemented here.
  1. Storage backends
    This isn't a strict requirement yet - to be researched
    Current proposition :
    [audit.file] — always-on default, append-only JSONL file, hash-chained. Zero external dependencies. Every deployment gets a working audit trail with no config required.

    [audit.db] — optional. SQLite or PostgreSQL, separate from the key DB. KMS connects with a write-only credential (INSERT only). ckms audit export uses a separate read-only credential.

  2. ckms audit CLI subcommands

  • ckms audit export --since <date> --format json|cef # reads from the local audit store
  • ckms audit verify # walks the hash chain, reports first broken link

Out of scope for this issue

  • OTel log signal export
  • Splunk HEC export
  • Syslog/CEF export
  • auth_method field on AuthenticatedUser (noted as a possible future extension)

Acceptance criteria

Schema & payload safety

  • The AuditEvent struct captures: who (user), what (operation), when (timestamp), where (client_ip), on what (object_uid), and with what outcome (result, duration_ms) — all fields must be populated or explicitly None with a documented reason
  • Audit logs must not contain sensitive payloads: no plaintext key material, no credentials, no secrets in any logged field (OWASP A09, ISO 27001 A.8.15)

Runtime behavior

  • All operations passing through default_scope produce an AuditEvent
  • Auth failures (401) produce an event with user = "unauthenticated" and the client IP
  • An audit store write failure logs an ERROR in diagnostic logs but does not return an error to the client

Storage & tamper-evidence

  • [audit.db] and the key DB are demonstrably separate — different connection strings in test setup
  • The KMS process used for audit storage only appends — UPDATE and DELETE on the audit store are not issued by the KMS Rust source code; if INSERT-only DB privileges are not enforceable by the KMS itself (e.g. file backend), compliance is accepted by code review of the open-source Rust source
  • Hash chain is intact after a sequence of events and verifiable with ckms audit verify
  • ckms audit verify exits non-zero and identifies the broken link when any past event is modified or deleted

CLI & export

  • ckms audit export --format json produces valid, parseable JSON
  • ckms audit export --format cef produces valid CEF-formatted lines on stdout
  • ckms audit export output must not expose more personal data than what was logged — no enrichment, no joins against user directories at export time (GDPR/RGPD compliance)

Configuration & compliance defaults

  • KMS starts successfully with no [audit] section in config — audit is disabled by default, no error
  • When [audit] enabled = true with no further config, KMS writes to a stable default path without error
  • Retention period is configurable per storage backend; 6–12 months is documented as the default for EU/FR deployments (CNIL recommendation, NIS2)
  • [audit.otlp], [audit.splunk_hec], [audit.syslog] sub-tables are reserved in the config schema — Phase 2 can add them without breaking existing configs

Other info

Write AuditEvent to a dedicated local store (Postgres/SQLite/MySQL, separate from key storage)

Hash chaining for tamper-evidence

ckms audit export --since <date> --format json|cef reads from this local store

This alone satisfies most of the compliance requirement: you have a durable, tamper-evident, user-attributed record of every key operation

What is marked in bold is not a suggestion but a requirement for this issue approval - not satisfying that can fail the Audit phase in the future

It's also mandatory (for audit compliance) that the KMS is proven to ONLY write logs, and not backward tamper with them

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions