Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
RPC 故障排查阅读约 13 分钟

RPC API 密钥轮换:零停机凭证生命周期管理

一套机制层面的操作手册,指导如何在不丢弃任何请求的情况下轮换 RPC API 密钥、JWT 和项目 ID。

TL;DR

RPC 凭证是嵌入在端点 URL 或 Authorization 头中的 API 密钥、项目 ID 或 JWT,它是 RPC 安全事件最常见的原因,因为它会通过日志、打包文件和 CI 输出泄露。零停机轮换采用双密钥重叠:在密钥 A 仍在服务时配置密钥 B,部署从单一密钥存储间接点读取密钥的代码,验证 B 正在接收流量,然后在排空窗口结束后才撤销 A。先轮换 A 再部署 B 会导致中断;先部署 B 再轮换 A 则不会。本指南涵盖威胁模型、原子交换模式、故障转移协调、泄露检测、事件响应,以及一个可运行的 Node.js 示例,用于记录每个请求由哪个密钥提供服务。

RPC 凭证究竟是什么以及它的形态

RPC 凭证是授权你的应用程序访问提供商端点的秘密。它不是端点主机名;它是提供商映射到你的账户、配额和计费的令牌、密钥或项目标识符。其形态因提供商而异,并有相应文档说明,但主要有四种形式:URL 路径或查询字符串中的密钥、Authorization: Bearer 头、带有效期和作用域的 JWT,以及 IP 白名单密钥。

URL 嵌入形式最容易暴露,因为凭证成为每个请求行的一部分。它会出现在反向代理访问日志、浏览器历史记录、HTTP Referer 头、错误追踪器和支持截图里。基于头的认证将秘密排除在 URL 之外,这正是 OAuth 2.0 Bearer Token Usage 规范(RFC 6750) 定义 Bearer 方案的目的:令牌在 Authorization 头中传输,而不是在请求目标中。

JWT 增加了结构。RFC 7519 定义了 JSON Web Token 格式,签发 JWT 的提供商通常会附加 exp 声明和 scope 或 project 声明。这为你提供了两个额外的生命周期杠杆:令牌自行过期,作用域限制了泄露令牌能做什么。并非每个提供商都提供作用域或过期时间,因此应将这些视为需要验证的能力,而非假设。

  • URL 路径或查询密钥:使用最简单,但最难保密。
  • Authorization: Bearer 头:将秘密排除在日志和 Referer 之外。
  • JWT:增加 exp 和 scope 声明,支持短期凭证。
  • IP 白名单密钥:将凭证绑定到网络来源,这会在没有固定 IP 的情况下破坏无服务器出口。

威胁模型:为什么未轮换的密钥是最常见的 RPC 事件

客户端打包文件或公共仓库中的密钥实际上就是公开的。一旦它被提交、推送或发送到浏览器,你就必须假设对手已经拥有它。OWASP API 安全 Top 10 将 API2: Broken Authentication 列为顶级风险,而凭证泄露是进入该类别的一条直接路径,因为攻击者不需要破解认证;他们只需重用有效凭证。

泄露很少是单一的戏剧性事件。密钥会通过打印环境变量的 CI 日志、事件通话期间的屏幕共享、捕获完整请求 URL 的错误追踪器以及记录上游请求的第三方代理泄露。每一个都是你的凭证在你无法控制的系统中的副本。

滥用通常是计算资源,而非数据窃取。泄露的密钥被用来针对你的配额运行查询,这会增加你的账单,并可能耗尽生产流量所依赖的容量。更糟的情况下,它会成为立足点:攻击者可以读取你项目的指标,观察你的流量模式,并安排进一步的滥用以避免触发告警阈值。

  • 假设打包文件、仓库或日志中的任何密钥都已泄露。
  • CI 日志、屏幕共享、错误追踪器和代理是常见的泄露渠道。
  • 主要滥用是配额消耗和账单膨胀,不一定是数据外泄。
  • 泄露的密钥可能暴露项目指标和流量模式。

双密钥重叠:唯一能避免中断的轮换模式

