Envoy AWS 请求签名扩展架构:凭证提供链、SigV4 签名与异步凭证获取深度解析
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
Envoy 通过aws_request_signing与aws_lambda两个扩展,在代理层直接完成对 AWS API 的请求签名,让下游服务无需感知 AWS SDK 即可安全访问 S3、Lambda、SQS 等云资源。本文以 source/extensions/common/aws/README.md 为骨架,结合source/extensions/common/aws/下的源码实现,系统讲解 AWS 公共组件的六大核心机制:凭证提供器(Credentials Provider)、默认凭证提供链、基于元数据服务的异步凭证提供器、AWS 集群管理器、SigV4/SigV4A 签名器,以及支撑签名流程不中断的异步凭证获取订阅通知模型。读完本文,你将理解 Envoy 内部如何像 AWS SDK 一样定位并刷新凭证、如何复用内部集群完成元数据抓取、如何在凭证尚未就绪时暂停上游请求,以及 IAM Roles Anywhere 基于 X509 证书的新式签名如何落地。
一、整体架构概览
AWS 公共组件(Envoy::Extensions::Common::Aws)位于 source/extensions/common/aws,是aws_request_signing与aws_lambda两个扩展共用的底层库。整体架构可分为四层:
- 凭证层(Credentials):
Credentials容器(AccessKeyId / SecretAccessKey / SessionToken)与各类CredentialsProvider,负责从不同来源获取凭证; - 编排层(Chain + Cluster Manager):
CredentialsProviderChain按优先级串联多个提供器,AwsClusterManager统一管理用于抓取元数据的内部集群; - 元数据抓取层(MetadataFetcher):以异步 HTTP 请求方式从 IMDS、ECS/EKS 容器代理或 STS 服务抓取临时凭证;
- 签名层(Signer):基于凭证对 HTTP 请求执行 SigV4 / SigV4A 签名,并暴露“凭证未就绪则暂停”的异步回调接口。
各核心类型的继承关系可从源码直接印证:所有凭证提供器继承自CredentialsProvider(credentials_provider.h),元数据类提供器继承自MetadataCredentialsProviderBase(metadata_credentials_provider_base.h),签名器统一实现Signer接口(signer.h)。
二、凭证提供器(Credential Providers)
2.1 设计思想:与 AWS SDK 对齐
Credential Providers(Envoy::Extensions::Common::Aws::CredentialsProvider)的实现方式与 AWS SDK 高度类似:每个提供器专职从一种特定来源获取凭证,彼此职责单一、可插拔。接口定义见 credentials_provider.h,核心方法有三个:
providerName():返回提供器名称(用于日志与统计);getCredentials():同步返回当前可用凭证;credentialsPending():返回是否仍处于凭证获取中(异步提供器专用)。
Credentials是一个不可变容器,构造时要求 access key 与 secret key 同时非空才认为是有效凭证,并提供hasCredentials()判断方法;所有组件在环境中找不到时返回std::nullopt,且空字符串同样视为未找到。
2.2 静态凭证提供器
最简单的两类是静态来源提供器:
EnvironmentCredentialsProvider:从环境变量AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKEN读取凭证;ConfigCredentialsProvider:从 Envoy 静态配置内嵌的凭证读取(对应inline_credential配置)。
这两类不需要任何网络交互,因此也不参与异步刷新机制,是“同步即得”的最快路径。
2.3 元数据类提供器
其余提供器均属于元数据类,需要向 AWS 的元数据/STS 端点发起 HTTP 请求:
| 提供器 | 凭证来源 | 集群类型 |
|---|---|---|
InstanceProfileCredentialsProvider | EC2 实例元数据服务 IMDS(169.254.169.254) | STATIC |
ContainerCredentialsProvider | ECS/EKS 容器代理(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI/AWS_CONTAINER_CREDENTIALS_FULL_URI) | STATIC |
WebIdentityCredentialsProvider | STS AssumeRoleWithWebIdentity(token 来自文件或环境) | LOGICAL_DNS |
AssumeRoleCredentialsProvider | STS AssumeRole | LOGICAL_DNS |
IAMRolesAnywhereCredentialsProvider | IAM Roles Anywhere(基于 X509 证书) | LOGICAL_DNS |
CredentialsFileCredentialsProvider | 本地~/.aws/credentials文件 | 静态 |
全部实现位于 credential_providers 目录。
2.4 刷新节奏与统计
元数据提供器在 metadata_credentials_provider_base.h 中统一定义了统计项:
credential_refreshes_performed(Counter):执行刷新次数;credential_refreshes_failed(Counter):刷新失败次数;credential_refreshes_succeeded(Counter):刷新成功次数;metadata_refresh_state(Gauge):当前刷新状态。
凭证缓存的默认刷新策略定义在 credentials_provider.h:
REFRESH_INTERVAL = 1h:正常状态下凭证的缓存刷新周期;REFRESH_GRACE_PERIOD = 60s:到期前的宽限期,避免临界时刻使用过期凭证;MAX_CACHE_JITTER = 30s:最大刷新抖动,用于打散大规模实例同时刷新的峰值。
三、凭证提供链(Credential Provider Chain)
3.1 链式降级模型
CredentialsProviderChain(credentials_provider.h)是一个有序的凭证提供器列表。核心逻辑:
add(provider)追加提供器;chainGetCredentials()从头到尾遍历,返回第一个能给出凭证的提供器的结果;- 一旦前序提供器通过
getCredentials()返回了凭证,后续提供器不再被检查(这与 AWS SDK 的AWSCredentialsProviderChain语义一致,源码注释也明确引用了 aws-sdk-cpp 的参考实现)。
因此,链的顺序即凭证解析的优先级顺序。
3.2 默认凭证提供链
通过Extensions::Common::Aws::CommonCredentialsProviderChain::defaultCredentialsProviderChain可一键创建默认链,其定义在 credential_provider_chains.cc。README 明确给出的链顺序如下:
EnvironmentCredentialsProvider(环境变量)CredentialsFileCredentialsProvider(本地凭证文件)IAMRolesAnywhereCredentialsProvider(Roles Anywhere)WebIdentityCredentialsProvider(Web Identity / STS)ContainerCredentialsProvider(ECS/EKS 容器代理)InstanceProfileCredentialsProvider(EC2 实例元数据)
从 credential_provider_chains.cc 的实现可以看到默认链的构建细节:无自定义配置时,代码依次构造environment、credentials_file、container、instance_profile、assume_role_with_web_identity五类提供器(此时 Roles Anywhere 仅在用户显式提供其配置时才被合并进链);若用户提供了部分提供器的自定义配置(如 Web Identity 的 token 文件路径、凭证文件的 profile 等),则通过MergeFrom合并覆盖。
该顺序体现了“越靠近本地、越廉价的方式越优先”:环境变量与文件解析零网络开销,其次是容器/实例元数据这类内网端点,最后才是需要额外网络跳转的 STS 类服务。
3.3 自定义凭证提供链
除了默认链,credential_provider_chains.h 还提供了customCredentialsProviderChain:完全由用户在配置中指定提供器集合与顺序。其校验逻辑在 credential_provider_chains.cc:自定义链至少需要包含一个凭证提供器,否则返回InvalidArgumentError。同时,源码对 AssumeRole 提供器做了一层防护——若 AssumeRole 内部再嵌套 AssumeRole,会记录 warning 并忽略内层配置(credential_provider_chains.cc),避免无限递归。
3.4 默认环境变量约定
Web Identity 与 Container 提供器在无显式配置时,依赖标准 AWS 环境变量(credential_provider_chains.cc):
AWS_WEB_IDENTITY_TOKEN_FILE:Web Identity token 文件路径;AWS_ROLE_ARN:目标角色 ARN;AWS_ROLE_SESSION_NAME:会话名(未设置时源码会用纳秒级时间戳生成,见 credential_provider_chains.h);AWS_CONTAINER_CREDENTIALS_RELATIVE_URI:容器凭证相对 URI(优先使用);AWS_CONTAINER_CREDENTIALS_FULL_URI:容器凭证完整 URI(备选);AWS_CONTAINER_AUTHORIZATION_TOKEN:访问容器凭证端点时的授权 token;AWS_EC2_METADATA_DISABLED:设为"true"时跳过 InstanceProfile 提供器(credential_provider_chains.cc)。
一个值得注意的细节:Web Identity 的 token 若来自文件,源码会自动为其配置watched_directory(credential_provider_chains.cc),从而在 Kubernetes 等场景下 token 文件被自动轮换时能实时感知并加载新 token。
四、元数据凭证提供器(Metadata Credential Providers)
4.1 异步 HTTP 抓取模型
元数据凭证提供器(Envoy::Extensions::Common::Aws::MetadataCredentialsProviderBase)是凭证提供器的异步子类:它通过异步 HTTP 请求从远端抓取凭证。为此它依赖两样基础设施:
- 上游集群:由 AWS 集群管理器(AWS Cluster Manager)创建,用于发起 HTTP 请求;
- MetadataFetcher:负责单次元数据抓取,其接口定义在 metadata_fetcher.h,设计上“一个实例同一时刻只抓取一次”,实现思路与 JwksFetcher 类似。
抓取失败被归类为三种原因(MetadataReceiver::Failure):Network(网络错误)、InvalidMetadata(解析失败)、MissingConfig(缺少配置)。
4.2 FirstRefresh 退避策略
元数据提供器的首次刷新采用指数退避(metadata_fetcher.h):
- 初始状态为
FirstRefresh,首次刷新失败后以2 秒为起点重试; - 每次翻倍,最大退避到 30 秒,直到首次成功;
- 首次成功后进入
Ready状态,之后按正常的缓存时长(1 小时 + 宽限期 + 抖动)周期性刷新。
4.3 线程本地缓存与单例
凭证在多个工作线程间共享,因此MetadataCredentialsProviderBase使用 ThreadLocal 槽位保存每个线程的凭证副本(metadata_credentials_provider_base.h),并通过setCredentialsToAllThreads在主线程写入后广播到各线程。
由于同一主机上 IMDS 只有一份实例,InstanceProfileCredentialsProvider被注册为单例(SINGLETON_MANAGER_REGISTRATION(instance_profile_credentials_provider),见 credential_provider_chains.cc);ContainerCredentialsProvider同样如此。这样即使存在多个凭证提供链(例如 per-route 配置下每个路由都有一条链),也不会对 IMDS 产生多轮重复抓取。
五、AWS 集群管理器(AWS Cluster Manager)
5.1 动机:集群去重与上线通知
当多个扩展(aws_request_signing、aws_lambda)同时配置时,若各自创建指向同一目标的集群,会白白浪费资源。AWS Cluster Manager(AwsClusterManagerImpl,aws_cluster_manager.h)解决两个问题:
- 集群去重:多个元数据凭证提供器如果目标地址相同,共享同一个内部集群;只有不存在时才会新建;
- 上线订阅通知:提供器订阅“集群已就绪”事件,集群 online 后才开始凭证刷新周期,避免在集群尚未创建完成时盲目发请求。
5.2 关键接口
addManagedCluster(cluster_name, cluster_type, uri):申请集群。存在同名同目标集群则复用,否则创建;addManagedClusterUpdateCallbacks(cluster_name, cb):注册集群更新回调,返回 RAII 句柄(AwsManagedClusterUpdateCallbacksHandlePtr),销毁句柄即自动退订(aws_cluster_manager.h);getUriFromClusterName(cluster_name):按名取回 URI。
从 credential_provider_chains.cc 可看到具体用法:STS 类提供器以LOGICAL_DNS类型创建指向sts.<region>.amazonaws.com:443的集群;Container / InstanceProfile 则以STATIC类型创建指向元数据端点的集群。
5.3 集群按目标去重的具体表现
aws_cluster_manager.h 注释明确了各类提供器的去重粒度:
- InstanceProfile:只需一个指向 IMDS 的集群,任意数量的
aws_request_signing扩展实例共用; - Container:只需一个指向容器代理的集群,任意扩展实例共用;
- WebIdentity / IAM Roles Anywhere:按 region(Roles Anywhere 按 trust anchor 推导出的主机)维护每 region 一个集群,同一 region 下不同 role ARN 或 session name 的多个提供器共享集群,并各自收到集群就绪通知。
5.4 固定单例(Pinned Singleton)
AwsClusterManagerImpl以固定单例(pinned singleton)方式注册(SINGLETON_MANAGER_REGISTRATION(aws_cluster_manager),见 credential_provider_chains.cc,并通过singletonManager().getTyped(..., true)固定)。这意味着它一旦实例化,将存活到服务器进程结束。由此带来的收益是:即使aws_request_signing或aws_lambda扩展被替换/重建,指向 IMDS 或 STS 的集群也不会被重新创建,避免了重建集群带来的额外延迟。源码中addManagedCluster在集群管理器初始化阶段无法实时创建时,会把集群请求排队(createQueuedClusters),待集群管理器就绪后再补建,保证不丢失任何提供器的请求。
六、请求签名(Signing)
6.1 SigV4 与 SigV4A
AWS 公共组件同时支持AWS SigV4与AWS SigV4A两种签名协议。签名实现位于 signers 目录:
SigV4SignerImpl:标准 SigV4(x-amz-date、x-amz-content-sha256等头部);SigV4ASignerImpl:SigV4A(基于 ECDSA、支持多 region 签名的变体,头部使用x-amz-region-set);IAMRolesAnywhereSigV4Signer:IAM Roles Anywhere 专用签名器。
签名器的行为验证直接复用 aws-c-auth 的官方签名测试语料(aws-signing-test-suite),确保 Envoy 生成的签名与 AWS SDK 输出完全一致。
6.2 签名流程与头部
SignerBaseImpl(signer_base_impl.h)实现了标准 SigV4 过程,并定义了签名相关的头部与查询参数常量:
- 签名头部:
x-amz-content-sha256、x-amz-date、x-amz-security-token; - 默认排除签名头部:
x-forwarded-for、x-forwarded-proto、x-amzn-trace-id(避免代理链路附加头污染签名,可通过配置的 exclude/include matcher 扩展); - 预签名查询参数(query string 模式):
X-Amz-Algorithm、X-Amz-Credential、X-Amz-Date、X-Amz-Region-Set、X-Amz-Security-Token、X-Amz-Signature、X-Amz-SignedHeaders、X-Amz-Expires(默认过期时间 5 秒); - 空 body 的内容哈希使用空字符串的 SHA-256 常量
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855; - 也支持
UNSIGNED-PAYLOAD字面量作为 content hash。
Signer接口(signer.h)提供四种签名入口:
sign(message, sign_body):对完整请求签名,可选择是否把 body 纳入签名(纳入时 body 必须被完整缓冲);signEmptyPayload(headers):按空 body 签名;signUnsignedPayload(headers):以UNSIGNED-PAYLOAD签名;sign(headers, content_hash):调用方预计算好 body 的 SHA-256 十六进制值后签名。
当凭证尚在异步获取中时,以上签名方法返回absl::NotFoundError,调用方应通过addCallbackIfCredentialsPending等待凭证就绪。
6.3 IAM Roles Anywhere:基于 X509 的签名变体
IAM Roles Anywhere 引入了一种新的 SigV4 签名变体:基于 X509 证书身份而不是传统的 access key。其实现为IAMRolesAnywhereSignerBaseImpl,是使用X509Credential凭证类型的 SigV4 特化实现:
X509Credentials(credentials_provider.h)承载证书(base64 DER)、私钥 PEM、证书链、证书序列号、过期时间以及签名算法(RSA / ECDSA);IAMRolesAnywhereX509CredentialsProvider从配置加载证书与私钥并完成初始化校验(credential_provider_chains.cc),初始化失败时该提供器会被整体禁用并记录 error 日志;- 随后由
IAMRolesAnywhereSigV4Signer使用 X509 私钥对向 Roles Anywhere 服务(rolesanywhere服务名)发起的调用签名,换取临时 AK/SK 凭证。
从源码看,该提供器的集群名由 trust anchor ARN 推导出的主机名中的.替换为_生成(credential_provider_chains.cc),从而保证同一 trust anchor 复用同一集群。
七、异步凭证获取(Asynchronous Credential Retrieval)
7.1 为什么需要异步
aws_lambda与aws_request_signing扩展都支持异步凭证获取:当凭证尚在抓取时,上游请求会被暂停(paused),待凭证返回后再继续。这是冷启动场景下的关键能力——例如刚启动的 EC2/ECS 实例首次请求时 IMDS 尚未返回临时凭证,若直接签名会失败。
7.2 订阅/通知模型
整套机制建立在订阅/通知(subscription/notification)模型之上,核心流程如下:
- 订阅阶段:凭证提供链在初始化时,对链内每个元数据提供器调用
subscribeToCredentialUpdates(见 credential_provider_chains.cc 的setupSubscriptions); - 通知阶段:元数据提供器成功(或失败)获取凭证后,通过
onCredentialUpdate回调通知已订阅的凭证提供链(CredentialSubscriberCallbacks接口定义在 credentials_provider.h)。订阅句柄使用 RAII +weak_ptr管理,保证提供器(可能是单例)比链存活更久时也不会悬挂; - 等待阶段:签名器在签名请求时调用凭证链的
addCallbackIfCredentialsPending(cb):若凭证当前不可用,则把回调存入队列并返回true;当链收到onCredentialUpdate通知后,会依次触发所有等待回调(对应 credentials_provider.h 中credential_pending_callbacks_列表); - 恢复阶段:回调触发后,暂停的请求继续执行签名并放行。
7.3 各扩展的接入方式
每个使用签名异步能力的扩展,都通过addCallbackIfCredentialsPending在凭证未就绪时暂停自身执行流。Signer接口的注释(signer.h)明确约定:返回true表示凭证 pending 且回调已入队,返回false表示凭证已就绪可以立即签名。这一约定让上层扩展无需关心凭证细节,只需遵循“先检查 pending、再签名”的两步式流程。
八、从配置到运行:一次完整的签名请求旅程
结合上述机制,可以串起一次典型的签名请求完整链路:
- 扩展(如
aws_request_signing)启动时,调用defaultCredentialsProviderChain或customCredentialsProviderChain构建凭证提供链(credential_provider_chains.cc); - 链构造期间,元数据类提供器通过
AwsClusterManager申请内部集群并注册上线回调;集群管理器按目标去重,必要时创建 STATIC/LOGICAL_DNS 集群(aws_cluster_manager.h); - 集群上线后通知提供器,提供器通过
MetadataFetcher异步抓取凭证,首刷采用 2 秒起、最大 30 秒的指数退避; - 凭证抓取期间,签名器调用
addCallbackIfCredentialsPending挂起等待;凭证就绪后经setCredentialsToAllThreads广播到所有线程并通知订阅链(metadata_credentials_provider_base.h); - 凭证就绪后,签名器按 SigV4/SigV4A 计算规范请求、生成
Authorization头或预签名查询参数并放行请求; - 之后按 1 小时刷新周期(含 60 秒宽限期与最多 30 秒抖动)在后台静默续期,保证代理长期运行不掉线。
九、进一步探索指引
- 组件根目录与架构说明:source/extensions/common/aws/README.md
- 凭证提供链的实现与默认链构建:credential_provider_chains.cc、credential_provider_chains.h
- 各类凭证提供器:credential_providers
- 元数据提供器基类与抓取器:metadata_credentials_provider_base.h、metadata_fetcher.h
- 集群管理器:aws_cluster_manager.h、aws_cluster_manager.cc
- 签名器接口与实现:signer.h、signer_base_impl.h、signers
- 凭证与订阅模型定义:credentials_provider.h
结语
Envoy 的 AWS 公共组件用一套与 AWS SDK 对齐、但面向代理场景重构的设计,解决了代理层签名的三大难点:凭证来源多样化(环境、文件、容器、实例元数据、STS、Roles Anywhere 六类来源通过链式降级统一)、凭证获取的异步化(订阅通知 + 回调暂停,保证请求在凭证就绪前不失败)、集群资源的去重复用(固定单例集群管理器按目标共享集群并广播就绪事件)。理解这四层机制,是深入定制aws_request_signing与aws_lambda扩展、排查“请求签名失败/凭证过期”类问题的基础。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考