gRPC Security Handshakers 源码深度解析:TLS/ALTS 握手框架与安全端点实现
【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc
导读
本篇文章聚焦 gRPC 核心仓库中负责安全握手的实现模块——src/core/handshaker/security/。该目录承载了 gRPC 安全基础设施中至关重要的一环:通过 TLS、ALTS 等传输安全机制(TSI)完成客户端与服务端之间的身份认证与通信加密。阅读本文后,你将理解SecurityHandshaker如何驱动 TSI 握手状态机、SecureEndpoint/PipelinedSecureEndpoint如何对grpc_endpoint进行加解密包装、握手完成后如何校验对端身份并移交 auth context,以及握手管道(pipelining)启发式选择器的调优参数。
一、模块定位:安全握手在 gRPC 连接建立中的角色
1.1 为什么需要 handshaker 框架
在 gRPC 中,一条连接从底层 TCP 就绪到真正承载 RPC 流量之间,需要先执行若干"握手"步骤,例如 HTTP CONNECT 代理协商、TLS 握手等。gRPC 为此设计了可插拔的 handshaker 框架(src/core/handshaker/handshaker.h):每个Handshaker代表一次握手操作,HandshakeManager按照优先级依次调用它们。
核心抽象定义如下(见 handshaker.h):
HandshakerArgs:握手过程中的输入/输出参数,包含endpoint(可被握手器替换为包装后的端点)、ChannelArgs、未被消费的read_buffer、exit_early标志、EventEngine 指针、截止时间deadline以及 channelz 追踪节点等;Handshaker:抽象基类,暴露DoHandshake()、Shutdown()和name()三个纯虚接口;HandshakeManager:按加入顺序串行执行握手器,并通过on_handshake_done回调把最终结果交还调用方。
HandshakerFactory通过HandshakerPriority枚举(handshaker_factory.h)声明自己的执行优先级,安全握手器注册在kSecurityHandshakers这一档,位于 TCP 连接建立、HTTP CONNECT 之后,负责"连接建立之后的传输安全握手"。
1.2 security/ 目录在整个框架中的位置
src/core/handshaker/下的兄弟模块包括http_connect/(HTTP CONNECT 代理握手)、tcp_connect/(TCP 基础握手)、endpoint_info/(端信息数据结构)以及proxy_mapper相关注册表(见 src/core/handshaker/AGENTS.md)。security/是其中专门承担TLS / ALTS 等安全握手的目录,其职责正如目录内 AGENTS.md 所述:"负责在客户端与服务端之间建立安全连接,使用 TLS 或 ALTS 等传输安全机制认证服务端并加密双方全部通信"。
二、目录结构与核心文件职责
src/core/handshaker/security/下共有 7 个文件,对应关系如下(依据 AGENTS.md 的 Files 小节):
| 文件 | 职责 |
|---|---|
| security_handshaker.h / security_handshaker.cc | 定义SecurityHandshaker类,是所有安全握手器的基类(实际为对 TSItsi_handshaker的封装驱动),并提供客户端/服务端两个HandshakerFactory |
| secure_endpoint.h / secure_endpoint.cc | 定义SecureEndpoint,对底层grpc_endpoint的包装,提供加解密后的安全通道 |
| pipelined_secure_endpoint.cc | 支持读取管道化(read pipelining)的SecureEndpoint实现 |
| pipelining_heuristic_selector.h | 选择是否启用读管道化的启发式策略(含四种实现) |
三、SecurityHandshaker:TSI 握手状态机的驱动者
3.1 创建入口与工厂注册
SecurityHandshakerCreate()是安全握手器的统一创建入口(security_handshaker.cc),其逻辑为:
- 若传入的
absl::StatusOr<tsi_handshaker*>携带错误状态,则返回一个FailHandshaker,它会在DoHandshake()时直接以失败状态回调(name()为"security_fail"); - 若 TSI 握手器指针为空,同样返回
FailHandshaker(错误码为UnknownError); - 否则构造真正的
SecurityHandshaker。
工厂注册发生在SecurityRegisterHandshakerFactories()(security_handshaker.cc),分别向HANDSHAKER_CLIENT与HANDSHAKER_SERVER注册ClientSecurityHandshakerFactory与ServerSecurityHandshakerFactory。该函数由核心插件注册表在构建CoreConfiguration时调用(见 src/core/plugin_registry/grpc_plugin_registry.cc)。
两个工厂的实现非常精简(security_handshaker.cc):从ChannelArgs中取出对应的安全连接器对象(客户端为grpc_channel_security_connector,服务端为grpc_server_security_connector),然后委托其add_handshakers()方法把具体的 TSI 握手器加入HandshakeManager。它们声明的优先级均为HandshakerPriority::kSecurityHandshakers。
3.2 安全连接器如何产生 TSI 握手器
以 SSL/TLS 为例,SslChannelSecurityConnector::add_handshakers()(src/core/credentials/transport/ssl/ssl_security_connector.cc)展示了完整链路:
- 调用
tsi_ssl_client_handshaker_factory_create_handshaker()创建 TSI 层握手器,传入覆盖目标名(overridden target name)、可选 channel args(如GRPC_ARG_TRANSPORT_PROTOCOLS)以及用于遥测的CollectionScope、backend service、locality 等信息; - 将得到的
tsi_handshaker*连同this(连接器)和args一起交给SecurityHandshakerCreate(); - 最终通过
handshake_mgr->Add(...)挂到握手管理器上。
同理,ALTS 安全连接器(src/core/credentials/transport/alts/alts_security_connector.cc)、TLS 连接器(src/core/credentials/transport/tls/tls_security_connector.cc)、local 与 insecure 连接器都遵循同一模式。这正是 AGENTS.md 中"SecurityHandshaker类被设计为可扩展的,可以轻松添加新的安全机制"的具体体现——新增一种安全机制,只需提供对应的 TSI 握手器工厂与grpc_security_connector子类。
3.3 握手状态机:读-写-再读的循环驱动
SecurityHandshaker::DoHandshake()(security_handshaker.cc)是握手入口,其核心是一个围绕 TSItsi_handshaker_next()的状态循环:
DoHandshake └─> MoveReadBufferIntoHandshakeBuffer() // 把已读数据拷入握手缓冲 └─> DoHandshakerNextLocked() // 调用 tsi_handshaker_next() ├─ TSI_INCOMPLETE_DATA → grpc_endpoint_read() 等待更多数据 ├─ bytes_to_send > 0 → grpc_endpoint_write() 向对端发送握手消息 │ └─ 写完后若无 handshaker_result → 继续读对端数据 └─ 拿到 handshaker_result → CheckPeerLocked() 校验对端身份关键实现细节(均可在 security_handshaker.cc 中验证):
- 初始缓冲:
GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE为 256 字节,MoveReadBufferIntoHandshakeBuffer()(第 L151-L165 行)在数据超出时会通过gpr_realloc动态扩容; - 异步支持:
tsi_handshaker_next()可能返回TSI_ASYNC,此时回调OnHandshakeNextDoneGrpcWrapper会在 TSI 线程被调用,代码通过self.release()把引用释放给异步路径(第 L419-L440 行); - 防死锁设计:读写回调通过
OnHandshakeDataReceivedFromPeerFnScheduler/OnHandshakeDataSentToPeerFnScheduler投递到 EventEngine 上异步执行,避免在持有互斥锁mu_时内联回调造成死锁(源码注释明确指出这是 EventEngine 迁移前的临时方案); - 并发保护:整个握手状态机运行在
Mutex mu_保护之下,所有*Locked方法都要求持锁调用(ABSL_EXCLUSIVE_LOCKS_REQUIRED)。
3.4 对端校验与握手收尾
当 TSI 报告握手完成并给出tsi_handshaker_result后,CheckPeerLocked()(第 L314-L339 行)做三件事:
- 通过
tsi_handshaker_result_extract_peer()提取对端身份信息tsi_peer; - 调用连接器的
check_peer()异步校验对端(TLS 场景下校验证书链与主机名,见 ssl_security_connector.cc),并把校验结果通过on_peer_checked_闭包回调; - 校验通过后,从 auth context 中查询
GRPC_TRANSPORT_SECURITY_LEVEL_PROPERTY_NAME属性,若不存在或为TSI_SECURITY_NONE,则通过global_stats().IncrementInsecureConnectionsCreated()统计一次"非安全连接"。
校验完成后进入OnPeerCheckedFn()(第 L216-L312 行)的收尾阶段:
- 从
tsi_handshaker_result取回"未使用字节"(tsi_handshaker_result_get_unused_bytes)——这些字节属于握手消息尾部夹带的早期应用数据,必须原样交还; - 查询 frame protector 类型(
TSI_FRAME_PROTECTOR_ZERO_COPY/TSI_FRAME_PROTECTOR_NORMAL/TSI_FRAME_PROTECTOR_NONE),据此创建零拷贝或普通 protector; - 若存在 protector,则用
grpc_secure_endpoint_create()把原 endpoint 包装为安全端点,未使用字节作为 leftover slices 传入;否则把未使用字节直接追加回read_buffer; - 把 auth context 写入
ChannelArgs,并在有 protector 时写入由 auth context 生成的 channelz 安全信息(MakeChannelzSecurityFromAuthContext,目前以 TLS 类型记录远端证书); - 最后调用
Finish(absl::OkStatus())通知握手成功。
Shutdown()(第 L519-L527 行)则负责中途取消:置位is_shutdown_、调用connector_->cancel_check_peer()取消对端校验、tsi_handshaker_shutdown()关闭 TSI 握手器并释放 endpoint。
四、SecureEndpoint:加解密通道的端点包装
4.1 创建接口与 channel arg
SecureEndpoint本质上是把tsi_frame_protector/tsi_zero_copy_grpc_protector与底层grpc_endpoint组合成一个对外透明的安全端点。创建函数在 secure_endpoint.h:
grpc_secure_endpoint_create():包装 iomgr 风格的grpc_endpoint,可接收握手遗留的 leftover slices;grpc_pipelined_secure_endpoint_create():EventEngine 风格、支持管道化的变体。
该头文件还定义了三个可调 channel arg(整数类型):
| Channel Arg | 含义 |
|---|---|
grpc.secure_endpoint.decryption_offload_threshold | 单次读取达到该大小时,将解密卸载到 EventEngine 线程执行 |
grpc.secure_endpoint.encryption_offload_threshold | 单次写入达到该大小时,将加密卸载到 EventEngine 线程执行 |
grpc.secure_endpoint.encryption_offload_max_buffered_writes | 加密卸载路径上允许缓冲的最大写请求数 |
这些参数允许在高吞吐场景下把 CPU 密集的加解密操作移出 I/O 线程,以换取更低的尾延迟与更高的吞吐(阈值以下则内联执行以降低调度开销)。
4.2 数据面:protector 与零拷贝路径
从 pipelined_secure_endpoint.cc 的FrameProtector实现可以看到数据面的设计:
FrameProtector同时持有普通与零拷贝两种 protector,二者互斥:is_zero_copy_protector_标记当前使用哪种;- 读/写各分配一个
STAGING_BUFFER_SIZE(8192 字节)的暂存 slice,普通 protector 模式下读写数据经暂存缓冲中转; - 内存全部来自
ResourceQuota的MemoryOwner(通过memory_owner_.MakeSlice(...)分配),并记录self_reservation_保证自身存活期间的内存配额; - 读与写各自持有独立的互斥锁(
read_mu_/write_mu_),允许并发读写方向上的加解密; - 构造时若传入 leftover slices,会复制进内部
SliceBuffer,保证握手阶段遗留字节在安全端点内被正确消费; - 通过
GRPC_TRACE_LOG(secure_endpoint, INFO)与GRPC_TRACE_FLAG_ENABLED(secure_endpoint)提供细粒度的调试追踪。
4.3 管道化读取与启发式选择器
PipelinedSecureEndpoint的"管道化"指在读取路径上提前发起下一次读,让解密与网络读取并行,从而摊薄大消息场景下的往返延迟。但持续管道化在小消息(如普通 RPC 头部)场景下会浪费资源,因此引入 pipelining_heuristic_selector.h 动态决策:
| 启发式 | 行为 | 关键阈值 |
|---|---|---|
AlwaysOffHeuristic | 永远关闭管道化 | — |
AlwaysOnHeuristic | 永远开启管道化 | — |
ConsecutiveSmallReadsHeuristic | 连续出现小读取时关闭管道化,出现大读取时重新开启 | 小读取阈值950 * 1024字节;连续 30 次小读取即关闭 |
MovingAverageHeuristic(默认) | 对读取大小做指数移动平均(新值权重 0.05),低于阈值关闭、高于阈值开启 | 关闭阈值64 * 1024字节;开启阈值950 * 1024字节 |
PipeliningHeuristicSelector默认采用kMovingAverage,可通过SetHeuristicType()在四种策略间切换。从阈值设计可以推断:gRPC 认为单次读取超过约 950 KiB 属于"大读取",值得保持管道化,而 64 KiB 以下的持续小读取则更适合内联解密、关闭管道化以节省开销。
五、源码佐证:测试用例与调用链印证
- 端到端验证:test/core/handshake/secure_endpoint_test.cc 与 test/core/handshake/secure_endpoint_read_coalescing_test.cc 直接测试
grpc_secure_endpoint_create的读写加解密与读取合并行为;test/core/event_engine/posix/posix_endpoint_test.cc 则覆盖了管道化安全端点在 EventEngine 路径上的行为。 - 注册链路:
SecurityRegisterHandshakerFactories仅在 src/core/plugin_registry/grpc_plugin_registry.cc 声明并在第 L151 行调用,印证了安全握手器作为核心插件随 gRPC Core 一同注册的事实。 - 连接器协作:除 SSL 外,TLS(tls_security_connector.cc)、ALTS(alts_security_connector.cc)、local(local_security_connector.cc)、insecure(insecure_security_connector.cc)等连接器均调用
SecurityHandshakerCreate注入握手器,验证了"安全连接器决定机制、SecurityHandshaker 统一驱动"的分层设计。
六、总结
src/core/handshaker/security/是 gRPC 传输安全的核心实现层:SecurityHandshaker以统一的状态机驱动 TLS/ALTS 等 TSI 握手,SecureEndpoint与PipelinedSecureEndpoint负责握手完成后的数据加解密与读取管道化,PipeliningHeuristicSelector则提供了可调优的性能开关。理解这一模块,即可掌握 gRPC 从"TCP 就绪"到"安全通道就绪"的完整链路,以及grpc.secure_endpoint.*系列 channel arg 和读取管道化阈值的调优入口。
【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考