- 零信任
- 网络
- 后端
- 认证鉴权
【免费下载链接】ziti
The parent project for OpenZiti. Here you will find the executables for a fully zero-trust, programmable network @OpenZiti
导读
本文基于 Ziti 开源仓库(OpenZiti 的父项目,承载控制器与路由器全部可执行程序)的 0.6 版本发布说明,深入解析该版本三项核心变更:后端持久化模型从 PostgreSQL 迁往 Bbolt、UPDB(用户名/密码数据库)注册流程从裸 URL 切换为 JWT 体系、以及 API 层针对多个非法字段值的错误响应格式重构。读完本文,你将理解 Ziti 0.6 引入的新存储架构与注册安全模型,掌握新旧 UPDB 注册实体的差异与 JWT 验证/注册地址拼接方法,并能准确区分新旧两套错误响应格式,为跨版本升级和 API 客户端适配提供直接依据。
版本主题:Edge 与 Fabric 共享 Bbolt 持久化底座
Ziti 0.6.0 的核心主题是把 Ziti Edge 与 Ziti Fabric 的后端持久化模型统一进同一仓库,并以 Bbolt 作为底层存储引擎——Bbolt 是一个内存型数据存储,由内存映射文件(memory-mapped file)提供持久化支撑。这一改动直接移除了对 PostgreSQL 的依赖,显著降低了控制面的部署与运维复杂度。
从当前仓库源码可以确认这一架构演进的落地形态:
- controller/db/db.go 中的
Open()函数基于go.etcd.io/bbolt打开数据库文件,并以"ziti"作为根 bucket 名(RootBucket),同时通过controller/storage/boltz封装层提供事务、索引、约束与查询能力; - 根 bucket 下还维护了
metadata子 bucket,用于存放raftIndex、clusterId等集群元数据(见 db.go),与 HA 模式下 Raft 日志的持久化索引衔接; - controller/storage/boltz 目录提供了完整的存储抽象:
store.go、store_crud.go、store_query.go、indexes.go、typed_bucket.go、migration.go等文件共同构成实体存取、索引、迁移与查询框架; - 控制器侧的各类实体存储(身份、认证器、注册、服务、策略、路由器等)全部落在 controller/db 目录,均以 Bbolt bucket 为落点。
因此,0.6 起 Ziti 控制面不再依赖外部数据库服务,单进程即可承载持久化逻辑,这与后续版本的 raft 集成共同构成高可用与一致性的数据基础。
UPDB 注册 JWT 化:从裸 URL 到标准化令牌
变更背景
在 0.5.x 及更早版本中,UPDB(username password database)注册与其他注册方式(如 OTT、CA 自动注册等)的处理方式不一致:系统直接为待注册身份生成一个裸 URL,客户端需要靠该 URL 完成后续注册。0.6 起,UPDB 注册统一改为 JWT 流程——与其他注册方式保持一致,JWT 由控制器签发并内嵌于身份实体的enrollment.updb对象中。
从源码看,UPDB 注册模块位于 controller/model/enrollment_mod_updb.go,其Process()方法按db.MethodEnrollUpdb(常量值为"updb",见 controller/db/enrollment_store.go)处理请求:先按 token 读取注册记录并校验是否过期,再读取关联身份,校验请求体携带的密码后生成AuthenticatorUpdb(用户名、加盐哈希密码与盐),最终通过Enrollment.ReplaceWithAuthenticator将注册记录替换为正式认证器。整个链路中 JWT token 充当"一次性凭据",杜绝了裸 URL 可被直接枚举/重放的弱点。
新旧实体形态对比
0.5.x 的 UPDB 注册实体(节选enrollment.updb字段)如下:
{ "id": "612843ae-6ac8-48ac-a737-bfc2d28ab9ea", "name": "updb--5badbdc5-e8dd-4877-82df-c06aea7f1197", "type": { "id": "577104f2-1e3a-4947-a927-7383baefbc9a", "name": "User" }, "isDefaultAdmin": false, "isAdmin": false, "authenticators": {}, "enrollment": { "updb": { "username": "asdf", "url": "https://demo.ziti.netfoundry.io:1080/enroll?method=updb&token=911e6562-0c83-11ea-a81a-000d3a1b4b17&username=asdf" } }, "permissions": [] }可见旧版仅提供username与url,注册地址直接暴露在身份对象中。
0.6.x 的 UPDB 注册实体(注意enrollment.updb对象的变化)如下:
{ "id": "39f11c10-0693-41ed-9bec-8011e2721562", "name": "updb--b55f5372-3993-40f5-b534-126e0dd2f1be", "type": { "entity": "identity-types", "id": "577104f2-1e3a-4947-a927-7383baefbc9a", "name": "User", "_links": { "self": { "href": "./identity-types/577104f2-1e3a-4947-a927-7383baefbc9a" } } }, "isDefaultAdmin": false, "isAdmin": false, "authenticators": {}, "enrollment": { "updb": { "expiresAt": "2019-11-21T17:33:18.2855234Z", "issuedAt": "2019-11-21T17:28:18.2855234Z", "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbSI6InVwZGIiLCJleHAiOjE1NzQzNTc1OTgsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjEyODAiLCJqdGkiOiJiYzBlY2NlOC05ZGY0LTQzZDYtYTVhMC0wMjI1MzY2YmM4M2EiLCJzdWIiOiIzOWYxMWMxMC0wNjkzLTQxZWQtOWJlYy04MDExZTI3MjE1NjIifQ.PUcnACCdwqfWRGRzF8lG6xDTgHKAwKV6eTw8tHFuNBXaUNbqExBwUQEW0-cCHsV-nLEyhxyjhXmVCkIDgz-ukKfS0xStiDrJQbiq8m0auodkArmJSsYzElXkKdv37FHu0t-CGoXptdLyuo9eCnzzmci3ev18zMR5HjYMCQEclELV6OEICNr_0EwhAGJa1yX6ODYrLMZ3SdEd6fj-ZGX7j9owTs6iEsqCB_TORfnGGg6lEINE5GlYsyp7JUxolS6H4lPeN5h2mxk2_OkJY8GX3ydv75LsIZ-jjL3xC5XncCESrefgDabib1fudJ4038D0EzqTcOREPAqmjWhnDhTulQ", "token": "bc0ecce8-9df4-43d6-a5a0-0225366bc83a" } }, "permissions": [] }新版enrollment.updb对象包含四个字段:
| 字段 | 含义 |
|---|---|
jwt | 控制器签发的 JWT 令牌,携带注册所需全部声明(payload 解出后含em、exp、iss、jti、sub等标准声明) |
token | 注册令牌 UUID,对应 JWT 的jti声明,用于标识并检索注册记录 |
issuedAt | JWT 签发时间(RFC 3339 时间戳) |
expiresAt | JWT 过期时间(RFC 3339 时间戳),过期后注册将失败 |
破坏性影响与迁移指引
破坏点:任何依赖裸 URL 完成 UPDB 注册的客户端逻辑都将失效——enrollment.updb.url字段已被移除,不能再假设它存在。
新流程:拿到 JWT 后,注册方应按以下步骤处理:
- 验签:验证 JWT 签名,使其与 JWT
iss声明所指向 URL 的 TLS 服务器所出示证书相匹配(即校验签发方身份,防止伪造令牌); - 构造注册地址:依据 JWT 属性按下述格式拼接:
<iss> + "/enroll?token=" + <jti>即iss声明值(如示例中的https://localhost:1280)拼接/enroll?token=,再拼上jti声明值(如bc0ecce8-9df4-43d6-a5a0-0225366bc83a)。最终形如:
https://localhost:1280/enroll?token=bc0ecce8-9df4-43d6-a5a0-0225366bc83a- 携带密码提交:UPDB 注册最终需要用户名对应的密码。从 controller/model/enrollment_mod_updb.go 可见,控制器要求请求体携带密码(
data.Password为空会直接报错),密码经Hash()加盐哈希后以 base64 编码的哈希与盐存入认证器。
顺带一提,当前仓库的 token 型注册模块(controller/model/enrollment_mod_token.go)完整实现了 JWT 校验流程:从authorization请求头提取Bearer令牌,支持通过ziti-token-issuer-id指定签发者或按iss声明自动识别签发者,再经由 TokenIssuer 缓存执行签名验证与声明提取。虽然该模块服务的是外部 JWT 签发者场景,但验证了 0.6 确立的"注册令牌一律 JWT 化"的工程方向在后续版本中持续演进。
多值字段错误处理:合并为单一错误、values 聚合
变更内容
在 0.6 之前,当某个字段存在多个非法值需要逐一报告时(例如批量按 id 查询多个实体、其中多个 id 无效),API 会为每个非法值各产生一条独立错误。0.6 起改为:同一字段的多个非法值合并为一条错误,所有非法值集中放入该错误的values字段。
这一变更直接服务于 API 的批量操作场景。以旧格式示例中的INVALID_FIELD错误为例——旧版对ids[0]分别报错:
{ "error": { "args": { "urlVars": { "id": "097018b6-108e-42b3-869b-deb9e1814594" } }, "cause": { "errors": [ { "message": "entity not found for id [06ecf930-3a9f-4a6c-98b5-8f0be1bde9e2]", "field": "ids[0]", "value": "06ecf930-3a9f-4a6c-98b5-8f0be1bde9e2" } ] }, "causeMessage": "There were multiple field errors: the value '06ecf930-3a9f-4a6c-9...' for 'ids[0]' is invalid: entity not found for id [06ecf930-3a9f-4a6c-98b5-8f0be1bde9e2]", "code": "INVALID_FIELD", "message": "The field contains an invalid value", "requestId": "48ea4bce-f233-410e-a062-5dbceee20223" }, "meta": { "apiEnrolmentVersion": "0.0.1", "apiVersion": "0.0.1" } }新版则将同一字段的所有非法值收拢到单条错误,cause.value变成数组(如clusters字段下多个不存在的集群 id):
{ "error": { "args": { "urlVars": { "id": "5b15c442-5590-4c58-8bc7-0da788e0cfcf" } }, "cause": { "message": "clusters(s) not found", "field": "clusters", "value": [ "68f8739f-cf52-4d51-9553-dfe7cf9c6a03", "..." ] }, "causeMessage": "The field contains multiple invalid values", "code": "INVALID_FIELD", "message": "The field contains an invalid value", "requestId": "..." }, "meta": { "apiEnrolmentVersion": "0.0.1", "apiVersion": "0.0.1" } }新旧格式差异可归纳为三点:
| 对比维度 | 旧格式(0.5.x) | 新格式(0.6+) |
|---|---|---|
| 错误条目数 | 每个非法值一条独立错误(cause.errors[]数组逐项列出) | 每个字段一条错误 |
| 非法值载体 | 每条错误的cause.value为单个值 | 该字段所有非法值聚合为cause.value数组 |
| 错误码 | 均为INVALID_FIELD | 均为INVALID_FIELD(错误码不变) |
源码层面的印证
错误码INVALID_FIELD的生成与消费在仓库中均有迹可循:
- 服务端构造:
controller/apierror的NewBadRequestFieldError()(见 helpers.go)以errorz.InvalidFieldCode为AppCode、InvalidFieldMessage为Message组装INVALID_FIELD响应,并将FieldError作为Cause附加——FieldError 本身携带field、value、reason,正是cause对象与values数组的来源; - 客户端解析:Ziti CLI 的 REST 工具层(ziti/util/rest.go)在输出错误时会按
INVALID_FIELD - <field> [<value>] <reason>的格式格式化字段错误,说明该错误码的语义约定被客户端明确依赖,改动时需客户端同步适配。
对 API 消费者(SDK、CLI、自动化脚本)而言,升级到 0.6 后应调整批量校验类请求的错误解析逻辑:不要再假设一条错误只对应一个非法值,应读取cause.value(数组)并遍历处理。
升级注意要点小结
- 存储层:0.6 起控制面数据落地于 Bbolt 文件(由 controller/db/db.go 的
Open()以根 bucketziti打开),不再需要 PostgreSQL;若此前基于 PostgreSQL 部署,需规划数据迁移与全新初始化。 - UPDB 注册:
enrollment.updb.url已移除,改为jwt+token(对应jti)+issuedAt/expiresAt;注册方需验签(签名与issURL 的 TLS 证书匹配),并按<iss> + "/enroll?token=" + <jti>构造注册地址,随后携带密码提交注册请求。 - 错误响应:
INVALID_FIELD错误由"一值一错"改为"一字段一错",非法值聚合于cause.value数组;批量查询、批量关联等场景的错误处理代码需要同步调整。
以上变更均以 changelogs/CHANGELOG.0.6.md 发布说明为准,并结合当前仓库源码(controller/db、controller/model、controller/apierror 等)交叉验证。后续版本的变化可参考仓库 changelogs 目录下的对应版本说明。
- 零信任
- 网络
- 后端
- 认证鉴权
【免费下载链接】ziti
The parent project for OpenZiti. Here you will find the executables for a fully zero-trust, programmable network @OpenZiti
相关推荐
Flet v0.23.0 版本解析:新控件、错误处理强化与破坏性变更迁移指南
Flet v0.23.0 版本解析:新控件、错误处理强化与破坏性变更迁移指南 Flet 是一个只用 Python 就能构建实时 Web、移动端和桌面应用的框架,
前端跨平台桌面应用移动开发imbalanced-learn 0.6 版本变更深度解析:数据格式处理、采样器行为与 API 迁移指南
imbalanced learn 0.6 版本变更深度解析:数据格式处理、采样器行为与 API 迁移指南 导读 :本文基于 imbalanced learn 仓
机器学习特征工程数据增强Jedis 4.0版本重大变更解析与技术迁移指南
Jedis 4.0版本重大变更解析与技术迁移指南 前言 Jedis作为Java生态中最流行的Redis客户端之一,在4.0版本中进行了重大架构调整。本文将全面解
数据库缓存后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考