Envoy GCP 认证过滤器新增 token_metadata_key:将 GCP 认证令牌写入动态元数据
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
Envoy 的 GCP 认证过滤器(envoy.filters.http.gcp_authn)负责从 GCE Metadata Server 获取 GCP 认证令牌并注入到请求中。当前开发版本(见 changelogs/current/new_features/gcp_authn__token-metadata-key.rst)新增了token_metadata_key配置字段:不再局限于把令牌写入 HTTP 请求头,而是可以将令牌保存到以过滤器配置名为命名空间的**动态元数据(dynamic metadata)**中,供过滤器链中的下游过滤器、路由动作或上层控制面消费。阅读完本文,你将掌握该字段的配置方法、优先级语义、底层源码实现,以及如何在iam_access_token等依赖动态元数据的场景中利用这一新能力。
一、特性背景:GCP 认证过滤器的令牌注入方式
GCP 认证过滤器用于服务到服务(service-to-service)认证场景。当多个私有服务需要互相通信时,Envoy 作为代理在请求转发前,从 GCE Metadata Server 获取认证令牌(identity token / access token),并把它注入到发往目标服务的请求中,避免请求因缺少凭据而被目标服务拒绝。其整体行为在 GCP Authentication Filter 文档 中有完整描述。
在引入token_metadata_key之前,令牌的注入位置完全由请求头决定,共有两种方式(对应 proto 中token_header字段与默认行为):
- 默认行为:写入
Authorization请求头,格式为Authorization: Bearer <token>; - 自定义请求头:通过
token_header.name指定头名、token_header.value_prefix指定前缀(格式为value_prefix<token>,例如Bearer带一个尾随空格),此时令牌写入自定义头。
上述逻辑完整体现在过滤器实现 gcp_authn_filter.cc 的addTokenToRequest方法中(第 253-270 行),这也是token_metadata_key特性落地的位置。
二、token_metadata_key 配置项详解
token_metadata_key定义在过滤器配置 proto 中,见 gcp_authn.proto(第 52-54 行):
// Optional dynamic metadata key to save the token. The metadata uses the filter config name as the namespace. // Takes precedence over ``token_header``. string token_metadata_key = 7;关键语义如下:
| 要点 | 说明 |
|---|---|
| 字段类型 | string,可选字段;配置过滤器配置名空间下的动态元数据键名 |
| 命名空间 | 以**过滤器配置名(filter config name)**作为动态元数据命名空间,即envoy.filters.http.gcp_authn |
| 优先级 | 一旦设置,token_header不再生效;未设置时回退到token_header/ 默认Authorization: Bearer <token> |
| 数据形态 | 令牌以字符串值写入动态元数据的 Struct 字段中 |
从 proto 注释可以确认,该字段位于GcpAuthnFilterConfig的第 7 号字段(当前结构[#next-free-field: 9]),与token_header(字段 4)、cluster(字段 5)、timeout(字段 6)、audience(字段 8)并列。
与 token_header 的优先级关系
配置字段之间遵循明确的优先级:token_metadata_key>token_header> 默认Authorization头。proto 中token_header字段(第 46-50 行)的注释也印证了这一点:
默认情况下(未指定该字段),令牌写入
Authorization头,格式为Authorization: Bearer <token>。若设置了token_metadata_key,该字段不生效。
三、完整配置示例
官方配置示例文件 gcp-authn-filter-configuration.yaml 给出了过滤器与集群的完整配置。在 HTTP 过滤器链中启用该过滤器并配置token_metadata_key的写法如下:
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8000 filter_chains: - filters: - name: "http" typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: HTTP2 stat_prefix: "config_test" route_config: name: "route_config_0" virtual_hosts: - name: "integration" domains: ["*"] routes: - match: prefix: "/" route: cluster: "cluster_0" http_filters: - name: "envoy.filters.http.gcp_authn" typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig http_uri: uri: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=[AUDIENCE]" cluster: "gcp_authn" timeout: 10s # 新增:将获取到的令牌保存到动态元数据中, # 命名空间为 "envoy.filters.http.gcp_authn",键名为 "token" token_metadata_key: "token" - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: cluster_0 # Cluster for fake destination service which has typed metadata that contains the audience information. load_assignment: cluster_name: cluster_0 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 0.0.0.0 port_value: 8000 typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} metadata: typed_filter_metadata: envoy.filters.http.gcp_authn: "@type": type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience url: http://test.com # Cluster for GCE metadata server - name: gcp_authn type: STRICT_DNS connect_timeout: 5s dns_lookup_family: V4_ONLY load_assignment: cluster_name: "gcp_authn" endpoints: - lb_endpoints: - endpoint: address: socket_address: address: "metadata.google.internal" port_value: 80需要注意http_uri字段在 proto 中已被标记为 deprecated(计划在 3.0 版本废弃),控制面应优先使用新增的cluster与timeout字段来配置元数据服务器访问,http_uri仅作向后兼容兜底(见 gcp_authn.proto 第 28-37 行)。
四、源码级实现原理
1. 核心写入逻辑:addTokenToRequest
token_metadata_key的实现位于 gcp_authn_filter.cc 的GcpAuthnFilter::addTokenToRequest(第 253-270 行):
void GcpAuthnFilter::addTokenToRequest(Http::RequestHeaderMap& hdrs, absl::string_view token_str) { const FilterConfigProto proto = filter_config_->config(); if (!proto.token_metadata_key().empty()) { Protobuf::Struct metadata; (*metadata.mutable_fields())[proto.token_metadata_key()].set_string_value(token_str); decoder_callbacks_->streamInfo().setDynamicMetadata( std::string(decoder_callbacks_->filterConfigName()), metadata); return; } const envoy::extensions::filters::http::gcp_authn::v3::TokenHeader& header = proto.token_header(); if (header.ByteSizeLong() == 0) { std::string id_token = absl::StrCat("Bearer ", token_str); hdrs.setCopy(authorizationHeaderKey(), id_token); } else { std::string id_token = absl::StrCat(header.value_prefix(), token_str); hdrs.setCopy(Http::LowerCaseString(header.name()), id_token); } }实现要点:
- 分支优先级在代码层面固化:只要
token_metadata_key非空,函数立即构造Protobuf::Struct并将令牌以字符串值放入metadata[token_metadata_key],随后调用streamInfo().setDynamicMetadata(filterConfigName(), metadata)写入动态元数据并return,完全跳过请求头写入逻辑; - 命名空间取自过滤器配置名:
decoder_callbacks_->filterConfigName()返回的是配置中该过滤器的名字(如envoy.filters.http.gcp_authn),这正是 proto 注释所述"以过滤器配置名为命名空间"的由来; - 令牌原文写入:写入动态元数据的是令牌原始字符串(JWT 或 access token),不带
Bearer前缀。
2. 调用链
addTokenToRequest在两条路径上被调用(见 gcp_authn_filter.cc):
- 缓存命中路径:
decodeHeaders中先查TokenCache,命中时直接调用addTokenToRequest(hdrs, token.value())后Continue(第 188-196 行),不发起对元数据服务器的异步请求; - 异步获取完成路径:
onComplete回调中在continueDecoding()之前调用addTokenToRequest将令牌写入(第 223-244 行)。
因此无论令牌来自缓存还是新获取,写入动态元数据的路径是统一的。
五、下游如何消费动态元数据中的令牌
写入动态元数据后,过滤器链中排在gcp_authn之后的过滤器、路由动作等都可以按filter_metadata["envoy.filters.http.gcp_authn"]["token"]的形态读取该令牌。仓库内一个典型的下游消费场景是iam_access_token(见 gcp_authn__iam-access-token.rst 与 gcp_authn.proto 第 88-100 行):它的authorization字段是 substitution formatter 模板,官方注释给出的示例为:
Bearer %DYNAMIC_METADATA(gcp_authn:token)%也就是说,先由 gcp_authn 过滤器用token_metadata_key把 GCE 令牌写入gcp_authn命名空间,再通过%DYNAMIC_METADATA(gcp_authn:token)%模板在 IAM 令牌请求中复用它作为授权头(底层格式化逻辑见 gcp_authn_filter.cc 第 72-82 行构造的account_formatter_/auth_formatter_)。这从仓库证据上说明了token_metadata_key的实际价值:让令牌不落入 HTTP 请求头,而是在请求处理管道内部以结构化方式流转,供其他基于动态元数据的组件复用。
六、测试验证
仓库测试 gcp_authn_filter_test.cc 为token_metadata_key提供了三组针对性用例,可作为行为契约:
| 测试用例 | 验证内容 |
|---|---|
SaveTokenToDynamicMetadata(第 993 行) | 配置token_metadata_key: custom_token_key后,令牌以该键写入setDynamicMetadata("envoy.filters.http.gcp_authn", ...),且Authorization头保持为空 |
SaveTokenToDynamicMetadataCacheHit(第 1025 行) | 缓存命中时同样走动态元数据写入路径(缓存令牌值为cached_token),不触发异步客户端调用 |
TokenMetadataKeyPrecedenceOverTokenHeader(第 1060 行) | 同时配置token_metadata_key与token_header时,token_metadata_key优先,自定义请求头custom-header与Authorization均未被写入 |
测试代码还展示了预期的元数据结构:expected_metadata.fields["custom_token_key"]为字符串值(第 1006-1010 行),与实现中Protobuf::Struct的写入方式一一对应。测试中的过滤器配置名 mock 为envoy.filters.http.gcp_authn(第 999-1000 行),印证了命名空间语义。
七、使用注意事项
结合 proto 注释与实现,使用token_metadata_key时有几点需要留意:
- 优先级的明确性:设置该字段后,令牌不再出现在任何请求头中。若下游依赖
Authorization头透传令牌,需评估是否受影响; - 命名空间一致性:写入位置是
streamInfo的动态元数据,命名空间为过滤器配置名。消费方读取时必须使用相同的命名空间与键名,例如%DYNAMIC_METADATA(gcp_authn:token)%; - 数据形态:动态元数据中保存的是令牌原文(无
Bearer前缀),消费方若需要组装标准认证头需自行添加前缀; - 适用范围:
token_metadata_key与token_header、audience、cache_config等字段一样,proto 注释标注"并非所有 data plane 都支持",跨实现使用时需确认目标 data plane 的能力(见 gcp_authn.proto 相关字段注释); - 当前状态:该字段属于
changelogs/current中的新特性,即当前开发分支引入的能力,适用于使用最新版本代码构建的 Envoy。
总结
token_metadata_key是 gcp_authn 过滤器令牌传递方式的补充通道:它以过滤器配置名为命名空间,将获取到的 GCP 认证令牌写入动态元数据,优先级高于token_header与默认Authorization头。从 proto 定义 到 过滤器实现 再到 单元测试,该特性的语义清晰、行为有测试保障,为基于动态元数据令牌流转的复杂认证编排(如iam_access_token的 substitution formatter 消费)提供了基础能力。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考