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安全, 加密资产金库, 区块链基础设施, 变更追踪, 可视化界面, 后端开发, 多签钱包, 审计日志, 序列化枚举, 数据差异比对, 数据库模式扩展, 数据库设计, 测试用例, 结构化数据