对应 5 份设计规格,共 75 个 Task: 1. 性能优化 (12 Task) — 批量INSERT/N+1内联name/合并COUNT/按需重绘/chunk拆分 2. 安全纵深防御 (8 Task) — RLS/行级数据范围/Redis session_key/审计哈希链 3. 事件驱动架构 (10 Task) — 11个缺失事件补发/LISTEN+NOTIFY/schema版本化 4. 前端工程化 (10 Task) — hook统一/组件拆分/Bundle优化 5. 可观测性运维 (10 Task) — 深度健康检查/Prometheus/OTel/生产Docker/告警
90 lines
4.7 KiB
Markdown
90 lines
4.7 KiB
Markdown
# 安全纵深防御实施计划
|
||
|
||
> 设计规格: `docs/superpowers/specs/2026-04-26-security-defense-in-depth-design.md`
|
||
> 日期: 2026-04-26 | 状态: draft | 总周期: 2-3 周
|
||
|
||
---
|
||
|
||
## Phase 1: PostgreSQL RLS 安全网(Week 1)
|
||
|
||
### Task 1: 创建 RLS 策略迁移
|
||
|
||
**涉及文件**: `crates/erp-server/migration/src/m000073_enable_rls_all_tables.rs`(新增), `lib.rs`
|
||
|
||
**步骤**: 对所有含 tenant_id 的表(30 基础 + 34 健康)执行 `ALTER TABLE ENABLE ROW LEVEL SECURITY` + `CREATE POLICY tenant_isolation USING (tenant_id = current_setting('app.current_tenant_id')::uuid)` + `CREATE POLICY tenant_bypass USING (current_user IN ('erp_admin', 'erp_migration'))`。创建 `erp_app` 数据库角色。down 方法完整回退。
|
||
|
||
**验收**: 迁移执行成功;以 erp_app 角色未设置 tenant_id 时查询返回空;现有应用行为不变。
|
||
|
||
### Task 2: Axum middleware 设置当前连接的 tenant_id
|
||
|
||
**涉及文件**: `crates/erp-server/src/middleware/tenant.rs`
|
||
|
||
**步骤**: tenant 中间件从 JWT 解析 tenant_id 后执行 `SET LOCAL app.current_tenant_id = '<id>'`。确认在事务内执行(SeaORM 显式事务包装或 session 级 SET + RESET)。添加 tracing 日志记录注入状态。SET LOCAL 失败时 warn 但不阻断请求。
|
||
|
||
**验收**: 注入后以 erp_app 角色查询自动按 tenant_id 过滤;`cargo test --workspace` 通过。
|
||
|
||
### Task 3: 验证现有测试不受影响
|
||
|
||
**涉及文件**: 可能修改测试辅助代码
|
||
|
||
**步骤**: 运行 `cargo test --workspace` 检查 RLS 导致的测试失败。分析失败原因(测试未设置 tenant_id → 查询返回空),在 TestDb/TestApp 初始化时注入 tenant_id。不修改任何业务逻辑代码。
|
||
|
||
**验收**: `cargo test --workspace` 全部通过。
|
||
|
||
---
|
||
|
||
## Phase 2: 行级数据范围 + session_key Redis(Week 2)
|
||
|
||
### Task 4: require_permission 增加 data_scope 过滤逻辑
|
||
|
||
**涉及文件**: `erp-core/src/types.rs`(TenantContext 增加 permission_data_scopes), `erp-core/src/rbac.rs`(apply_data_scope 函数), JWT 中间件, `erp-health/src/handler/mod.rs`
|
||
|
||
**步骤**: TenantContext 新增 `permission_data_scopes: HashMap<String, DataScope>`(枚举 All/Self/Department/DepartmentTree)。JWT 中间件查询 `role_permissions.data_scope` 填充。实现 `apply_data_scope(query, ctx, permission, owner_column, dept_column)` 按变体追加 filter。各 health handler 列表查询调用此函数。
|
||
|
||
**验收**: data_scope = Department 时查询自动追加部门过滤;未配置时默认 All 向后兼容。
|
||
|
||
### Task 5: 微信 session_key 从 HashMap 迁移到 Redis
|
||
|
||
**涉及文件**: `crates/erp-auth/src/service/wechat_service.rs`, `erp-server/src/app_state.rs`
|
||
|
||
**步骤**: 替换 `LazyLock<Mutex<HashMap>>` 为 Redis `SET wechat:session:{openid} {key} EX 300` / `GET + DEL`。Redis 连接池通过 AuthState 传入。添加 fallback:Redis 不可用时降级内存 HashMap 并 warn 日志。
|
||
|
||
**验收**: 小程序登录端到端通过;`cargo test -p erp-auth` 通过。
|
||
|
||
### Task 6: 小程序 openid 加密存储
|
||
|
||
**涉及文件**: `apps/miniprogram/src/utils/secure-storage.ts`(新增), `stores/auth.ts`
|
||
|
||
**步骤**: 创建 AES 加密存储工具(setSecure/getSecure)。后端登录接口响应新增 `storage_key` 字段。auth.ts 中 openid 存储改用 `setSecure('openid', openid)`。
|
||
|
||
**验收**: `Taro.getStorageSync('openid')` 返回密文;小程序登录端到端通过。
|
||
|
||
---
|
||
|
||
## Phase 3: 健康检查增强 + 审计日志(Week 2-3)
|
||
|
||
### Task 7: /health/ready 增加 DB ping + Redis ping
|
||
|
||
**涉及文件**: `crates/erp-server/src/handlers/health.rs`
|
||
|
||
**步骤**: 添加 `sqlx::query("SELECT 1")` DB 检查 + `redis PING` 检查,使用 `tokio::join!` 并行。响应扩展 status(ok/degraded) + database + redis 字段。
|
||
|
||
**验收**: DB 不可用时返回 `status: "degraded"` + `database: "unreachable"`。
|
||
|
||
### Task 8: audit_logs 表增加 prev_hash 字段实现哈希链
|
||
|
||
**涉及文件**: `migration/src/m000074_audit_logs_hash_chain.rs`(新增), `erp-core/src/audit.rs`
|
||
|
||
**步骤**: 迁移添加 `prev_hash TEXT` + `record_hash TEXT` 列。写入时查询最新 record_hash 作为 prev_hash,计算 `SHA256(id + action + resource_type + resource_id + created_at + prev_hash)` 作为 record_hash。添加完整性验证函数检测篡改。内存缓存最近 1000 条 record_hash 优化性能。
|
||
|
||
**验收**: 新审计日志含哈希链字段;修改记录后验证函数检测到链断裂;`cargo test` 通过。
|
||
|
||
---
|
||
|
||
## 执行原则
|
||
|
||
1. **每 Task 完成后立即提交** — 不积压
|
||
2. **Phase 1 最高优先** — RLS 是医疗数据合规红线
|
||
3. **RLS 迁移必须可回退** — down 方法完整恢复
|
||
4. **渐进式启用 data_scope** — 未配置默认 All,不破坏现有行为
|