零停机轮换是一个顺序问题,而不是工具问题。安全的顺序是:在密钥 A 仍在服务时配置密钥 B,部署从密钥存储读取 B 的代码或配置,验证 B 正在接收流量,然后在排空窗口结束后才撤销 A。不安全的顺序是先轮换 A,再部署 B,因为在这两步之间,每个请求都使用提供商已经失效的凭证进行认证。

排空窗口是两个密钥都有效且你观察流量从 A 迁移到 B 的间隔。其长度取决于你的部署拓扑:单个服务可能在几分钟内排空,而具有长连接、缓存或边缘节点的集群可能需要更长时间。你不能从博客文章中挑选窗口;你要针对自己的端点进行测量。

此模式与 RPC 节点监控和故障转移 中描述的故障转移设计相结合。轮换是凭证变更,但它依赖于你已经在端点之间移动时使用的相同健康检查和流量转移机制。

  • 配置 B,部署 B,验证 B,然后撤销 A。
  • 在确认 B 正在服务流量之前,绝不要撤销 A。
  • 排空窗口是测量出来的,不是假设的。
  • 轮换应复用你现有的故障转移和健康检查工具。

通过单一间接点实现原子交换

只有当凭证从一个地方读取时,轮换才只是配置更改。如果密钥是分散在各个服务中的字符串字面量,轮换就变成了每个服务都需要部署的代码更改,而遗漏一个的概率会随着每个文件而上升。解决方案是单一间接点:在进程启动时从密钥管理器加载的环境变量,或从密钥存储进行运行时查找。

间接点应该是唯一知道凭证名称的代码。其他所有内容都请求“RPC 凭证”并接收存储当前持有的任何内容。这样,轮换密钥就是对存储的写入加上重启或重新加载,而不是在整个仓库中进行搜索替换。

将间接点与 CI 中的密钥扫描器配对,这样字面量根本不会到达仓库。扫描器是后备措施,而不是主要控制;主要控制是开发人员永远没有理由将密钥粘贴到源代码中。

  • 一个间接点:来自密钥管理器的环境变量,或运行时密钥存储查找。
  • 源代码、配置文件或容器镜像中不得有凭证字面量。
  • 在 CI 中运行密钥扫描器作为后备措施。
  • 按环境使用不同密钥,这样预发布环境的泄露不会影响生产环境。

将轮换与端点池和故障转移协调

密钥属于端点。如果你运行端点池以实现冗余,如 多区域 RPC 故障转移路由 中所述,轮换必须按端点协调:轮换一个端点的密钥,验证它,然后转到下一个。天真的全局同时交换所有端点可能会触发速率限制或提供商的每密钥并发限制,因为你所有的流量会短暂集中在新凭证上。

安全的节奏是顺序进行,并在步骤之间进行验证。轮换端点一,确认其健康检查通过且其流量由新密钥提供服务,然后轮换端点二。如果某一步失败,你仍然有其余端点使用旧密钥,这可以在你调查时保持服务运行。

这也是 公共 RPC 端点与专用端点 之间区别的重要之处。公共端点可能根本不签发按项目的凭证,因此轮换问题特定于专用或经过认证的端点。在计划轮换之前,确认你的池中哪些端点实际携带凭证。

  • 一次轮换一个端点,并在步骤之间进行验证。
  • 全局同时交换可能会集中流量并触发限制。
  • 在新密钥得到验证之前,至少保持一个端点使用旧密钥。
  • 在计划之前,确认你的池中哪些端点经过认证。

一个可运行的 Node.js 示例,具有可观察的密钥选择

下面的示例加载两个密钥,优先使用新密钥,在重叠窗口期间回退到旧密钥,并记录每个请求由哪个密钥提供服务。日志行是关键:它使排空过程可观察,因此你可以在撤销 A 之前观察流量从 A 迁移到 B。

代码从环境变量读取两个密钥,这就是单一间接点。在生产环境中,你会在进程启动时从密钥管理器加载这些变量。回退逻辑故意保持简单:尝试新密钥,如果提供商以认证错误拒绝它,则使用旧密钥重试一次。如果新密钥配置错误,正是这次重试保持了服务运行。

不要盲目复制重试逻辑。一些提供商将失败的认证尝试计入你的配额或触发锁定,因此在生产环境中启用回退之前,请根据提供商的文档验证其行为。

