Envoy Injected Credentials 深入指南:向代理请求注入 Basic Auth、Bearer Token 与 OAuth2 访问令牌
2026/9/12 16:15:04 网站建设 项目流程

Envoy Injected Credentials 深入指南:向代理请求注入 Basic Auth、Bearer Token 与 OAuth2 访问令牌

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

导读

Envoy 的 Injected Credentials(注入凭证)扩展体系为代理转发路径提供了一套标准化的"凭据注入"能力:它允许你通过 HTTP 过滤器,把从 SDS(Secret Discovery Service)读取的静态凭证(如 HTTP Basic Auth、Bearer Token 或任意自定义令牌),或从授权服务器动态获取的 OAuth2 access token,自动注入到发往上游的 HTTP 请求头中。本文以 docs/root/api-v3/config/injected_credentials/injected_credentials.rst 为索引主线,结合api/envoy/extensions/http/injected_credentials/下的 proto 定义与source/extensions/http/injected_credentials/下的 C++ 实现,完整讲解 Generic 与 OAuth2 两类凭证注入器的配置字段、工作流程、错误处理与统计指标,并给出可直接落地的配置示例。读完本文,你将掌握如何把 Envoy 侧车(sidecar)配置成"自动携带工作负载身份凭证"的代理,而无需修改业务应用的请求代码。

一、Injected Credentials 是什么:定位与适用场景

Injected Credentials 并不是一个独立的过滤器,而是一组可被其他 HTTP 过滤器按需装配的凭证注入扩展,注册在envoy.http.injected_credentials扩展类别下。当前仓库中该类别包含两个实现:

  • envoy.http.injected_credentials.generic:注入任意静态凭证(Basic Auth、Bearer Token 等),定义见 generic.proto;
  • envoy.http.injected_credentials.oauth2:通过 OAuth2 Client Credentials Grant 流程动态获取 access token 并注入,定义见 oauth2.proto。

两者的 proto 均标记为package_version_status = ACTIVE(其中 oauth2.proto 在 xds 状态注解中额外标有work_in_progress = true,从 oauth2.proto 可见),说明该能力已进入正式 API 版本管理。

承载注入动作的是 HTTP 过滤器envoy.filters.http.credential_injector,其配置定义在 credential_injector.proto 中。proto 文档明确了两点重要定位:

  1. 面向工作负载认证(workload authentication):被注入凭证所代表的身份,被视为 Envoy 代理后面那个工作负载的身份(典型部署形态是 Envoy 作为 sidecar 与业务进程同舱运行);
  2. 不处理终端用户认证(end user authentication):该过滤器的唯一目的是认证工作负载本身,而不是登录态的最终用户。

