19. Audit Log

Append-only hash chain + SGX-signed checkpoint · cross-DB binding

Fireblocks 의 Audit Log 기능 (확인된 사실)

security-checklist.md, p.2: log / track / audit / export workspace events.

기록되는 이벤트 (Fireblocks 자료에서 확인된 것):

  • IP allowlist 추가·수정·활성화·비활성화
  • Recovery passphrase verification 알림 / 결과 / risk status
  • (그 외 lifecycle event 는 본 자료 명시 없음 — 구현체가 광범위 기록한다고 가정)

접근 권한

user-roles.md, p.7 — View all workspace settings including audit logs:

  • Owner / Admin / NSA / Security Auditor / Security Admin ✓

Hash Chain + SGX Checkpoint 의 영속화 모델

외부 감사자가 "log 가 변조되지 않았다"를 cryptographically 검증할 수 있어야 institutional-grade. 두 layer:

graph LR
  subgraph EVENTS["audit_events (append-only)"]
    E1["seq=1
prev_hash=0
current_hash=h1"] E2["seq=2
prev_hash=h1
current_hash=h2"] E3["seq=3
prev_hash=h2
current_hash=h3"] E4["seq=4
prev_hash=h3
current_hash=h4"] end subgraph CP["audit_checkpoints"] C1["checkpoint @ seq=4
checkpoint_hash=h4
sgx_signature
mrenclave"] end E1 --> E2 --> E3 --> E4 E4 -.signs.-> C1 EXT["External auditor"] EXT -.recompute chain.-> EVENTS EXT -.verify signature.-> C1
Figure 9. Hash chain + SGX checkpoint — 외부 감사자가 hash chain 재계산 + checkpoint signature 검증으로 정합성 입증.

DB Schema

-- ★★ append-only — BEFORE UPDATE / BEFORE DELETE 트리거로 강제 (23 참조)
CREATE TABLE audit_events (
  id                BINARY(16) PRIMARY KEY,
  partition_key     VARCHAR(64) NOT NULL,             -- e.g., workspace_id + month
  seq               BIGINT NOT NULL,                  -- per-partition monotonic
  event_type        VARCHAR(64) NOT NULL,             -- 'user-add', 'tx-broadcast', ...
  event_domain      VARCHAR(64) NOT NULL,             -- 'user', 'transaction', 'policy', ...
  source_aggregate_type VARCHAR(64) NOT NULL,
  source_aggregate_id BINARY(16) NOT NULL,
  source_db         VARCHAR(32) NOT NULL,             -- 'walletdb', 'ledgerdb', ...
  event_payload_cbor BLOB NOT NULL,                   -- ★ byte-equal cross-DB binding
  prev_hash         BINARY(32) NOT NULL,              -- prior seq 의 current_hash
  current_hash      BINARY(32) NOT NULL,              -- SHA256(prev_hash || payload || seq)
  actor_user_id     BINARY(16),
  occurred_at       DATETIME(6) NOT NULL,
  UNIQUE KEY (partition_key, seq),
  KEY (event_domain, occurred_at),
  KEY (source_aggregate_id)
);

-- ★ append-only — SGX/TEE 가 주기적으로 서명
CREATE TABLE audit_checkpoints (
  id                BINARY(16) PRIMARY KEY,
  partition_key     VARCHAR(64) NOT NULL,
  last_seq          BIGINT NOT NULL,
  checkpoint_hash   BINARY(32) NOT NULL,              -- = last seq 의 current_hash
  sgx_signature     BLOB NOT NULL,                    -- enclave signature
  sgx_mrenclave     BINARY(32) NOT NULL,              -- enclave measurement
  signed_at         DATETIME(6) NOT NULL,
  UNIQUE KEY (partition_key, last_seq)
);

Hash Chain 동작

seq=1: prev=0,  current = SHA256(0  || payload_1 || 1)
seq=2: prev=h1, current = SHA256(h1 || payload_2 || 2)
seq=3: prev=h2, current = SHA256(h2 || payload_3 || 3)
...

중간 row 변조 시 뒤의 모든 hash 가 깨짐 → 변조 감지

SGX-Signed Checkpoint 의 의미

  • 운영자가 hash chain 을 통째로 재계산해서 위조하더라도, SGX enclave 의 서명이 없으면 외부 감사자에게 무효
  • MRENCLAVE 가 known-good 값과 일치해야 enclave integrity 확보
  • 외부 감사자는 SGX attestation 또는 DCAP 으로 enclave 검증 가능

Cross-DB Binding

audit_events 의 event_payload_cbor 가 다른 DB 의 source row (예: approval_decisions, signing_events, transactions) 와 byte-equal CBOR encoding 일치해야 함. 이로써 cross-DB 일관성도 cryptographically 입증.

-- 검증 query (외부 감사자)
SELECT
  a.partition_key,
  a.seq,
  a.event_payload_cbor,
  (SELECT cbor_encode(approver_user_id, decision, decided_at, mobile_device_sig)
   FROM approverdb.approval_decisions
   WHERE id = a.source_aggregate_id) AS source_payload,
  a.event_payload_cbor = source_payload AS payload_matches
FROM auditdb.audit_events a
WHERE a.event_domain = 'approval'
  AND a.partition_key = ?;

Append-only Enforcement

Application code 가 evolve 하더라도 DB 가 거절해야 의미가 있음. 23. Storage Discipline 의 trigger 패턴 참조.