utilityjnr/The-Audit-Log-Expansion

GitHub: utilityjnr/The-Audit-Log-Expansion

通过 PostgreSQL JSONB 列记录金库配置变更的结构化前后差异,实现可精确查询的安全审计追踪。

Stars: 0 | Forks: 0

# 审计日志扩展 ## 概述 大多数 vault 系统中的安全审计只回答一个问题:*是否发生了某事?* 这远远不够。当一个多签 vault 的阈值悄无声息地从 3-of-5 降低到 1-of-5,或者当一个具有完整权重(full weight)的未知签名者被添加时,审计追踪需要回答一个更难的问题:*到底改变了什么,从什么变成了什么?* 本项目通过为现有的 `vault_audit_log` 表扩展一个 `payload JSONB` 列,来记录**每次 vault 配置变更的差异**——不仅是事件类型,还包括以结构化 JSON 序列化的变更前/后状态。Rust 后端使用了一个强类型的枚举(`VaultActionEvent`),以确保写入数据库的每个事件都是格式良好且可反序列化的。 ## 问题描述 原始的审计日志表结构如下: ``` CREATE TABLE vault_audit_log ( id SERIAL PRIMARY KEY, vault_id INTEGER NOT NULL, action_type TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); ``` 对于 vault `42`,某条记录可能会显示 `action_type = 'CONFIG_CHANGE'`,但该行中没有任何信息能告诉你: - 变更前的阈值是多少 - 被改成了多少 - 添加或移除了哪个签名者 - 该签名者携带的权重是多少 如果没有这些数据,事件响应将如同盲人摸象。你知道*发生了一次*变更;但你不知道*具体变更了什么*。 ## 解决方案 在 `vault_audit_log` 表中新增一个 `payload JSONB` 列,并在每次配置变更时使用序列化后的 `VaultActionEvent` 枚举值来填充它。该枚举是现有事件及其携带字段的唯一事实来源(single source of truth)。 ## 架构 ``` ┌─────────────────────────────────────────────────────┐ │ Rust Backend │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ VaultActionEvent (serde Serialize/Deserialize)│ │ │ │ │ │ │ │ ThresholdChange { old: u32, new: u32 } │ │ │ │ SignerAdded { address: String, │ │ │ │ weight: u32 } │ │ │ │ VaultCreated │ │ │ └──────────────┬───────────────────────────────┘ │ │ │ serde_json::to_value() │ │ ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ log_event(pool, vault_id, event) │ │ │ │ INSERT INTO vault_audit_log │ │ │ │ (vault_id, action_type, payload) │ │ │ └──────────────┬───────────────────────────────┘ │ │ │ sqlx async query │ └─────────────────┼───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ PostgreSQL │ │ │ │ vault_audit_log │ │ ┌──────────┬──────────┬─────────────┬───────────┐ │ │ │ vault_id │action_type│ payload │created_at │ │ │ ├──────────┼──────────┼─────────────┼───────────┤ │ │ │ 42 │CONFIG_CHANGE│{"type": │2026-05-11 │ │ │ │ │ │"Threshold │ │ │ │ │ │ │Change", │ │ │ │ │ │ │"old":2, │ │ │ │ │ │ │"new":3} │ │ │ │ └──────────┴──────────┴─────────────┴───────────┘ │ └─────────────────────────────────────────────────────┘ ``` **数据流:** 1. 触发一次 vault 配置变更(阈值更新、签名者添加、vault 创建)。 2. 调用者使用相关的差异字段构建一个 `VaultActionEvent` 变体。 3. `log_event()` 将其序列化为 `serde_json::Value`,并与 `vault_id` 和 `action_type` 一起插入。 4. PostgreSQL 将 payload 作为原生 JSONB 存储,使其可以通过 `->` / `->>` 操作符进行索引和查询。 ## 表结构 ### 迁移文件 ``` -- migrations/001_add_payload_to_vault_audit_log.sql ALTER TABLE vault_audit_log ADD COLUMN IF NOT EXISTS payload JSONB; ``` ### 生成的表结构 | 列名 | 类型 | 描述 | |---|---|---| | `id` | `SERIAL` | 主键 | | `vault_id` | `INTEGER` | 该事件所属的 vault | | `action_type` | `TEXT` | 高层分类,例如 `CONFIG_CHANGE` | | `payload` | `JSONB` | 结构化的差异数据(见下文事件类型) | | `created_at` | `TIMESTAMPTZ` | 插入时自动设置 | ## 事件类型 `VaultActionEvent` 枚举是每个可审计事件的规范定义。`#[serde(tag = "type")]` 属性会将变体名称作为 `"type"` 键嵌入到 JSON 中,使 payload 具备自描述性。 ``` #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type")] pub enum VaultActionEvent { ThresholdChange { old: u32, new: u32 }, SignerAdded { address: String, weight: u32 }, VaultCreated, } ``` ### 序列化示例 **ThresholdChange** — 阈值从 3 降至 2: ``` { "type": "ThresholdChange", "old": 3, "new": 2 } ``` **SignerAdded** — 添加权重为 1 的新签名者: ``` { "type": "SignerAdded", "address": "0xabc...def", "weight": 1 } ``` **VaultCreated** — 无需差异字段: ``` { "type": "VaultCreated" } ``` ## 后端代码 ### `backend/src/models/audit.rs` ``` use serde::{Deserialize, Serialize}; use sqlx::PgPool; #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type")] pub enum VaultActionEvent { ThresholdChange { old: u32, new: u32 }, SignerAdded { address: String, weight: u32 }, VaultCreated, } pub async fn log_event( pool: &PgPool, vault_id: i32, event: VaultActionEvent, ) -> Result<(), sqlx::Error> { let payload = serde_json::to_value(event).unwrap(); sqlx::query!( "INSERT INTO vault_audit_log (vault_id, action_type, payload) VALUES ($1, $2, $3)", vault_id, "CONFIG_CHANGE", payload ) .execute(pool) .await?; Ok(()) } ``` ### `backend/src/main.rs` ``` mod models; use models::audit::{log_event, VaultActionEvent}; use sqlx::postgres::PgPoolOptions; #[tokio::main] async fn main() -> Result<(), sqlx::Error> { let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = PgPoolOptions::new().connect(&database_url).await?; // Example: log a threshold change on vault 1 log_event(&pool, 1, VaultActionEvent::ThresholdChange { old: 2, new: 3 }).await?; Ok(()) } ``` ### `backend/Cargo.toml` ``` [package] name = "audit-log-expansion" version = "0.1.0" edition = "2021" [dependencies] sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] } ``` ## 在 PostgreSQL 中查询 Payload 由于 `payload` 是原生 JSONB,你可以直接在 SQL 中过滤并提取字段: ``` -- All threshold changes across all vaults SELECT vault_id, payload->>'old' AS old_threshold, payload->>'new' AS new_threshold, created_at FROM vault_audit_log WHERE payload->>'type' = 'ThresholdChange'; -- Signers added to a specific vault SELECT payload->>'address' AS signer, payload->>'weight' AS weight, created_at FROM vault_audit_log WHERE vault_id = 42 AND payload->>'type' = 'SignerAdded'; -- Any threshold lowered (potential security event) SELECT * FROM vault_audit_log WHERE payload->>'type' = 'ThresholdChange' AND (payload->>'new')::int < (payload->>'old')::int; ``` ## 项目结构 ``` The Audit Log Expansion/ ├── README.md ├── migrations/ │ └── 001_add_payload_to_vault_audit_log.sql └── backend/ ├── Cargo.toml └── src/ ├── main.rs └── models/ ├── mod.rs └── audit.rs ``` ## 设置与运行 ``` # 1. 应用 migration psql $DATABASE_URL -f migrations/001_add_payload_to_vault_audit_log.sql # 2. 运行 backend cd backend DATABASE_URL=postgres://user:pass@localhost/dbname cargo run ```
标签:JSONB数据类型, PostgreSQL数据库, Rust编程语言, Web3安全, 加密资产金库, 区块链基础设施, 变更追踪, 可视化界面, 后端开发, 多签钱包, 审计日志, 序列化枚举, 数据差异比对, 数据库模式扩展, 数据库设计, 测试用例, 结构化数据