const NEW_KEY = process.env.RPC_KEY_NEW;
const OLD_KEY = process.env.RPC_KEY_OLD;
const ENDPOINT = process.env.RPC_ENDPOINT; // e.g. https://rpc.example.com/<key>

function urlFor(key) {
  return ENDPOINT.replace('{key}', key);
}

async function callRpc(method, params) {
  const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params });

  // Prefer the new key; fall back to the old key during the overlap window.
  for (const [label, key] of [['new', NEW_KEY], ['old', OLD_KEY]]) {
    if (!key) continue;
    const res = await fetch(urlFor(key), {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body,
    });

    // 401/403 means this key is not accepted; try the next one.
    if (res.status === 401 || res.status === 403) {
      console.warn(`[rpc] key=${label} rejected status=${res.status}`);
      continue;
    }

    // Log which key served the request so the drain is observable.
    console.log(`[rpc] key=${label} status=${res.status} method=${method}`);
    return res.json();
  }

  throw new Error('all RPC keys rejected');
}

callRpc('eth_blockNumber', []).catch((e) => {
  console.error('[rpc] fatal', e.message);
  process.exit(1);
});

针对你自己的端点测量排空

仅凭文档无法知道你的排空窗口。要测量它。下面的方法对任何端点都可复现,并生成一个你用自己数字填写的表格。每次轮换运行一次并保留结果;随着时间的推移,它们会成为你的轮换预算。

测量很简单:部署新密钥后,以固定间隔采样密钥选择日志,并记录由新密钥提供服务的请求比例。当该比例达到 100% 并在你最长的连接持续时间内保持在那里时,排空完成,可以撤销 A。

为回滚演练记录同样的表格。如果你从未练习过撤销 B 并恢复 A,你就不知道回滚是否有效,而第一次需要它时将在事件期间。

  • 结果表列:时间戳、采样请求数、新密钥服务百分比、旧密钥服务百分比、认证失败数、备注。
  • 以固定间隔(例如每 30 秒)采样,直到新密钥比例达到 100%。
  • 在撤销旧密钥之前,在 100% 处保持你最长的连接持续时间。
  • 在回滚演练期间重复该表格,以证明反向路径有效。

检测泄露或滥用的密钥

检测关注的是异常,而不是签名。注意使用量或支出上升而你自己流量没有相应上升、来自你不运营的 IP 范围的请求,以及你并未积极使用的密钥突然出现 429。最后一个信号特别说明问题:如果一个你认为空闲的密钥被限流,那么有其他东西在使用它。

如何修复 RPC 429 错误 指南涵盖了速率限制机制,但安全解读不同。空闲密钥上的 429 是未经授权使用的证据,而不是容量问题。在你能够归因流量之前,将其视为潜在事件。

也要对认证失败发出告警。401 或 403 响应的激增通常意味着部署发布了错误的密钥或轮换不完整。无论哪种情况,这都是一个信号,表明你的凭证状态和部署状态已经偏离。

  • 使用量或支出异常,但没有匹配的流量。
  • 来自意外 IP 范围的请求。
  • 你并未积极使用的密钥出现 429。
  • 部署或轮换后 401/403 响应激增。

事件响应:先撤销,后调查

当你怀疑密钥泄露时,在调查之前先撤销它。已撤销的密钥比活跃密钥成本更低,撤销期间短暂中断的成本几乎总是低于持续滥用的成本。调查可以针对已撤销凭证的日志进行,而攻击者无法继续消耗你的配额。

顺序是:撤销可疑密钥,确认撤销生效,然后拉取日志和指标以确定范围。如果密钥在客户端打包文件中,假设每个副本都是公开的,并轮换整个链条,包括任何派生凭证。

事件发生后,关闭泄露渠道,而不仅仅是凭证。如果密钥通过 CI 日志泄露,修复日志记录。如果通过错误追踪器泄露,在捕获 URL 之前对其进行脱敏。轮换而不修复渠道保证会重演。

  • 先撤销可疑密钥;之后针对其日志进行调查。
  • 在宣布事件已控制之前,确认撤销已生效。
  • 假设打包密钥的每个副本都是公开的,并轮换整个链条。
  • 修复泄露渠道,而不仅仅是凭证。

凭证生命周期的操作检查清单

