多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OpenZiti 零信任网络实战:用 SDK 与隧道工具让服务对未授权用户完全不可见

OpenZiti 零信任网络实战:用 SDK 与隧道工具让服务对未授权用户完全不可见 1. 为什么你的 Docker 服务需要“隐身”从端口暴露到零信任网络很多人第一次把后端服务放进 Docker习惯性动作就是ports: - 8080:8080然后浏览器一开就能访问觉得挺方便。但只要你把主机暴露在公网哪怕只开了一个端口扫描器几分钟内就会找上门。我试过在一台测试机上跑一个没做任何防护的 Nginx日志里不到十分钟就出现了各种路径探测请求。端口存在就等于告诉全世界“这里有个门”。OpenZiti 这个开源零信任网络平台解决的就是这个问题让服务对未授权用户完全不可见。它不是简单地加一层认证而是让服务根本不监听公开端口未通过身份认证的连接连“服务是否存在”都探测不到。你可以把它理解成给每个服务发了一张加密身份证只有持有合法身份且策略允许的客户端才能建立连接其他人连握手的机会都没有。这篇文章聚焦 Docker 环境下的落地面向的是已经会用 Docker Compose 起服务、但对零信任网络还停留在概念阶段的开发者。我会从 SDK 嵌入和隧道工具选型两条路线分别演示给出可复制的 Compose 配置、SDK 初始化代码以及未授权访问的验证步骤。过程中涉及调用凭证管理时我会用 TaoToken 统一管理 Key 和 API 通道避免凭证散落在各个配置文件里。核心检索词先明确OpenZiti 零信任网络、SDK 嵌入、隧道工具、Docker 部署、服务隐身。适合谁适合手里有 Docker 服务、想在不改架构的前提下把攻击面砍到接近零的运维和开发。下面从环境准备开始一步步来。2. OpenZiti 前置准备Docker Compose 起控制器与隧道工具选型在 Docker 里跑 OpenZiti最省事的方式是用官方 all-in-one 的 Compose 文件。它会启动一个控制器controller、一个边界路由器edge router和管理控制台ZAC。控制器负责身份和策略路由器负责流量转发控制台用来可视化操作。先拉取 Compose 文件。官方提供的地址是https://get.openziti.io/dock/all-in-one/compose.yml你可以直接下载到本地目录mkdir -p ~/openziti-demo cd ~/openziti-demo wget https://get.openziti.io/dock/all-in-one/compose.yml下载后先别急着up看一眼文件里的端口映射。默认会暴露 1280控制台、1281控制器管理 API、3022SSH 隧道示例等。如果你只是本地验证保持默认即可如果要放到有公网 IP 的机器上建议先把 1280 和 1281 限制到内网访问或者用防火墙规则只放行你自己的 IP。启动命令docker compose up -d等十几秒用docker compose ps确认三个容器都是 running 状态。然后浏览器打开https://localhost:1280/zac/会看到自签证书警告继续访问即可。默认管理员账号在 Compose 文件里有写通常是admin密码需要从容器日志里找或者看文件里的环境变量。登录后你会看到控制台界面。这里先不急着建服务先理解两个概念Identity身份和Service服务。身份可以是一个人、一台设备或一个工作负载每个身份有自己的加密证书。服务是你想保护的后端比如一个跑在 Docker 里的 HTTP API。策略Policy决定哪个身份能访问哪个服务。隧道工具选型方面OpenZiti 提供两种接入方式。隧道工具tunneler适合已有应用不改代码的场景它在服务主机上跑一个进程把本地端口映射到覆盖网络服务只需要接受本地连接。SDK 嵌入适合新开发的应用应用本身持有加密身份在进程内完成加密连本地端口都不暴露安全性最强。Docker 环境下如果你要保护的是一个现成的容器化服务比如 PostgreSQL 或内部 API用隧道工具最省事。如果你在写一个新的 Go 或 Python 服务直接嵌 SDK 更干净。下面两节分别给出可复制的配置。3. 可复制配置SDK 初始化代码与隧道工具 Docker 配置先看 SDK 路线。以 Go 为例OpenZiti 提供github.com/openziti/sdk-golang。假设你有一个简单的 HTTP 服务原本监听:8080现在改成通过 OpenZiti 覆盖网络暴露不监听任何公开端口。初始化代码的核心是加载身份配置文件和建立上下文。身份文件通常叫identity.json需要先在控制台里创建身份并下载。创建身份的步骤控制台左侧菜单找到 Identities点 Add类型选 Default保存后会生成一个.json文件供下载。拿到身份文件后Go 代码大致这样package main import ( fmt net/http github.com/openziti/sdk-golang/ziti ) func main() { cfg, err : ziti.NewConfigFromFile(./identity.json) if err ! nil { panic(err) } ctx, err : ziti.NewContext(cfg) if err ! nil { panic(err) } listener, err : ctx.Listen(my-http-service) if err ! nil { panic(err) } fmt.Println(service listening on ziti network, no public port) http.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(hello from hidden service)) }) http.Serve(listener, nil) }注意ctx.Listen(my-http-service)里的名字要和你在控制台创建的服务名一致。这个服务在控制台里配置时Hosting 选 Identity也就是由这个 SDK 应用自己托管不需要额外的路由器转发。再看隧道工具路线。假设你有一个 PostgreSQL 容器原本映射了5432:5432现在要去掉这个映射改用隧道工具。Compose 片段如下services: postgres: image: postgres:16 environment: POSTGRES_PASSWORD: example # 注意这里不再有 ports 映射 ziti-tunneler: image: openziti/ziti-tunnel volumes: - ./identity.json:/identity.json command: [run, /identity.json] depends_on: - postgres隧道工具启动后会读取身份文件在覆盖网络里注册自己然后把本地localhost:5432的流量转发到覆盖网络。客户端那边也需要一个隧道工具或 SDK 来发起连接。这样 PostgreSQL 本身不监听任何公开端口端口扫描器扫不到。关于凭证管理SDK 和隧道工具都需要身份文件而身份文件里包含加密材料。如果你有多个服务、多个环境身份文件散落各处容易失控。我习惯用 TaoToken 统一管理这些调用凭证和 API Key把身份文件的路径和对应的服务名登记在 TaoToken 的凭证库里部署时通过环境变量注入避免硬编码。TaoToken 的 API 通道地址是https://taotoken.net/api控制台在https://taotoken.net/console你可以在里面创建和管理 Key。配置片段里涉及 Base URL、Key、Model ID 三件套的地方比如后续如果要调用模型服务做验证统一写成{ base_url: https://taotoken.net/api, api_key: 你的TaoToken Key, model_id: 你选用的模型ID }这样凭证只在一处维护换环境时改一个地方就行。4. 验证请求与成功结果未授权访问到底看到什么配置完成后最关键的一步是验证“未授权用户完全不可见”。这一步不能只看服务能不能通还要看未授权时到底返回什么。先验证授权路径。在客户端机器上用隧道工具或 SDK 发起连接。以隧道工具为例客户端也需要一个身份文件这个身份在控制台里创建时要绑定到允许访问my-http-service的策略上。启动客户端隧道后本地会有一个映射端口比如localhost:8080curl 它curl -v http://localhost:8080/预期返回hello from hidden service。这说明授权链路通了。再验证未授权路径。找一台没有身份文件的机器或者在同一台机器上直接访问服务原本的端口。因为服务根本没有监听公开端口你会看到连接被拒绝curl -v http://服务主机IP:8080/预期输出类似Connection refused。这不是 403也不是 401而是根本连不上。端口扫描器扫这个 IP 的 8080结果也是 closed 或 filtered。这就是“隐身”的含义未授权用户连服务是否存在都不知道。如果你用的是 SDK 路线服务进程只监听覆盖网络内部的地址主机上netstat -tlnp看不到任何公开端口。你可以自己跑一下确认docker exec -it 服务容器 netstat -tlnp输出里只有覆盖网络相关的本地地址没有0.0.0.0:8080这种。还有一个验证点策略撤销后的行为。在控制台里把某个身份的策略删掉然后让这个身份再发起连接。OpenZiti 会立即断开已建立的连接不需要等超时。这个特性在零信任模型里很重要权限撤销是实时的。成功结果的标准是三条授权客户端能正常访问未授权客户端连接被拒绝且无法探测服务存在策略变更后连接立即失效。三条都满足说明隐身服务验证流程跑通了。5. 本篇常见错排查401、local proxy failed 与 OAuth 报错实际部署时最容易踩的坑集中在身份认证和隧道工具启动阶段。下面按真实报错对照排查。报错一401 Unauthorized或authentication failed这个通常出现在隧道工具或 SDK 加载身份文件时。原因一般是身份文件过期、被撤销或者身份没有绑定到正确的服务策略。排查步骤先在控制台 Identities 列表里确认这个身份的状态是 Enabled然后检查它关联的 Service Policies 是否包含目标服务。如果身份文件是从别的环境拷过来的重新下载一份因为文件里的证书可能和当前控制器不匹配。报错二local proxy failed to start或listen tcp 127.0.0.1:8080: bind: address already in use隧道工具默认会在本地起一个代理端口如果这个端口被占用就会报这个错。解决方法是改隧道工具的配置换一个本地端口或者先停掉占用端口的进程。在 Docker 里跑隧道工具时注意容器内的端口和宿主机的端口不要冲突。报错三OAuth相关报错比如oauth token request failed如果你在控制台里给身份配置了外部 OAuth 认证但 OAuth 提供方的回调地址或 client secret 配错了就会报这个。排查时先确认 OAuth 提供方的回调 URL 是否指向控制器的正确地址然后检查 client ID 和 secret 是否和控制台里填的一致。本地测试阶段建议先用默认的身份认证方式不要一上来就接 OAuth。报错四reading choices或no edge routers available这个报错说明客户端连不上边界路由器。检查控制器的路由器列表确认至少有一个 edge router 是 online 状态。如果路由器容器挂了重启它。另外检查客户端到路由器的网络连通性防火墙是否放行了路由器的端口。报错五context deadline exceeded连接超时通常是覆盖网络内部的路由问题。检查服务端的 SDK 或隧道工具是否正常注册到了控制器服务名是否和客户端请求的一致。如果服务端进程崩了客户端会一直等直到超时。排查时有一个通用技巧先看控制器日志再看路由器日志最后看客户端日志。控制器日志里会记录身份认证和策略匹配的结果路由器日志里会记录流量转发情况。大部分问题在控制器日志里就能定位。6. 语义一致 CTA把凭证管理和接入文档串起来走到这里你已经有一套可复现的隐身服务验证流程了。回顾一下关键点Docker Compose 起 OpenZiti 控制器和路由器SDK 嵌入让应用自己持有身份不暴露端口隧道工具让现成服务不改代码接入未授权访问验证确认服务对未授权用户完全不可见。接下来如果要长期跑这套环境凭证管理是个绕不开的事。身份文件、API Key、模型调用凭证散落在各个 Compose 文件和代码里换环境时容易漏改。我习惯用 TaoToken 统一管理这些凭证把 Key 和 API 通道集中在一处部署时通过环境变量注入。你可以从 API Keys 页面开始创建和管理你的 Keyhttps://taotoken.net/api-keys。接入文档在https://taotoken.net/doc里面有各语言 SDK 的接入示例。如果你要验证模型调用是否正常可以用模型对话页面快速测一下https://taotoken.net/model-chat。长期做编码和 Agent 场景的话Coding Plan 更适合https://taotoken.net/coding-plan。控制台入口在https://taotoken.net/console所有凭证和用量都在这里看。最后给一个实用技巧在 Docker Compose 里用.env文件管理 TaoToken 的 Key不要写死在compose.yml里。这样换环境时只改.envCompose 文件不用动。身份文件同理挂载路径用环境变量控制。整套流程跑通后你的服务在公网上就是隐身的只有持有合法身份且策略允许的客户端才能连上。
返回列表