审计模块的验收标准不是“数据库里有一行”,而是证据在正确时机形成、租户不串、内容不泄密、失败不静默、查询与保留保持类别语义,并且值班人员能界定受影响的时间、实例和记录范围。
1. 当前自动化证据
源码已把审计测试分散在四层:
| 层次 | 代表测试 | 主要守护内容 |
|---|---|---|
| Unit | AuditBatchIntegrityTests、ChannelAuditQueueTests、AuditBatchWriterTests | 稳定 ID、冲突证据、背压、定时批次、重试与停机 |
| Unit | AuditEntryNormalizerTests、AuditEnvelopeMapperTests、ExternalRequestLoggingHandlerTests | 类别映射、脱敏、URL 边界和公开投影 |
| Unit | EfCoreAuditSaveChangesInterceptorTests、SqlSugarEntityChangeAuditorTests、EntityChangeAuditOutboxTests | 提交感知、回滚、敏感字段与事务 Outbox |
| Application | AuditRetentionPolicyServiceTests、AuditAlertServiceTests、SiemForwardServiceTests | 策略版本、规则评估、SIEM 水位/DLQ/退避 |
| Integration | DapperAuditQueryIntegrationTests、AuditRetentionEndpointTests、CapOutboxIntegrationTests | SQL 分表查询、HTTP 权限/错误和真实 Outbox |
| Architecture | AuditingPersistenceArchitectureTests、MongoAuditArchitectureTests、AuditSinkContractCohesionTests | Adapter 依赖方向、类别覆盖与合同聚合 |
结构测试能证明类型、Token 或注册存在,不能替代运行语义。尤其是跨 Provider 排序、真实数据库回滚、进程崩溃和大数据清理,必须由集成或进程级 Fixture 证明。
2. 建议执行命令
在 BitzOrcasVNext 根目录使用仓库锁定的 .NET SDK:
# 通用审计、持久化与外呼单元测试。dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj --filter "FullyQualifiedName~Auditing|FullyQualifiedName~Audit|FullyQualifiedName~ExternalRequestLoggingHandler"
# 告警、导出、保留策略和 SIEM Application 测试。dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj --filter "FullyQualifiedName~Auditing"
# SQL 查询、HTTP 保留入口和 CAP Outbox 集成证据。dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj --filter "FullyQualifiedName~Audit|FullyQualifiedName~CapOutbox"
# 依赖方向与 Adapter 合同。dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj --filter "FullyQualifiedName~Audit"按测试框架的 OR 过滤规则执行前,应先用 dotnet test --list-tests 确认实际命中范围;不要因为过滤表达式返回 0 个测试就把命令当成绿色门禁。
3. 生产者覆盖矩阵
每个来源至少覆盖成功、业务失败、系统异常、取消和上下文:
| 来源 | 必测分支 | 关键断言 |
|---|---|---|
| HTTP | 2xx、4xx、401/403/429、500、断开 | 有效租户/Actor、原响应和异常不被审计覆盖 |
| Mediator | Success、Result Failure、throw、[AuditIgnore] | 错误码稳定、生成策略生效、异常由其他证据补位 |
| EF Core | 无事务、显式事务、CAP 事务、回滚、提交未知 | 只发布已提交变更,敏感字符串遮蔽,版本恢复 |
| SqlSugar | INSERT/UPDATE/DELETE、无法解析 SQL、回滚 | 只保留表与动作,不泄露 SQL/参数,基础设施表排除 |
| HttpClient | 2xx/4xx/5xx、DNS、超时、取消 | Body 为空、URL 无 Query/user-info、状态 0 表示传输失败 |
| CAP | 成功、异常、重复执行键、观测 Sink 故障 | 独立 Category、异常类型、耗时、业务结果不被改变 |
| Job | Success、Result Failure、throw、scheduler cancel | Job 名、系统主体、平台租户与原异常 |
| Exception | Warning、Error、Fatal、告警失败 | 脱敏、限长、告警不替代原证据 |
4. 告警规则管理面
当前告警 API 包括:
GET/PUT /api/audit/alerts:分页与保存;DELETE /api/audit/alerts/{ruleId}:按乐观版本软删除;POST /api/audit/alerts/{ruleId}/toggle:启停;POST /api/audit/alerts/{ruleId}/dry-run:只统计,不通知;GET /api/audit/alerts/history:触发与通知结果历史。
规则只允许受控 Category、Success/Failure 结果、1–10,000 阈值、1–1,440 分钟窗口、0–1,440 分钟冷却和分号分隔的收件人标识。通知目标由 Notification 模块解释,规则不保存 URL 或凭据。
每分钟运行的 AuditAlertEvaluationJobExecutor 枚举租户,统计窗口 TotalCount,达到阈值且不在冷却期时发送 audit.alert.triggered,并保存通知成功/失败历史。
告警规则保存目前也使用 auditing.audit.view。应与保留策略一样,把该权限暂时限制到审计管理员,并计划拆分管理动作。
5. SIEM 管理与投递
管理 API
| 路由 | 动作 |
|---|---|
GET/PUT /api/operations/siem/config | 读取或保存租户配置 |
GET /api/operations/siem/metrics | Sent/Failed/Retried/Dropped、水位与最近错误 |
GET /api/operations/siem/deliveries | 按 Pending/Retrying/Dropped 查看 DLQ |
POST .../deliveries/{recordId}/replay | 重新入队一条记录 |
POST .../deliveries/{recordId}/drop | 标为终态丢弃 |
POST /api/operations/siem/test | 发送不推进水位、不写 DLQ 的测试事件 |
读取使用 operations.siem.view,配置、重放、丢弃和测试使用 Update。管理变更会尝试写 Activity,但该 Activity 是 best-effort,不会阻断配置保存。
受控 Profile
| ProfileId | 传输 | 默认端口 | 格式 |
|---|---|---|---|
syslog-rfc5424-udp | UDP | 514 | CEF |
syslog-rfc5424-tcp | TCP | 514 | CEF |
syslog-rfc5424-tls | TLS | 6514 | CEF |
TLS 的可选 CA 证书通过 ISecretStore 的 CredentialName 解析;缺失时本轮失败关闭,不降级到明文。配置限制 BatchSize 1–1,000、MaxRetryAttempts 0–100 和有效端口,并用版本防并发覆盖。
运行语义
SIEM 作业每 30 秒枚举租户。每轮先重试最多 100 条到期 DLQ,再按配置 Category 查询新事件、格式化为 CEF 并投递。失败事件以 30 秒首次重试时间进入 DLQ,后续按最多 256 秒的指数退避重试;超过上限进入 Dropped。测试事件不推进水位,也不污染 DLQ。
Category 配置当前只验证非空文本,不在保存时验证是否属于 AuditCategory。全部为未知值时,转发服务会得到空类别集合并返回 Success,实际不发送。运维 UI 应只允许目录值,并把未知值视为配置错误。
SiemForwardJobExecutor 与告警作业一样会累计失败租户但最终返回 Success。值班不能只看 Quartz 最终状态,还要检查租户级指标、LastError、水位停滞和 DLQ。
6. 当前可观测信号与缺口
已经存在的信号:
ChannelAuditQueue.RejectedCount,首条及每 1,000 条采样告警;- Writer 的批量耗时 Debug、连续重试 Error 和第 1/10 次失败告警;
- SIEM 持久化 Sent/Failed/Retried/Dropped、最后水位、LastError 与 UpdatedAt;
- 告警规则触发历史和 Notification 状态;
- Retention 的 Tenant/Scope/Days/Deleted 结构化日志及 Activity/Exception 证据。
当前通用审计管道没有专用 System.Diagnostics.Metrics Meter,也没有公开 Queue Depth、Oldest Age、Enqueue Rate 或 Persist p95。建议补充低基数指标,但在落地前不要在监控手册里把它们写成现成名称。TenantId、UserId、CorrelationId、Path 和自由文本 Module 不应成为 Metric Label。
7. 值班阈值
| 信号 | 级别 | 首要动作 |
|---|---|---|
| RejectedCount 增加 | Critical | 查 Store、连接池、Writer 重试;界定同步入口失败范围 |
| 同一批连续重试 | Critical | 暂停非必要流量,保护数据库连接,确认稳定 ID 重试 |
| 停机宽限期耗尽 | Critical | 标记可能丢失的实例与时间窗,保留节点/容器事件 |
| Retention 任一租户/Scope 失败 | Critical | 禁止盲目手工补删,先核对 Legal Hold 与剩余范围 |
| SIEM 水位两轮不前进 | Critical | 查查询方向、DLQ、凭据和端点;不要直接 Drop |
| DLQ Dropped 增长 | Security incident | 保存原 CEF、错误码和工单,决定重放或接受缺口 |
| 告警 dry-run 与实际不符 | Critical | 暂停依赖该规则的自动响应,检查 ResultCondition |
| 查询 p95/扫描表数激增 | Warning | 检查时间过滤、分表数量、10,000 候选边界与索引 |
8. 排障路径
处理证据缺口时,先保存实例、部署版本、起止时间、Category、Tenant 和最后已知 AuditId。不要立即做大范围清理、DLQ Drop 或重复业务命令;这些动作可能覆盖原故障边界。
9. 发布前故障演练
- 容量 1 填满 Channel,验证异步等待、同步拒绝和采样告警;
- 让 Store 连续失败后恢复,确认同一批次重试、无冲突重复且上游背压解除;
- 制造 SqlSugar 提交结果未知与 Mongo 跨集合部分完成,验证稳定 ID 修复;
- 分别执行 SIGTERM、停机超时与 SIGKILL,记录可证明和不可证明的窗口;
- 用 JSON Secret、Bearer、URI 密码、卡号与超长正文验证入队前脱敏;
- 对 SqlSugar/Mongo/Dapper 比较七类、同时间戳、第二页与游标;
- 两租户逐 Scope 清理,确认永久类别与另一个租户不变;
- 告警规则分别用 Success/Failure Fixture 验证 ResultCondition;
- SIEM 连续运行两轮,中间插入新事件,再演练 TLS 凭据缺失、DLQ 重试、重放和 Drop;
- Notification、Secret Store、租户目录与策略 Store 故障时,确认 Job 状态真实反映失败。
10. GA 门禁
值班交接时可以先保存 SIEM 的聚合指标,再单独导出待处理投递;两份结果的时间点必须写入事件记录:
# 先采集水位、失败和丢弃计数。curl -fsS -H "Authorization: Bearer <TOKEN>" \ https://<HOST>/api/operations/siem/metrics > siem-metrics.json# 再保存需要 replay/drop 决策的投递明细。curl -fsS -H "Authorization: Bearer <TOKEN>" \ https://<HOST>/api/operations/siem/deliveries > siem-deliveries.json- 通用队列无静默丢弃,重试、幂等、停机和强制终止边界写入 SLO;
- 高价值实体变更走 CAP 事务 Outbox,非 CAP 降级路径有显式部署限制;
- 七类在 SqlSugar/Mongo/Dapper 查询中保真,SqlSugar TraceId 缺口关闭;
- JSON/表单/URI/Token/卡号与长度边界有回归测试,原始 Body 不进入外呼审计;
- Retention 按 Tenant+Scope 执行,Host 默认也受法定下限门禁,Preview 明示 Request 边界;
- 告警 ResultCondition 真正参与过滤,逐租户失败传播到 Job 结果;
- SIEM 第二轮增量水位测试转绿,未知 Category 被拒绝,失败租户使 Job 失败;
AutoCreateTables与AuditShardingSchedule要么真正接线,要么从公共配置移除;- Queue Depth/Oldest Age 等必要 SLO 信号有正式 Meter,而不是靠日志估算;
- Production 清理、SIEM Drop 和跨租户调查具备独立权限、审批、Purpose 与不可删除证明。