下面的检查清单是最低可行的生命周期。它假设有密钥存储、CI 扫描器和按环境区分的密钥。如果其中任何一项缺失,请在下一次轮换之前添加它们,因为每一项都消除了一类泄露。

API 服务RPC 定价 页面描述了凭证如何映射到计划和配额,这在你决定签发多少密钥以及如何限定其范围时很有用。RPC 端点指南(RPC Assistant) 涵盖了端点选择,这是凭证决策的另一半。

  • 将凭证存储在密钥管理器中,绝不要放在源代码或镜像中。
  • 在 CI 中运行密钥扫描器,并在发现时阻止合并。
  • 签发按环境区分的密钥,这样预发布环境无法触及生产环境。
  • 在提供商支持的情况下应用最小权限作用域。
  • 为 JWT 设置过期时间,并在过期前轮换。
  • 对认证失败和空闲密钥的 429 发出告警。
  • 保留一份书面轮换运行手册,包含双密钥重叠顺序。

无法通过工程手段消除的限制和权衡

URL 嵌入密钥本质上比头认证更容易泄露。无论多少流程纪律都无法改变一个事实:请求目标中的凭证会被比头中的凭证更多的系统捕获。如果你的提供商支持头认证,请优先使用它;如果不支持,请将 URL 密钥视为更高风险的凭证,并更频繁地轮换它。

并非每个提供商都提供作用域或过期时间。在缺失这些的情况下,你无法通过凭证本身限制泄露密钥的影响范围,因此你通过网络控制、告警和更短的轮换间隔来补偿。这是一个真实的约束,而不是你可以填补的配置缺口。

IP 白名单会在没有固定 IP 的情况下破坏无服务器出口。如果你的工作负载在临时计算上运行,一旦出口地址发生变化,白名单密钥就会失败。你要么通过 NAT 或代理固定出口,要么放弃白名单并接受更高的暴露。请慎重选择,并记录该选择。

  • URL 密钥通过比头密钥更多的渠道泄露;更频繁地轮换它们。
  • 缺少作用域或过期时间意味着需要补偿控制,而不是修复。
  • IP 白名单需要固定出口地址;没有固定地址的无服务器会中断。
  • 这里的每个权衡都应被记录,而不是在事件期间才发现。

轮换失败故障排除

大多数轮换失败分为四类:新密钥实际上没有部署、旧密钥被过早撤销、端点池一次性全部轮换,或者凭证缓存在你忘记的地方。前三个是顺序错误;第四个是间接失败。

如果你在部署新密钥后立即看到 401 或 403 响应,请检查密钥存储写入是否传播到每个实例。滚动部署可能会让旧实例继续使用旧密钥运行,这在重叠期间没问题,但如果你在部署完成之前撤销 A,就会变成中断。

如果你在轮换期间看到 429 响应,你可能将流量集中在一个密钥上。放慢轮换速度并顺序轮换端点。RPC 端点指南(RPC Assistant)OnFinality Learn 中心 都涵盖了有助于诊断此问题的端点级行为。

  • 部署后 401/403:验证秘密已传播到每个实例。
  • 撤销后中断:你在部署完成之前撤销了 A。
  • 轮换期间 429:你将流量集中在一个密钥上;请顺序轮换。
  • 持续认证失败:代理、CDN 或边车中的缓存凭证。

后续步骤:将轮换纳入日常运营

目标不是事件期间的英勇轮换;而是按计划进行的无聊轮换。选择一个间隔,将其放入运行手册,并练习双密钥重叠直到成为常规。你做过十次的轮换是配置更改;你从未做过的轮换是等待触发的中断。

从一个端点开始。配置第二个密钥,部署示例代码,观察排空表填满,并撤销旧密钥。然后将该模式扩展到你的池的其余部分,以及根据需要扩展到你的 Base 或其他网络端点。

如果你正在选择在哪里运行此生命周期,API 服务RPC 定价 页面描述了凭证和配额模型,而 OnFinality Learn 中心 收集了相关的操作指南。轮换模式本身与提供商无关;只有凭证形态会变化。

  • 安排轮换;不要等待事件发生。
  • 练习双密钥重叠直到成为常规。
  • 从一个端点开始,然后扩展到池。
  • 保留每次轮换的排空表作为你的轮换预算。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始