将 300 万个变量升级为信封加密

在我们开始时,Railway(至少暂时)完全托管在 Google Cloud Platform 上 将我们的基础设施转变为裸机

一开始,利用现有的 GCP 提供的服务来启动一家初创企业 完全有道理。 构建容器注册表、块存储、负载均衡器或密钥管理等核心服务只会妨碍产品的构建。

转向裸机为我们提供了建立盈利业务所需的利润,但即使不考虑成本,也依赖于单一基础设施 供应商存在巨大的技术风险。 “GCP 失败的频率有多少?” 可能会问。 足以保证整个 博客文章 就此主题而言!

继续给我们带来最多问题的 GCP 服务是密钥管理服务 (KMS) 并且,w希莱 从技术上讲,依赖性仍然存在,我们对 KMS 的使用已大大减少,使得迁移变得几乎微不足道。

让我们深入研究一下如何消除这种 KMS 依赖性,同时提高性能,并且无需停机。

前后对 KMS 的加密请求的视图

KMS 的缺点

Railway 的宗旨是让用户轻松部署代码。 这些部署通常需要 API 密钥和数据库凭证等配置值可用。 铁路解决 这个w伊思 服务变量,它们在运行时自动注入到部署中。

由于这些值通常包含敏感信息,因此它们在存储到我们的 Postgres 数据库之前会被加密。 到目前为止, 这些加密操作都是通过直接调用KMS来完成的。

当每个部署的变量数量是平均数(大约 10 个)时,这会按预期工作,但某些部署有数百个 🫣。

这种尖锐的 KMS 使用模式会导致问题。

  • KMS 是外部服务 → 解密部署的数百个变量需要许多网络请求(每个约 20 毫秒),这会减慢大批量的速度,即使同时完成也是如此
  • KMS 强制执行配额 → 超过配额将导致所有操作暂时失败。 可以根据要求提高配额,但不保证获得批准,并且 GCP 在没有警告的情况下降低了配额 在过去
  • KMS 并不意味着无限的密钥 → 所有变量目前均使用相同的 KMS 密钥进行加密,这意味着暴力攻击可能会损害所有 Railwa 中的变量y

幸运的是,有一种标准实践可以立即解决所有这些问题,这实际上就是 KMS 的设计目的。 (是的,我们当时拿着它 错误的.)

用密钥加密密钥

信封加密是使用数据加密密钥 (DEK) 加密明文数据(如变量),然后使用密钥加密密钥 (KEK) 加密 DEK 的做法。

通过允许每个用户(或铁路项目)使用单独的加密密钥,可以减少攻击的范围。 使用本地生成的数据密钥还意味着减少对外部密钥管理服务的依赖。 正是我们

GCP 对 KMS 的解释

GCP 对 KMS 的解释

如前所述,KMS 专为信封加密而设计,这也是其最大输入大小的原因 64KiB (足以加密一个大的 DEK)。 我们在实现变量加密时知道这一点,但由于 99% 的变量都符合最大大小(大约 10,000 字博客文章的长度),因此直接使用 KMS 来获取功能是有意义的。

实施信封加密

信封加密只需为整个批次使用一个数据加密密钥 (DEK),就无需对大批量变量调用 KMS 数百次。 我们只需为每个生成一个 DEK 环境 (项目的独立实例,例如生产或登台)并将其用于所有加密操作。

DEK 在本地生成(256 位 AES 使用 气相色谱法),使用KMS加密一次,然后存储在 Environment 数据模型。 我们创建了一个帮助实用程序来处理加密、解密和密钥生成,并且 随附的 它到我们的请求上下文,它具有当前 HTTP 请求或后台作业的生命周期。

interface EnvelopeEncryptionManager {
  generateEncryptedDek(): Promise;
  encrypt(plaintext: string, encryptedDek: string): Promise;
  decrypt(encrypted: string): Promise;
}

这意味着对于每个部署(或其他操作),加密助手都会解密必要的 DEK 一次并将其缓存在内存中以供进一步操作, 减少 将 100 多次前往 KMS 的行程减少为 1。

不过这里还有一些其他的魔力。 你注意到了吗 decrypt() 不需要 DEK? 这是如何运作的?

启用密钥轮换

信封加密的要求之一是能够轻松轮换密钥。 正如我们所见,DEK 存储在 Environment 模型,但是如果需要的话我们如何旋转它呢? 我们要么必须存储旧密钥列表以在加密过程中进行尝试,要么重新加密所有变量并在潜在的巨大数据库事务中更新数据库。

不是很好。

幸运的是,还有一个最佳实践,即将加密密钥存储在加密值(密文)旁边。 对于我们来说,这意味着在将值作为 JSON 字符串存储在数据库中之前,使用密文序列化加密的 DEK:

variable.encryptedValue = `{"encryptedDek":"...","cipherText":"..."}`

使用此设置的变量解密可以通过以下方式完成 解密使用 KMS 获取 DEK(如果尚未缓存在内存中)并使用它在本地解密变量(无需 KMS)。 它还使得传递加密值变得更容易,因为相关的 Environment 或者 DEK 不需要跟着一起去。

这个策略的基本实现只有几百行代码我们仍然需要将超过 300 万个现有变量转换为新系统。

迁移 300 万个变量

将加密的 DEK 存储在密文旁边也使得同时支持传统加密和信封加密成为可能,从而使我们能够逐步 升级 超过 300 万个变量。

对于解密回退,如果在加密变量中找不到 DEK,则像以前一样直接调用 KMS 进行解密。

与加密回退类似,如果网络上尚不存在 DEK Environment,直接调用KMS。

这使得为​​单一环境生成 DEK 成为可能。 此环境中修改的变量将升级为写入时信封加密,从而将迁移工作减少到以下步骤:

  1. 部署初始信封加密代码(本身不执行任何操作)
  2. 开始为新创建的环境生成 DEK
  3. 运行数据迁移以生成适用于所有环境的 DEK
  4. 运行数据迁移以重新加密所有变量

向前进

引入信封加密已经过去几周了,并且取得了成功。 我们的 KMS 使用量下降了 10 倍,没有出现使用量峰值,并且我们已经能够实现前面提到的好处:

  • 更好的性能 → 每批使用 KMS 解密 DEK 一次,可将变量解密的慢速情况从最慢的约 10 秒减少到仅几毫秒
  • 无服务中断 → 更一致的使用模式意味着我们不再需要担心超出 KMS 配额并导致停机。 我们还能够将配额减少几个数量级
  • 更好的安全性 → 每个环境使用单独的数据密钥意味着暴力对一个环境的强制攻击不会损害任何其他环境的数据
  • 更灵活的加密 → 虽然我们不一定需要超越 64KiB KMS限制,使用对称AES加密意味着我们现在可以加密任意长度的数据

我们不仅获得了这些直接的好处,而且我们还已经使用了这个信封加密系统来保存我们最近发布的凭证 私人注册支持

我们距离不依赖 GCP 又近了一步。 是的,我们在技术上仍在使用 KMS,但将其替换为类似的东西 避难所 现在可以通过重复上述迁移步骤以增量方式完成每个环境的 DEK。

所以这就是我们如何将 300 万个变量升级为信封加密。 迁移数据始终是一项可怕的任务。 迁移敏感数据更糟糕! 但是,事实上,即使是最艰巨的工作也可以分解为可管理的部分,这就是我们讲述这个故事的方式。

我们做错了什么吗? 让我们知道 推特 随着我们在裸机之路上越走越远,请继续关注更多此类故事。

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​