领域模型
Tiyi 的领域被刻意保持得很小。七个一等资源覆盖了所有运维动作、所有 CLI 命令、所有 UI 页面。理解了这些,产品的其他部分就显而易见了。
七个资源
- 站点(Site)
- Tiyi 代理的一个 HTTP 主机。携带 TLS 设置、上游池引用与挂载的 WAF 策略。是你启用或停用的最小单元。
- 上游池(Upstream pool)
- 站点背后的一组后端。轮询或加权负载均衡、SNI 感知健康检查,可在多个站点之间共用。
- 证书(Certificate)
- 一对 TLS 密钥。要么上传(自签、CA 购买、企业签发),要么ACME 签发(HTTP-01 或 DNS-01)。在静态用 KEK 信封加密。
- WAF 策略(Policy)
- Coraza 在每个请求上执行的编译产物。CRS Core 层 + HTTP 策略 + 限制 + 规则调优 + IP 列表 + 自定义规则 + 插件 + 限速 + 排除项。
- 节点(Agent)
- 运行数据平面(Caddy + Coraza)的一台机器。通过长连 gRPC 流从主节点接收已签名的配置 bundle。
- 遥测汇总(Telemetry rollup)
- 开桶管道产生的计数器与 Top-K 采样。驱动 dashboard、Top-K 浏览器与 Prometheus 导出器。
- 审计行(Audit row)
- 每一次变更对应一行哈希链记录,归属到 JWT subject。这是合规面 —— 每一次变更都有一份防篡改回执。
站点与上游池
站点把主机名绑到 TLS 设置、上游池与 WAF 策略上。写入器在 apply 时把每一个启用的站点翻译成一个 Caddy server 块 —— Caddy JSON 是编译目标,不是存储格式。
上游池默认与站点解耦。一个池可以背后挂多个站点;删除被引用的池会被拒绝并附带 UPSTREAM_IN_USE 与引用列表。站点也可以使用内联 upstream URL 作为不需要复用的特例。
访问日志录入模式
每个站点有一个 recording_mode 字段控制访问日志体量:
on(自 2026-05-26 起的默认值)—— 录入每一个请求。sampled—— 录入可配置的百分比。如果没有设比例,写入器会折叠到off。off—— 不写入访问事件。
无论访问日志模式如何,安全事件(Coraza 规则命中)都会被写入。
证书
TLS 材料是一等资源 —— 永远不内联到站点上。Tiyi 支持四种签发路径,全部用从 crypto.kek_file 加载的 32 字节 KEK 在静态时信封加密:
- 上传 —— 粘贴或上传 PEM 密钥对。开发用自签,生产用 CA 购买或企业签发。
- ACME HTTP-01 —— 多节点:每个节点都跑一个 loopback 响应器,并通过节点流广播 (token, key_authorization) 对,让任何节点都能应答挑战。
- ACME DNS-01 —— 可插拔驱动注册表。Cloudflare 驱动已生产可用;Route53 与阿里云在 v3.0.0-rc.1 中提供凭据校验桩。
- 通配符 —— 在混合 order 中对
*.example.com自动选择 DNS-01。
Dashboard 把 certs_expiring_14d / certs_expiring_30d / acme_renewals_failed_24h / acme_orders_pending 暴露为 KPI。首次启动种入两个默认告警:证书即将到期 与 ACME 续期失败。
WAF 策略
策略是一个结构化的领域对象,apply 时编译成 Coraza SecLang。编译器是确定的 —— 同样的策略输入产生同样的 SecLang 输出。12 个可调层(按应用顺序):
- CRS Core —— 偏执等级、阻断与执行 PL、入站/出站阈值、采样。
- HTTP 策略 —— 方法、版本、Content-Type、字符集、受限扩展名/Header、方法覆盖。
- 限制 —— 6 个数值字段:最大请求体、最大响应体等,CRS 默认值作为 placeholder。
- 规则调优 —— 按规则的覆盖:
DEFAULT、DISABLE、LOG_ONLY、SCORE_OVERRIDE,可选限定到单个站点。 - IP 列表 —— 允许 / 拒绝 / 监控列表。条目可以是 CIDR 或
geo:CC伪条目(alpha-2/alpha-3 国家代码加PRIVATE/LOOPBACK)。 - 自定义规则 —— 运维写的 SecLang 或可视化规则,ID 在 8000000–8999999 区间。
- 插件 —— CRS 排除包(WordPress、Drupal、Nextcloud、phpBB、phpMyAdmin、XenForo、cPanel、DokuWiki)。支持离线归档导入。
- 限速 —— 按端点与按客户端范围的桶,动作可选阻断或仅记录。
- 排除项 —— 阶段-1 的
tx.crs_exclusions_<slug>选项,可选用@beginsWith限定。 - 预览 —— 当前版本的只读 SecLang dump。
- 版本 —— 每次保存一个快照,任意两个版本之间并排 diff。
- 测试台 —— 用 fixture 请求打编译后的 bundle,不会触碰生产数据平面。
按站点覆盖会把标量字段(偏执、阈值、采样)叠加到挂载的策略上而不必 fork 它。
节点
每一个代理流量的节点都跑一个嵌入式节点 —— 包括主节点与热备。节点持有一条长连 ConnectRPC 双向流到主节点。重连循环先试主节点、再试备节点、然后用指数退避重试。备节点接受只读的节点流。
更新以 ConfigUpdate{revision, bundle, signature} 消息流转。节点用首次连接固定的 ed25519 公钥校验 bundle 签名(首次连接 TOFU,之后拒绝重新固定),检查版本号严格大于上次应用的,再 apply 到本地 Caddy 并上报结果。重放与乱序都安全 —— 更新按版本号幂等。
为什么节点保持一致?主节点的嵌入式节点没有特殊地位。它通过与任何远程节点完全相同的入网与流协议连到自己的 API 地址。这意味着数据平面在所有地方都被同一段代码路径测试 —— 不存在 "主节点是不一样的" 这种特例。
遥测
遥测作为一条并行管道运行,结束了每事件 SQL 写入。路径:
- 分片入流环 —— 日志转发器事件按站点 key 散到分片环。
- 10 秒开桶聚合 —— 分片 worker 把计数器累加到 10 秒桶。
- Misra-Gries Top-K —— 高基数维度(客户端 IP、路径、UA、ASN、攻击标签)的有界摘要,带
__other__桶让 SUM(*) 等于真实流量。 - 每日 SQLite 分区位于
logs/rollup_10s/YYYY-MM-DD.db;降采样器把它们幂等地折叠成rollup_hour→rollup_day→rollup_month。 - 采样环 —— 按 IP 的 LRU + 全局环,带启动时回放的循环文件 WAL。
- API 树 —— 按站点的 URL 前缀树,节点数与深度均有上限;空闲叶子撤入
api_tree_archive。 - 读路径 —— REST API 位于
/api/v1/telemetry/*,本地管理 socket 上的 Prometheus 导出器位于/metrics。
审计链
每一次变更(站点、上游、证书、策略、IP 列表绑定、限速端点、信任配置、自定义规则、告警规则等)都会向 audit_event 追加一行。每一行存:
- 动作 —— 固定的字符串,例如
policy.update_layer、trust.update_profile、rule_override.create。 - 资源类型与 ID —— 变更的范围。
- 操作者 —— JWT subject,归属到真实用户 UUID(而不是泛化的 "local-admin")。
- 变更前/后 JSON —— 该行规范视图的扁平 diff。
- 哈希链 —— 每一行哈希前一行加自身的规范 payload。每日锚点提交链头。
Vben Admin 上的校验链按钮会走完整条链并确认每一段。SIEM 转发可在 siem.filter.include_audit 下把每一行审计转发到 Splunk / Elastic / Sentinel 管道。
这就是整个模型。每一个 CLI 命令、每一个 UI 页面、每一个 RPC 都作用于这七个资源中的一个。文档其余部分按名引用,不再重复定义。