- Expanded database schema to include `leaf_hash` in `audit_records` and added `audit_sths` table. - Implemented `server/audit_merkle.ts` for native WebCrypto RFC 6962 tree computations and inclusion proofs. - Created asynchronous micro-batcher in `server/audit.ts` to compute STH, sign with SPIFFE key, save to DB, and broadcast via Valkey. - Refactored `auditLog` to compute leaf hashes synchronously before database inserts. - Added hermetic unit tests with mock fallback patterns for SPIFFE/FFI in `server/audit_merkle.test.ts`. Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
118 lines
5.3 KiB
Markdown
118 lines
5.3 KiB
Markdown
# TASK METADATA
|
|
|
|
- **Target Files:** `server/audit.ts`, `server/audit_merkle.ts`,
|
|
`server/valkey.ts`, `server/db.ts`, and database schema/migration files.
|
|
- **Core Objective:** Implement an append-only RFC 6962 Merkle Tree audit ledger
|
|
in PostgreSQL with an in-process Deno micro-batcher for STH computation and
|
|
Valkey pub/sub broadcast.
|
|
- **Dependencies:** Deno 2.x WebCrypto (Ed25519), Valkey connection, PostgreSQL
|
|
schema updates, and SPIFFE workload identity key material.
|
|
- **Additional Important Notes:** Must use Deno's native WebCrypto (no external
|
|
crypto npm packages). Valkey session keys should use raw strings without
|
|
'session:' prefix (not fully applicable here, but good standard). Ensure tests
|
|
mock Valkey, PostgreSQL, and SPIFFE cleanly.
|
|
|
|
---
|
|
|
|
## Architectural Considerations & Risks
|
|
|
|
Before implementing the RFC 6962 Merkle Tree Ledger, we must evaluate the
|
|
current architecture and explicitly identify risks, regressions, and necessary
|
|
constraints.
|
|
|
|
**Risks & Tradeoffs:**
|
|
|
|
1. **In-Process Micro-Batcher Memory & State:** The micro-batcher runs
|
|
in-process inside Deno using asynchronous intervals (e.g., `setInterval` or
|
|
explicit flush). If the Deno process crashes abruptly, unflushed audit logs
|
|
in memory could be lost before the batch is committed to PostgreSQL and the
|
|
Merkle tree head is updated.
|
|
- _Mitigation:_ Ensure `auditLog` still performs an immediate async insert of
|
|
the _record_ to the database (as it currently does via `server/audit.ts`),
|
|
but the `leaf_hash` and tree updates are computed dynamically or buffered
|
|
for the batched STH computation. The database remains the source of truth;
|
|
the batcher just finalizes the cryptographical proof.
|
|
2. **Database Schema Locking:** Adding a `leaf_hash` column to the
|
|
`audit_records` table and creating the `audit_sths` table might cause locking
|
|
if the table is large.
|
|
- _Mitigation:_ The `leaf_hash` can be computed prior to insert.
|
|
3. **SPIFFE Identity Dependency:** STH generation requires the Deno process to
|
|
sign using the SPIFFE workload identity/server signing key via WebCrypto. If
|
|
SPIFFE/FFI fails or the keys are rotating during a batch, the STH generation
|
|
might fail.
|
|
- _Mitigation:_ Graceful error handling and retry mechanism for STH signing.
|
|
4. **No Third-Party Dependencies:** Adhering to the memory context, we must
|
|
exclusively use Deno 2.x native `crypto.subtle` API for SHA-256 and
|
|
Ed25519/ECDSA, rather than any Node.js polyfills or npm packages.
|
|
|
|
**Alternatives Evaluated:**
|
|
|
|
- _External Worker:_ Using a dedicated worker (e.g., a separate service or CRON)
|
|
for computing the Merkle root. _Decision:_ We rejected this to keep the
|
|
application self-contained and zero-dependency, per user guidance.
|
|
- _Synchronous Tree Updates:_ Recomputing the tree on every single write.
|
|
_Decision:_ Rejected. This would severely block relational write throughput. A
|
|
micro-batcher (e.g., 30-60s or size threshold) is the optimal path for
|
|
decoupling heavy crypto logic from the request hot-path.
|
|
|
|
## Proposed Implementation
|
|
|
|
The execution of this feature will be broken down into the following structured
|
|
phases:
|
|
|
|
### Phase 1: Database Schema Expansion
|
|
|
|
1. Update PostgreSQL schema to alter `audit_records` and add a `leaf_hash TEXT`
|
|
column.
|
|
2. Create the new `audit_sths` table:
|
|
```sql
|
|
CREATE TABLE audit_sths (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tree_size BIGINT NOT NULL,
|
|
root_hash TEXT NOT NULL,
|
|
signature TEXT NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### Phase 2: RFC 6962 Cryptographic Primitives (`server/audit_merkle.ts`)
|
|
|
|
1. Create `server/audit_merkle.ts`.
|
|
2. Implement RFC 6962 leaf hashing: `SHA-256(0x00 || entry_bytes)`.
|
|
3. Implement internal tree hashing:
|
|
`SHA-256(0x01 || left_child || right_child)`.
|
|
4. Implement the logic to construct the Merkle Tree from an array of leaves and
|
|
generate an inclusion proof (`verifyInclusionProof`).
|
|
5. Ensure all crypto utilizes `crypto.subtle.digest('SHA-256', ...)`.
|
|
|
|
### Phase 3: The Micro-Batcher & STH Signing (`server/audit.ts`)
|
|
|
|
1. Refactor `auditLog` to compute the `leaf_hash` synchronously before the async
|
|
database insert.
|
|
2. Implement a background micro-batcher in `server/audit.ts` (or
|
|
`server/audit_merkle.ts` if better decoupled) using `setInterval` (e.g.,
|
|
every 1000ms, or on explicit flush).
|
|
3. The batcher will:
|
|
- Query new `leaf_hash` entries from the database since the last STH
|
|
`tree_size`.
|
|
- Compute the new Merkle Root.
|
|
- Access the SPIFFE server signing key.
|
|
- Sign the STH (tree size, root hash) using `crypto.subtle.sign` (Ed25519).
|
|
- Insert the new STH into the `audit_sths` table.
|
|
|
|
### Phase 4: Valkey STH Broadcast (`server/valkey.ts`)
|
|
|
|
1. Once the STH is successfully saved to PostgreSQL, update the cache.
|
|
2. Use `valkey.set('auth:audit:latest_sth', json_payload)`.
|
|
3. Broadcast the update to independent witness nodes via pub/sub:
|
|
`valkey.publish('auth:audit:sth', json_payload)`.
|
|
|
|
### Phase 5: Testing & Quality Gates
|
|
|
|
1. Write hermetic tests in `server/audit_merkle.test.ts`.
|
|
2. Use `@std/testing/mock` to mock `valkey`, `sqlWrapper`, and SPIFFE keys.
|
|
3. Validate leaf hashing correctness against RFC 6962 test vectors.
|
|
4. Verify the Valkey `PUBLISH` payload formatting.
|
|
5. Execute standard quality gates: `deno fmt`, `deno task lint`,
|
|
`deno task check`, and `deno task test`.
|