相关文档索引:作为 API 导航页,injected_credentials.rst 通过toctree通配符../../extensions/http/injected_credentials/*/v3/*将两类凭证的 proto 文档聚合到同一目录树,即上文两个 proto 文件对应的 API 参考文档。

二、整体架构与调用链

从源码结构看,凭证注入能力由三层构成:

HTTP Filter(credential_injector) │ 在 decodeHeaders 阶段调用 ▼ CredentialInjector 抽象接口(Common::CredentialInjector) │ 按 TypedExtensionConfig 选择实现 ├── GenericCredentialInjector(generic) └── OAuth2ClientCredentialTokenInjector(oauth2) └── TokenProvider(异步拉取/缓存 token)
  • 抽象接口source/extensions/http/injected_credentials/common/credential.h定义了CredentialInjector::inject(RequestHeaderMap& headers, bool overwrite),返回absl::Status表示注入是否成功;
  • 凭证读取source/extensions/http/injected_credentials/common/secret_reader.h中的SDSSecretReader基于ThreadLocalGenericSecretProvider从 SDS 读取 Generic Secret,并把凭证缓存在线程本地槽位中;
  • 过滤器入口source/extensions/filters/http/credential_injector/credential_injector_filter.ccdecodeHeaders阶段调用注入器;注入失败时通过sendLocalReply返回401 Unauthorized(响应体代码为failed_to_inject_credential),否则返回Continue继续转发。

工厂类方面,generic 与 oauth2 的config.cc均实现了NamedCredentialInjectorConfigFactory,并在REGISTER_FACTORY中静态注册(见 generic/config.cc、oauth2/config.cc),因此可以在任意TypedExtensionConfig中通过@type直接引用。

三、Generic 凭证注入:任意凭证的头注入

3.1 配置字段

generic.proto 中Generic消息仅含三个字段:

字段类型必填说明
credentialSdsSecretConfig要注入的凭证,必须是 Generic Secretgeneric_secret类型),可走 SDS 动态下发,也可引用静态 secret
headerstring注入的目标请求头名,留空时默认Authorization,取值须符合 HTTP header name 校验规则(HTTP_HEADER_NAME,允许为空)
header_value_prefixstring注入前的值前缀,用于拼上 scheme(如BearerBasic);不设置则注入原始凭证值

header_value_prefix的语义在 proto 注释中有明确示例:若凭证值为xyz123、前缀为Bearer,最终头值为Bearer xyz123

3.2 配置示例(Basic Auth / Bearer Token)

credential_injector.proto 内嵌了完整可运行的示例。过滤器配置:

overwrite: true credential: name: generic_credential typed_config: "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic credential: name: credential sds_config: path_config_source: path: credential.yaml header: Authorization

配套的 SDS 文件credential.yaml(Basic Auth 场景):

resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: credential generic_secret: secret: inline_string: "Basic base64EncodedUsernamePassword"

Bearer Token 场景只需把inline_string换成"Bearer myToken"即可(此时无需配置header_value_prefix,因为前缀已写在凭证值里)。

注意:header_value_prefix与"把 scheme 写进凭证值"是两种等价做法。前者让凭证与 scheme 分离管理(凭证文件只存令牌本身),更利于凭证轮换时不动 scheme。

3.3 底层实现要点

从 generic_impl.cc 可以看到GenericCredentialInjector::inject的三个关键行为:

  1. 覆盖策略overwrite=false且目标头已存在时,返回AlreadyExistsError,不覆盖已有值(由过滤器层决定放行还是拒绝);
  2. 尾随换行剥离:凭证文件末尾常见的\n/\r不是合法的 HTTP 头值字符,实现会循环剥离尾部 CR/LF 后再注入;
  3. 空凭证兜底:剥离后凭证为空(SDS 尚未就绪或 secret 缺失)时返回NotFoundError,由过滤器按配置决定是否放行。

在 generic/config.cc 中,工厂根据SdsSecretConfig是否携带sds_config走两条路径:有则findOrCreateGenericSecretProvider(SDS 动态下发),无则findStaticGenericSecretProvider(静态 secret);header为空时在工厂内兜底为Authorization

四、OAuth2 凭证注入:动态获取并注入 Access Token

4.1 支持的流程与认证方式

oauth2.proto 中的OAuth2消息目前仅支持 Client Credentials Grantflow_typeoneof 中只有client_credentials分支,且为validate.required),即授权服务器直接为客户端签发令牌、不涉及用户授权码的机器对机器场景。令牌以 Bearer 形式注入Authorization头。

AuthType枚举控制客户端向授权服务器出示client_id/client_secret的方式:

  • BASIC_AUTH = 0:使用 HTTP Basic 认证 scheme 发送(默认推荐);
  • URL_ENCODED_BODY = 1:将凭据放在 URL 编码的请求体中发送,仅当授权服务器不支持 Basic 认证时才应使用

4.2 配置字段总览

字段类型必填/默认说明
token_endpointHttpUri授权服务器上获取 access token 的端点;HttpUri内含cluster(走哪个上游集群)与timeout(请求超时),见 oauth_client.cc 的setTimeout用法
scopesrepeated string请求的 OAuth scope;不填时按默认空 scope 处理(见下)
client_credentials.client_idstring是(min_len 1)Client ID
client_credentials.client_secretSdsSecretConfigClient Secret,同样必须是 Generic Secret
client_credentials.auth_typeAuthType否(默认 BASIC_AUTH)凭据发送方式
token_fetch_retry_intervalDuration否(默认 2s)两次 token 拉取失败重试的间隔,校验规则为不小于 1 秒gte {seconds: 1}
endpoint_paramsrepeated EndpointParameter附加到 token 请求体中的自定义参数(URL 编码后追加)

关于scopes的默认行为,token_provider.cc 中的oauthScopesList显示:scopes 为空时也会注入一个默认空 scope 字符串(DEFAULT_AUTH_SCOPE = ""),最终以空格连接后放入请求体。

4.3 Token 请求与响应处理

oauth_client.cc 展示了 token 请求的完整构造过程:

  • 请求体格式为grant_type=client_credentials&client_id={0}&client_secret={1},有 scope 时追加&scope={2}(L25-L28);
  • client_idclient_secretscope及自定义endpoint_params均通过PercentEncoding::encode(..., ":/=&?")做 URL 编码,防止特殊字符破坏请求体;
  • 请求通过clusterManager().getThreadLocalCluster(uri_.cluster())定位集群并用httpAsyncClient().send(...)异步发送;
  • 成功回调onSuccess要求响应码必须为200,响应体必须是包含access_tokenexpires_in的 JSON(如{"access_token":"...","expires_in":3600}),解析失败或字段缺失一律按失败处理;
  • 若集群不存在或请求流被重置,分别对应NotDispatchedClusterNotFoundStreamReset失败路径。

4.4 Token 缓存、过期与重试机制

token_provider.cc 与 token_provider.h 共同实现了完整的令牌生命周期管理:

  1. 启动即拉取TokenProvider构造时立即调用asyncGetAccessToken()
  2. 线程本地缓存:成功获取的 token 以"Bearer " + access_token形式写入ThreadLocalOauth2ClientCredentialsToken,所有工作线程通过 TLS 槽位读取,避免每次请求都触发网络调用;
  3. 定时刷新onGetAccessTokenSuccess记录token_expiry_time_,并在expires_in / 2时间后通过dispatcher_->createTimer触发预刷新,确保 token 在过期前就被替换;
  4. 失败重试onGetAccessTokenFailure按失败原因分桶统计;BadToken(响应解析失败)不重试,其余原因按token_fetch_retry_interval(默认 2s)重试;token 已过期时还会清空缓存的过期 token,避免上游收到陈旧凭证;
  5. 空 secret 保护asyncGetAccessToken开头检测到 client secret 为空(SDS 未就绪)时不发请求,直接等待重试间隔。

对应的统计指标(见 token_provider.h):token_requestedtoken_fetchedtoken_fetch_failed_on_client_secrettoken_fetch_failed_on_cluster_not_foundtoken_fetch_failed_on_stream_resettoken_fetch_failed_on_bad_tokentoken_fetch_failed_on_bad_response_code,可用于监控令牌拉取的成败与原因分布。

4.5 注入行为

client_credentials_impl.cc 中的OAuth2ClientCredentialTokenInjector::inject逻辑与 Generic 类似:overwrite=falseAuthorization已存在时返回AlreadyExistsError;token 为空(尚未获取成功或已过期被清空)时返回NotFoundError;成功则以setReferenceKey写入Authorization头(值为缓存好的Bearer <token>)。

五、CredentialInjector HTTP 过滤器:装配与行为控制

5.1 过滤器配置字段

credential_injector.proto 中CredentialInjector消息:

字段类型默认值说明
overwriteboolfalse目标头已存在时是否覆盖。为false且头已存在时,注入器返回AlreadyExists,过滤器统计already_exists继续转发(不覆盖原值)
allow_request_without_credentialboolfalse凭证缺失或注入失败时是否仍把请求发给上游。默认情况下返回401 Unauthorized;为true时不带凭证直接放行
credentialTypedExtensionConfig必填凭证注入器本体,@type指向envoy.http.injected_credentials.genericenvoy.http.injected_credentials.oauth2之一

5.2 过滤器行为细节

credential_injector_filter.cc 的实现要点:

  • 过滤器继承PassThroughDecoderFilter,只重写decodeHeaders,对请求体与响应路径零侵入;
  • FilterConfig::injectCredentialabsl::Status分类处理:AlreadyExists→ 记already_exists并放行;其他失败 → 记failed并按allow_request_without_credential决定放行与否;成功 → 记injected
  • 失败且不允许无凭证转发时,sendLocalReply(401 Unauthorized, "Failed to inject credential.", ..., "failed_to_inject_credential")StopIteration,请求被本地拒绝;
  • 过滤器级统计指标共三个:injectedfailedalready_exists(定义见 credential_injector_filter.h)。

5.3 完整装配示例(Basic Auth 场景)

http_filters: - name: envoy.filters.http.credential_injector typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector overwrite: true allow_request_without_credential: false credential: name: generic_credential typed_config: "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic credential: name: credential sds_config: path_config_source: path: /etc/envoy/credential.yaml header: Authorization

在过滤器链中,应把credential_injector放在需要携带凭证的转发路径之前(如router过滤器之前),并在 HTTP 连接管理器中引用该过滤器链。

六、OAuth2 场景的完整配置示例

结合 oauth2.proto 的字段,一个使用 Client Credentials Grant 的典型配置如下:

http_filters: - name: envoy.filters.http.credential_injector typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector overwrite: true allow_request_without_credential: false credential: name: oauth2_credential typed_config: "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.oauth2.v3.OAuth2 token_endpoint: cluster: auth_server_cluster uri: https://auth.example.com/oauth2/token timeout: 3s scopes: - "read:data" - "write:data" client_credentials: client_id: my-workload client_secret: name: oauth2_client_secret sds_config: path_config_source: path: /etc/envoy/client_secret.yaml auth_type: BASIC_AUTH token_fetch_retry_interval: 2s endpoint_params: - name: audience value: internal-api

配套的client_secret.yaml(Generic Secret 形式):

resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: oauth2_client_secret generic_secret: secret: inline_string: "s3cr3t-value"

该配置下,Envoy 会在启动时立即向auth_server_cluster对应的授权服务器发起 Client Credentials Grant 请求,成功后把Bearer <access_token>缓存在线程本地,并在每个请求的Authorization头中注入;token 过期前一半时间自动预刷新,拉取失败则按 2 秒间隔重试(BadToken类解析错误除外)。

七、测试与验证

仓库为两个注入器提供了完整的单元测试与集成测试,可作为行为契约参考:

  • Generic 注入器集成测试:credential_injector_integration_test.cc、credential_injector_upstream_integration_test.cc;
  • OAuth2 相关测试:config_test.cc(配置解析与工厂装配)、token_provider_test.cc(令牌拉取/缓存/重试逻辑)、credential_injector_oauth_integration_test.cc(端到端令牌注入);
  • 过滤器行为测试:filter_test.cc 覆盖了 overwrite、401 拒绝、无凭证放行等分支。

这些测试同时印证了本文所述的关键行为:凭证已存在且不覆盖时放行、注入失败返回 401、SDS 未就绪时进入重试等待等。

八、注意事项与使用限制

  1. 仅 Client Credentials Grant:OAuth2 注入器当前不支持授权码、隐式等流程,只面向机器身份(service-to-service)场景;
  2. 工作负载身份而非用户身份:被注入凭证标识的是代理背后的工作负载,不要把该过滤器当作终端用户认证方案(credential_injector.proto 中有明确声明);
  3. 凭证必须是 Generic SecretSdsSecretConfig引用的 secret 类型必须是generic_secret,且建议优先走 SDS 动态下发以支持无重启轮换;静态 secret 仅按名称引用;
  4. 凭证值合法性:从文件读取的凭证末尾换行会被自动剥离,但凭证值本身不应包含其他非法头字符(换行等);空凭证会被视为注入失败;
  5. 401 语义:默认情况下,SDS 未就绪或令牌获取失败都会导致请求直接以 401 失败,请结合allow_request_without_credential谨慎决定生产环境的降级策略,并利用injected/failed/token_fetch_failed_*等指标监控凭证链路的健康度;
  6. 超时与集群依赖:OAuth2 的token_endpoint.cluster必须指向可用的上游集群,HttpUri.timeout决定 token 请求超时;令牌刷新完全在 Envoy 内部异步进行,不影响业务请求的数据面性能。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询