ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Teleport 集群路由(Cluster Routing)深入解析:从证书签发到跨集群直连的实现原理

Teleport 集群路由(Cluster Routing)深入解析:从证书签发到跨集群直连的实现原理 Teleport 集群路由Cluster Routing深入解析从证书签发到跨集群直连的实现原理【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址: https://gitcode.com/gh_mirrors/tel/teleport导读在多集群部署中如何让用户使用根集群签发的证书直接在叶子集群leaf cluster上工作是影响大规模基础设施访问体验的关键问题。本文以 Teleport 官方设计文档 rfd/0021-cluster-routing.md 为核心系统梳理集群路由机制的演进历程从最初的jumphost 地址映射集群名称原始提案到最终落地的从 jumphost 证书推断集群名称实现方案并深入源码剖析RouteToCluster证书扩展、WithoutJumpHosts绕过跳转重签发等核心机制的底层原理。读完本文你将理解 Teleport 如何让tsh ssh -J在多叶子集群间无缝切换以及证书扩展在跨集群路由中扮演的关键角色。一、问题背景为什么需要集群路由1.1 根集群与叶子集群的信任关系在 Teleport 的信任集群trusted cluster模型中一个根集群root cluster可以与多个叶子集群leaf cluster建立信任关系。传统连接路径是用户 ──► 根集群代理 ──► 叶子集群代理 ──► 叶子集群节点1.2 核心痛点高延迟与低效率根据 RFD 21 的描述这种必经根集群的模式存在明显的体验问题高延迟当用户与根集群之间的网络延迟较高时每次访问叶子集群内的服务器都会产生糟糕的用户体验frustrating user experience多集群切换繁琐用户需要反复执行tsh login clusterName来切换目标集群并发工作几乎不可能tsh login每次只能切换到单一集群同时操作多个叶子集群几乎不可行。RFD 21 的核心目标正是允许用户直接使用根集群签发的证书访问叶子集群绕过根集群的转发从而规避上述问题。二、现状能力RouteToCluster与手动跳转在提出新方案之前Teleport 已经支持了跨集群访问的基础能力但操作繁琐# 第一步登录目标集群获得 RouteToCluster 指向叶子集群的证书 tsh login leafCluster # 第二步通过 jumphost 方式绕过根集群直连叶子集群 tsh ssh -J leafCluster serverName这里的关键概念是RouteToCluster它指定了 SSH 用户证书所要签发的目标集群名称。该字段定义在 api/types/types.pb.go 中// RouteToCluster is the name of Teleport cluster to issue credentials for. RouteToCluster string protobuf:bytes,12,opt,nameRouteToCluster,proto3 json:route_to_cluster,omitemptyRFD 21 明确指出这种方式的缺陷在于每次切换集群都要重新执行tsh login clusterName操作繁琐多集群并发工作几乎不可能——证书只能指向单一集群。三、原始提案jumphost 地址到集群名称的映射3.1 提案思路RFD 21 最初提出的方案是改变tsh ssh -J的行为当 jumphost 地址与 profile 中保存的根代理地址不匹配时自动为目标集群请求证书重签发。具体流程设想如下用户执行tsh ssh -J clusterName serverName客户端首先连接根代理遍历所有远程集群remote clusters检查给定的 jumphost 地址是否与某个叶子代理匹配若匹配则以指向该叶子集群的RouteToCluster重新签发证书为缓解重签发的性能损耗在客户端缓存证书RFD 中引用了 PR #5938 的思路即在客户端本地缓存已签发的证书。该方案期望达成的效果是用户可以在多个叶子集群上并发工作而无需反复tsh login。3.2 问题一jumphost 地址匹配的不可靠性RFD 21 详细列出了地址匹配思路的第一个致命问题缺乏统一的数据基准集群尤其是信任集群并不一定配置了ssh_public_addr或public_addr等选项即使配置了也无法保证准确性一个集群可能存在多个等效的公共地址不同用户可能输入不同的地址可能存在地址冲突多个信任集群甚至可能配置了完全相同的*public_addr值无论是有意还是非传统部署所致。换言之用地址作为集群身份的判定依据在本质上是不稳定、不可靠的。3.3 问题二-J覆盖影响证书重签发请求第二个问题来自-J参数的作用范围。RFD 21 指出-J不仅设置了 SSH 节点连接的代理地址还会影响更通用的 Auth API 请求地址。这意味着如果调用常规的证书重签发流程重签发请求会被发送到 jumphost即叶子代理而非根代理。虽然这一行为看起来是刻意设计但确实容易令人困惑——用户通常期望只有--proxy才影响此类请求而-J不应该参与。从源码看这一行为在 lib/client/api.go 中有明确体现当存在 JumpHosts 时SSH 代理地址会被覆盖为跳转主机的地址。四、实际实现从 jumphost 证书推断集群名称4.1 核心思想用证书而非地址识别集群针对问题一RFD 21 给出了最终落地的设计决策不要用 jumphost 地址本身来建立集群名称而是分析连接 jumphost 时服务器出示的主机证书。其依据是如果该主机证书确实属于 Teleport 代理那么它必然带有包含集群名称的证书扩展certificate extension。通过读取该扩展客户端可以可靠地知道用户证书应该使用哪个RouteToCluster值并在缺少对应证书时请求重签发。这一设计将集群身份的判定从不可靠的外部配置转移到加密签发的证书内部字段从根本上解决了地址匹配的不确定性问题。4.2 解决-J覆盖显式忽略跳转设置针对问题二RFD 21 的结论是证书重签发请求必须忽略-J覆盖让客户端回退到根代理——因为只有根代理才能签发指向叶子集群的 SSH 证书。这一设计在如今的源码中有着完整的落地实现。在 lib/client/api.go 中SignersForClusterWithReissue函数演示了完整的获取集群签名者 → 缺失则重签发流程// SignersForClusterWithReissue fetches cluster-specific signers from stored certificates. // If the cluster certificates are not found, it is requested to be reissued. func (tc *TeleportClient) SignersForClusterWithReissue(ctx context.Context, clusterName string) ([]ssh.Signer, error) { // ... signers, err : tc.localAgent.signersForCluster(clusterName) if err nil { return signers, nil } if !trace.IsNotFound(err) { return nil, trace.Wrap(err) } if err : tc.WithoutJumpHosts(func(tc *TeleportClient) error { return tc.ReissueUserCerts(ctx, CertCacheKeep, ReissueParams{RouteToCluster: clusterName}) }); err ! nil { return nil, trace.Wrap(err) } // ... }注意其中的关键调用tc.WithoutJumpHosts(...)。这个包装器正是 RFD 21 忽略-J覆盖决策的代码化实现。其定义位于同一文件的 lib/client/api.go// WithoutJumpHosts executes the given function with a Teleport client that has // no JumpHosts set, i.e. presumably falling back to the proxy specified in the // profile. func (tc *TeleportClient) WithoutJumpHosts(fn func(tcNoJump *TeleportClient) error) error { tcNoJump : TeleportClient{ Config: tc.Config, localAgent: tc.localAgent, OnShellCreated: tc.OnShellCreated, eventsCh: make(chan events.EventFields, 1024), lastPing: tc.lastPing, } tcNoJump.JumpHosts nil return trace.Wrap(fn(tcNoJump)) }函数注释中的 presumably falling back to the proxy specified in the profile推测回退到 profile 中指定的代理与 RFD 21 的表述几乎逐字对应重签发请求将回到根代理由根代理签发带有正确RouteToCluster的叶子集群证书同时客户端本地缓存CertCacheKeep保证性能。4.3 证书重签发后的集群选择逻辑重签发完成后客户端如何确定当前连接的是哪个集群从 lib/client/api.go 的ConnectToCluster可以看到集群选择逻辑cluster : tc.SiteName connected : pclt.ClusterName() root, err : tc.rootClusterName() if err nil len(tc.JumpHosts) 0 connected ! root { cluster connected }即当存在 jumphost 且实际连接的集群不是根集群时将目标集群切换为实际连接的集群。同时ViaJumpHost: len(tc.JumpHosts) 0标记了本次连接是否经过跳转主机。而在 lib/client/cluster_client.go 中RouteToCluster缺省时也会被填充为客户端当前的集群名确保证书请求始终有明确的目标if params.RouteToCluster { params.RouteToCluster c.cluster }五、服务端视角Auth 如何校验与签发路由证书5.1 RouteToCluster 的默认值与合法性校验在服务端签发证书前需要对RouteToCluster进行校验。相关逻辑位于 lib/auth/auth.goif req.RouteToCluster { req.RouteToCluster clusterName } if req.RouteToCluster ! clusterName { // 仅允许无作用域证书unscoped certs为远程集群签发 // ... return nil, trace.WrapWithMessage(services.ErrScopedIdentity, cannot generate certs for remote cluster %q, remote cluster access is only supported for unscoped certs, req.RouteToCluster) } rc, err : a.GetRemoteCluster(ctx, req.RouteToCluster) if err ! nil { return nil, trace.NotFound(remote cluster %q not found, req.RouteToCluster) }这段代码揭示了两个重要的实现事实空值默认回退请求未指定RouteToCluster时默认视为签发本集群clusterName的证书远程集群必须真实存在目标集群必须能在远程集群列表GetRemoteCluster中找到否则签发失败——这印证了 RFD 21 中遍历所有远程集群的前置条件在服务端同样存在。5.2 证书扩展机制的通用基础RFD 21 中反复强调的证书扩展certificate extension并非孤立设计而是 Teleport 证书体系中广泛使用的通用机制。在 api/types/types.pb.go 中定义了扩展的模式与类型CertExtensionMode指定扩展在证书中的使用模式CertExtensionType指定扩展适用的证书类型如 SSH 证书CertExtensionType_SSH。从源码结构可以推断RouteToCluster正是通过这类证书扩展机制携带在签发的证书中使得客户端在连接 jumphost 时能够通过解析主机证书的扩展来获知集群身份。六、未来工作从手动-J到自动直连RFD 21 在Future Work一节中规划了进一步的自动化方向其核心设想是让客户端自动选择直连路径无需用户显式传入-J6.1 信任建立阶段的直连友好声明当建立信任关系时trusted_cluster资源可以声明该集群应当对根集群的客户端直接可用并附带合适的 jumphost/public 地址。注意RFD 特别指出信任集群关系并非总是意味着直连友好——它也可能仅用于建立信任同时通过根集群暴露网络连通性即叶子集群本身对客户端不可达的场景。6.2 根集群记录并传播优先使用 jumphost扩展该直连友好指示会被根集群接收并记录在根集群自己的remote_cluster资源中。此后每当根 Auth 需要签发RouteToCluster指向此类叶子集群的 SSH 证书时同时在新证书中设置一个prefer jumphost扩展包含叶子集群宣告的 jumphost 地址客户端收到该扩展后自动使用 jumphost 连接叶子集群绕过根代理全程无需用户任何显式操作。这一设想将把用户手动传入-J演进为证书自身携带路由偏好让集群路由的决策进一步下沉到证书与客户端机制中与本文所述的从证书推断集群身份设计哲学一脉相承。七、总结RFD 21Cluster Routing为 Teleport 多集群访问解决了一个关键体验问题其设计演进本身极具参考价值阶段方案判定集群身份的依据结论原始提案jumphost 地址匹配集群名称外部配置public_addr等不可靠被否决实际实现从 jumphost 主机证书扩展推断集群名称加密证书内的扩展字段已实现state: implemented未来工作证书携带 prefer jumphost 扩展客户端自动直连证书声明的路由偏好规划中从实现层面看这一设计最终落实为三块相互咬合的代码机制证书字段RouteToCluster作为证书签发请求的目标集群标识api/types/types.pb.go客户端绕过WithoutJumpHosts在证书重签发时显式剥离-J确保请求回到根代理lib/client/api.go服务端校验签发前通过GetRemoteCluster验证目标远程集群存在lib/auth/auth.go。对使用 Teleport 的团队而言理解集群路由机制有助于合理设计多集群拓扑与信任关系并在网络拓扑允许时通过直连叶子集群显著降低访问延迟。对想深入源码的开发者建议从 lib/client/api.go 中的SignersForClusterWithReissue与ConnectToCluster两个函数入手结合 lib/client/cluster_client.go 中RouteToCluster的传递链路即可完整追踪一次跨集群证书签发的生命周期。【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址: https://gitcode.com/gh_mirrors/tel/teleport创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表