ARTICLE DETAIL

资讯详情

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

Envoy AWS 凭证文件提供者数据竞争修复:CredentialsFileCredentialsProvider 的并发安全演进

Envoy AWS 凭证文件提供者数据竞争修复:CredentialsFileCredentialsProvider 的并发安全演进 Envoy AWS 凭证文件提供者数据竞争修复CredentialsFileCredentialsProvider 的并发安全演进【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读本文围绕 Envoy 发布说明中关于AWS 凭证文件提供者CredentialsFileCredentialsProvider数据竞争data race修复的变更深入解析该提供者在使用watched_directory时存在的并发隐患、根因分析以及 mutex 保护方案。通过结合 Envoy 仓库中的 proto 配置定义、核心源码实现与单元测试帮助读者理解 Envoy AWS 扩展中凭证缓存的刷新机制掌握配置credentials_file_provider时需要注意的并发安全细节并能定位到对应源码进行深入研读。变更背景一条 bug_fix 发布说明在 changelogs/current/bug_fixes/aws__fixed-data-race-in-credentials-file-provider.rst 中记录了本次修复的核心内容修复了 AWS 凭证文件提供者CredentialsFileCredentialsProvider中的一处数据竞争该问题在配置了watched_directory时可能破坏堆内存并导致 Envoy 崩溃。在配置了 watched directory 的情况下缓存的凭证会在每次getCredentials()调用时被刷新而并发的工作线程会在没有同步的情况下写入缓存的Credentials和last_updated_成员。现在缓存状态由互斥锁mutex保护。这则发布说明揭示了三个关键技术点触发条件必须配置了watched_directory监听目录危害后果并发写入未加同步可能破坏堆内存corrupt the heap进而导致 Envoy 进程崩溃修复方案为缓存的凭证状态引入 mutex 保护。下文将逐层剖析该问题在源码中的具体体现。配置层CredentialsFileCredentialProvider 的 proto 定义CredentialsFileCredentialsProvider对应的配置消息定义位于 api/envoy/extensions/common/aws/v3/credential_provider.protomessage CredentialsFileCredentialProvider { // When using this data source, if a watched_directory is provided, the credential file // will be re-read when a file move is detected. // See watched_directory field in DataSource for more information. config.core.v3.DataSource credentials_data_source 1 [(udpa.annotations.sensitive) true]; // The profile within the credentials_file data source. If not provided, // the default profile will be used. string profile 2; }关键字段说明字段类型说明credentials_data_sourceconfig.core.v3.DataSource指定凭证文件的来源。支持文件路径、内联字符串、watched_directory等。该字段被标记为sensitive true表明其中可能包含敏感信息当配置了watched_directory时检测到文件被移动move后会重新读取凭证文件profilestring指定要读取的 AWS 凭证 profile 名称未提供时使用默认 profile即[default]或AWS_PROFILE环境变量指定的 profile该消息作为 CredentialProvider 中的credentials_file_provider字段使用CredentialsFileCredentialProvider credentials_file_provider 3;允许用户在 AWS 扩展的凭证提供者链中启用基于凭证文件的凭证获取。值得强调的是 proto 注释中提到的watched_directory语义当配置了 watched directory 时凭证文件会在检测到文件 move 事件时被重新读取。这一“热更新”能力正是本次数据竞争问题的源头。实现层needsRefresh 与 watched_directory 的行为差异构造阶段如何感知 watched_directory在 source/extensions/common/aws/credential_providers/credentials_file_credentials_provider.cc 的构造函数中提供者会创建数据源提供者并记录是否配置了 watched directoryif (credential_file_config.has_credentials_data_source()) { auto provider_or_error_ Config::DataSource::DataSourceProviderstd::string::create( credential_file_config.credentials_data_source(), context.mainThreadDispatcher(), context.threadLocal(), context.api(), false, [](absl::string_view data) { return std::make_sharedstd::string(data); }, 4096); if (provider_or_error_.ok()) { credential_file_data_source_provider_ std::move(provider_or_error_.value()); if (credential_file_config.credentials_data_source().has_watched_directory()) { has_watched_directory_ true; } } else { ENVOY_LOG(info, Invalid credential file data source); credential_file_data_source_provider_.reset(); } }从源码结构可以看到DataSourceProviderstd::string负责承载凭证文件的内容数据源可能来自文件、内联字符串或 watched directory而has_watched_directory_这个布尔成员专门记录是否启用了目录监听。刷新决策needsRefresh 的分支逻辑CredentialsFileCredentialsProvider继承自CachedCredentialsProviderBase其刷新时机由needsRefresh()决定credentials_file_credentials_provider.ccbool CredentialsFileCredentialsProvider::needsRefresh() { return has_watched_directory_ ? true : context_.api().timeSource().systemTime() - last_updated_ REFRESH_INTERVAL; }这里存在两种截然不同的刷新策略未配置 watched_directory默认基于时间间隔刷新只有当距离上次刷新超过REFRESH_INTERVAL时才重新读取凭证文件。REFRESH_INTERVAL在 source/extensions/common/aws/credentials_provider.h 中定义为constexpr std::chrono::hours REFRESH_INTERVAL{1};即默认 1 小时。这是一种低频、有节流的刷新机制配置了 watched_directoryneedsRefresh()无条件返回 true意味着每次调用getCredentials()都会触发一次刷新重新读取凭证文件内容。这正是发布说明中所述“With a watched directory the cached credentials were refreshed on everygetCredentials()call”的源码依据。选择“每次调用都刷新”的设计初衷是保证目录中凭证文件被替换move后能立即生效但代价是把refresh()推到了高频热路径上。根因分析为什么会产生数据竞争线程模型worker 线程并发调用 getCredentialsEnvoy 使用多线程事件循环架构每个 worker 线程独立运行。当多个 worker 线程上的请求同时需要 AWS 凭证时它们会并发调用凭证提供者的getCredentials()。修复前的基类 source/extensions/common/aws/cached_credentials_provider_base.h 实现大致如下无锁版本Credentials getCredentials() override { refreshIfNeeded(); return cached_credentials_; }而受保护成员Thread::MutexBasicLockable mu_; SystemTime last_updated_ ABSL_GUARDED_BY(mu_); Credentials cached_credentials_ ABSL_GUARDED_BY(mu_);竞态场景推演结合needsRefresh()的无条件返回 true 行为可以推断出如下竞态序列线程 A 调用getCredentials()判定needsRefresh() true进入refresh()线程 B 同时调用getCredentials()同样判定需要刷新也进入refresh()两个线程并发执行 extractCredentials()分别对cached_credentials_赋值Credentials(access_key_id, secret_access_key, session_token)线程 A、B 同时写入last_updated_ context_.api().timeSource().systemTime()。由于这些成员是共享状态且Credentials内部包含std::string成员两个线程对同一std::string进行并发赋值属于未定义行为UB字符串内部的堆缓冲区可能被并发释放或重新分配从而破坏堆元数据corrupt the heap最终导致 Envoy 崩溃或内存损坏。这与发布说明中“could corrupt the heap and crash Envoy”的描述完全吻合。值得说明的是在没有 watched directory 的默认路径下REFRESH_INTERVAL为 1 小时refresh()极少被触发因此竞态窗口很小而 watched directory 模式下每次调用都刷新把竞态概率提升到几乎必然发生才使得该问题被明确发现并修复。修复后的实现mutex 全程保护修复后的基类实现当前仓库源码Credentials getCredentials() override { // The cached credentials are read by worker threads and refreshed in place by both // refresh() and needsRefresh(). Guard the whole read-refresh-read sequence so concurrent // getCredentials() calls cannot race on cached_credentials_ or last_updated_. Thread::LockGuard guard(mu_); refreshIfNeeded(); return cached_credentials_; }修复要点互斥锁mu_新增Thread::MutexBasicLockable mu_作为缓存状态的守卫加锁范围getCredentials()在进入时获取LockGuard将判定是否需要刷新 → 刷新 → 读取缓存的完整 read-refresh-read 序列置于临界区内直至返回缓存副本才释放线程安全注解last_updated_与cached_credentials_均标注ABSL_GUARDED_BY(mu_)needsRefresh()、refresh()、extractCredentials()均声明ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_)见 credentials_file_credentials_provider.h借助 ABSL 线程安全静态分析工具在编译期强制约束调用必须持锁。这样任意时刻只有一个线程能够读写cached_credentials_与last_updated_彻底消除了并发写导致的堆损坏风险。同时return cached_credentials_;返回的是Credentials的副本临界区释放后调用方持有的副本与共享缓存互不影响。相关行为细节refresh 中的异常路径处理除了并发安全修复refresh()中还有两个值得注意的行为细节credentials_file_credentials_provider.cc数据源优先如果配置了credentials_data_source优先从数据源读取凭证内容否则回退到默认 AWS 凭证文件路径由Utility::getCredentialFilePath()返回遵循AWS_SHARED_CREDENTIALS_FILE环境变量约定读取失败的节流若读取凭证文件失败会立即更新last_updated_后返回。注释明确说明这样做的目的是即使本次未能成功提取凭证在REFRESH_INTERVAL内也不会反复尝试读取避免每个请求都去读一个不存在的文件造成性能损耗敏感信息脱敏日志中secret_access_key与session_token在非空时打印为*****避免敏感凭证泄漏到日志。测试验证单元测试如何覆盖该修复仓库在 test/extensions/common/aws/credential_providers/credentials_file_credentials_provider_test.cc 中对该提供者进行了系统测试测试夹具覆盖了多种典型场景多 profile 解析测试文件中包含[default]、[profile1]含前导空格、值中含、[profile2]缺少 secret、[profile3]secret 为空、[profile4]两侧带空格等边界情况自定义 profile 优先于环境变量CustomProfileFromConfigShouldBeHonored环境变量AWS_SHARED_CREDENTIALS_FILE与AWS_PROFILE的读写TestEnvironment::setEnvVar/unsetEnvVar。测试夹具使用Event::SimulatedTimeSystem模拟时间推进配合NiceMockServer::Configuration::MockServerFactoryContext隔离外部依赖可精确控制刷新时机来验证缓存行为。有兴趣的读者可以通过以下命令运行该测试单元bazel test //test/extensions/common/aws/credential_providers:credentials_file_credentials_provider_test配置实践建议结合 proto 定义、源码行为与发布说明在使用CredentialsFileCredentialProvider时有以下实践要点默认不配置watched_directory时凭证以 1 小时REFRESH_INTERVAL为间隔刷新适合凭证文件不常变动的静态场景需要凭证热更新时可通过credentials_data_source配置watched_directory实现文件 move 即重读但应了解其“每次getCredentials()均刷新”的行为特性修复后并发安全已有保障但高频文件读取仍有 I/O 开销需按实际流量评估profile 选择显式设置profile字段可避免依赖运行环境中的AWS_PROFILE变量提升配置的可移植性与确定性敏感信息处理credentials_data_source已标记为 sensitive应避免将凭证明文写入易被读取的配置源。总结本次 bug_fix 修复了CredentialsFileCredentialsProvider在watched_directory模式下由于高频刷新与多线程并发导致的缓存状态竞争问题。根因在于needsRefresh()在 watched directory 模式下无条件返回 true使得cached_credentials_与last_updated_被多个 worker 线程无同步并发写入修复通过在CachedCredentialsProviderBase中引入Thread::MutexBasicLockable mu_并对 read-refresh-read 全流程加锁配合 ABSL 线程安全注解实现编译期约束从根上消除了堆内存损坏的隐患。理解这一修复的来龙去脉有助于 Envoy 使用者在配置 AWS 凭证提供者时做出更稳健的取舍也为阅读 Envoy 并发安全代码提供了典型范例。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表