详细记录如何通过Cloudflare Access+邮箱OTP+隧道+OpenTelemetry+Caddy+arize Phoenix构建安全隔离的可观测链路,保护内部敏感信息不暴露。
在构建 AI 评估平台时,直接从仪表板、模型和评分框架入手是非常诱人的。
我们选择了一个更根本的问题出发:
一个团队如何才能安全地查看评估遥测数据,而不会将虚拟机、数据库、凭证或内部服务暴露于公共互联网?
本文记录的是我们实际构建的管道:一条面向 AI 评估平台的安全、自托管的追踪可观测性路径。
它并不声称已经完成了一个持久化的多租户评估执行系统。可观测性和访问管道已经在线运行;持久化任务执行、真实项目接入和 CI 门控仍是后续的独立步骤。
已实现的路径如下:
Team member's browser
|
| HTTPS
v
Cloudflare Access
|
| Email one-time passcode
| Approved company email domain
v
Cloudflare Tunnel
|
| Outbound-only connection from Azure
v
Azure virtual machine
|
v
Caddy reverse proxy
|
+----------------------+
| |
v v
Arize Phoenix UI Evaluation API
|
| Safe operational trace metadata
v
OpenTelemetry Collector
|
| Redaction rules
v
Arize Phoenix
|
v
Trace and evaluation visibility
这为团队提供了一个干净的浏览器界面来查看追踪和评估元数据,同时将实际基础设施保持在私有状态。
已部署的管道包括:
当前的实际实现是一条可观测性管道,而非一个已完成的评估任务处理平台。
以下是我们计划的后续步骤,不应与已部署的管道混淆:
明确这个边界非常重要。追踪平台可以证明遥测数据在系统中安全地流动;但它本身不能证明评估任务正在被正确执行、持久化或治理。
Azure 虚拟机没有公网 IP。
这意味着以下内容不会暴露于互联网:
虚拟机不接收来自互联网的入站流量,而是运行一个向 Cloudflare 创建出站连接的隧道客户端。
传统公共服务器模式
Internet --> Public IP --> VM --> Application
实现的模式
Internet --> Cloudflare Access --> Cloudflare Tunnel <-- Private VM
这消除了最明显的攻击面:一个可公开寻址的虚拟机。
即使有人发现了 Azure 资源组、虚拟机名称、私有 IP 或应用端口,他们也无法直接从公共互联网访问该工作负载。
该平台使用 Cloudflare Access 进行保护。
团队成员在浏览器中打开评估平台。
Cloudflare 在请求到达 Azure 之前拦截该请求。
用户输入其公司邮箱地址。
Cloudflare 发送一次性验证码。
Cloudflare 验证验证码。
Cloudflare 允许已认证的浏览器会话通过隧道。
用户到达 Phoenix 界面。
这为团队提供了一种低摩擦的体验,同时保留了强大的外层安全边界。
没有共享平台密码。
没有公共应用登录端点。
无需直接暴露虚拟机来使 UI 变得便捷。
安全设计使用多层防御,而非依赖单一控制。
Layer 1: Cloudflare edge protection
Layer 2: Company-domain email OTP
Layer 3: Cloudflare Tunnel
Layer 4: No public Azure VM IP
Layer 5: Local reverse proxy routing
Layer 6: Private container registry
Layer 7: Managed identity
Layer 8: Key Vault-backed secrets
Layer 9: Telemetry redaction
Layer 10: Separate service containers and databases
如果某一层配置错误,其他层仍然会缩小爆炸半径。
Cloudflare Access 是第一道防线。
它在请求发送到 Azure 环境之前验证身份。只有使用已批准公司邮箱域的用户才能收到 OTP 并建立已认证的浏览器会话。
这防止了匿名互联网用户访问应用界面。
虚拟机没有公网 IP 地址。
这消除了以下内容的直接暴露:
管理访问必须通过已批准的 Azure 和网络访问路径,而不是公共 SSH 端点。
隧道从虚拟机发起。
这很关键,因为 Azure 工作负载不需要接收公共入站流量。它只需要建立到 Cloudflare 的出站连接。
隧道将受保护的公共主机名映射到虚拟机上的本地反向代理监听器。
Cloudflare 知道如何到达服务。
公共互联网不知道如何到达虚拟机。
Caddy 充当本地反向代理。
它提供一个受控的内部入口点,并将流量路由到正确的服务:
/ -> Phoenix UI
/v1 -> Evaluation API
/health -> Evaluation API health check
/ready -> Evaluation API readiness check
这避免了向用户单独暴露每个服务。
浏览器看到一个受保护的站点。内部服务保持在本地代理边界之后。
平台使用的容器存储在 Azure Container Registry 中。
注册表配置为避免公共镜像访问。虚拟机使用其 Azure 托管标识检索镜像。
这提供了几个优势:
容器标签(如 latest 或 production)会随时间变化。
不可变摘要标识一个精确的镜像构建。
这给了我们更强的部署声明:
此工作负载运行了这个精确的镜像。
而不是:
此工作负载运行了恰好具有此标签的任何镜像。
临时镜像推送权限仅在构建过程中使用,之后被撤销。运行时虚拟机仅保留镜像拉取权限。
平台需要敏感配置,包括:
这些存储在 Azure Key Vault 中,而不是:
虚拟机仅通过其托管标识访问所需的密钥。
工作负载使用专用 Azure 托管标识。
托管标识为虚拟机提供了一个受控的 Azure 身份,而无需在磁盘上放置长期存在的 Azure 密码或客户端密钥。
该身份的权限范围包括:
它不会在整个订阅中获得广泛的管理权限。
这遵循了最小权限原则:
工作负载仅获得运行所需的权限。
可观测性只有在不成为第二次数据泄露时才有价值。
评估 API 向 OpenTelemetry 发出安全的操作元数据,例如:
遥测路径设计为不发出:
OpenTelemetry Collector 在将遥测转发到 Phoenix 之前也应用脱敏规则。
这意味着 UI 可以回答以下操作问题:
而不会自动存储潜在的敏感 Agent 内容。
实现的遥测管道如下:
Evaluation API
|
| Creates safe spans
v
OpenTelemetry Collector
|
| Removes protected fields
v
Phoenix ingestion endpoint
|
v
Phoenix trace interface
通过此路径发送的第一条追踪是特意合成的。
它仅包含非敏感元数据,例如:
未使用任何客户 prompt、用户消息、模型响应或生产 Agent 内容来验证管道。
这很重要,因为在引入真实数据之前就证明了遥测集成的可行性。
部署期间,公共浏览器端点显示 Cloudflare Tunnel 错误。
问题不是公共 DNS 问题,也不是暴露的 Azure 端口问题。
Cloudflare 拥有主机名路由,但没有看到健康的隧道连接器。
根本原因有两个部分:
修复步骤:
此事件强化了一个重要的运营原则:
在基于隧道的设计中,隧道健康状况是一个生产依赖。
关键健康路径不再是"端口 443 开放吗?"而是:
连接器已认证吗?
连接器健康吗?
它能到达本地代理吗?
代理能到达正确的上游服务吗?
当前系统是一个强大的访问和可观测性基础,但它不是最终的安全状态。
最高优先级的后续步骤是:
我们构建了一条从已认证团队浏览器到私有 Azure 可观测性服务的受控路径:
Authenticated user
->
Cloudflare Access
->
Cloudflare Tunnel
->
Private Azure VM
->
Reverse proxy
->
Phoenix trace UI
虚拟机不是公共的。数据库不是公共的。内部服务端口不是公共的。密钥不存储在代码中。镜像分发是私有的。遥测设计为避免将 prompts、输出、凭据和头信息携带到可观测性层。
下一阶段是评估执行和项目治理。但在连接真实 Agent 工作负载之前,我们现在拥有了负责任地观察它们所需的安全可视化管道。