作者:Dan Lorenc
最近我听到了很多关于如何签署开源软件发布的问题。一旦你解决了那些不可能解决的工具/加密问题,你很快就会意识到你仅仅触及了复杂性的表面。这些问题并不都是 OSS 特有的,但是社区驱动的项目确实面临一些超越技术和哲学领域的独特挑战。
Photo by Austin Kehmeier on Unsplash
签署软件发布是什么意思?谁应该做这件事?钥匙应该放在哪里?用户如何验证它?我们为什么还要再这样做?如果你(可以理解地认为)这是一个解决了的问题,你像有很多人一样,但你要失望了。以下是我认为最有意义的,以及我打算在我所维护的项目中尝试的内容。
这种方法并不适用于所有人,可能也不是完美的。它设计得很容易设置,很难搞砸,比我所见过的几乎任何 OSS 项目都好。有两个步骤。如果你只做第一件事,你仍然做得很出色。
在我们开始之前:基本卫生准备。需要 2FA。main和所有发布分支的分支保护。需要 PR 评论。需要 CI(这在下面很重要)。你记得已经在这么做吗?仔细检查所有的仓库,特别是构建的那些!有关自动化工具,请参阅 OpenSSF Security Scorecards[1]项目。
配置 CI/build 系统,对它执行的每个构建进行签名。在信封上签名(示例如下),至少包含以下内容:
对于信封格式:in-Toto links[2]可用,Grafeas Provenance[3]也是常见的。也有些 SBOM 的东西。不要太担心,选择一个你喜欢并会使用的。
像Tekton Chains[4]这样的项目旨在使这变得更容易,但许多构建系统也可以通过配置或扩展来以这种方式运行。ITE-6[5]是一个即将发布的跨系统定义这种格式的标准。
将密钥存储在专用的 KMS 系统中,尽可能锁定对它的访问。不要在本地导出或保存私钥。定期审计密钥使用情况。
在源代码管理系统之外的地方发布发布版本。如果你使用 GitHub,请将你的发行版和签名存储在 GCS 或 S3 上。锁定对构建系统的访问。审计访问。锁定对构建系统的发布工件的访问。现在,你的发布页面上的所有内容都由构建系统进行了签名,并且构建可以从源代码一直到发布工件进行验证。
在你的版本旁边发布这些来源和签名。将公钥存储在存储库中。用户可以在源代码中找到用于发布的公钥。
如果整个 SCM 被破坏在你的威胁模型中,你也可以签名 git 标记。我建议在你可以信任的地方运行 SCM。无论如何我都建议这样做。
如果整个构建系统被破坏在威胁模型中,那么你可以尝试可重现的构建。我再次建议在你可以信任的地方运行构建系统。无论如何,可复制构建仍然是一个好主意。
第 1 步中的系统为用户提供了关于工件的可验证的来源。这可以显示它的来源以及用于构建和它的工具。这很重要,但它不能告诉你所使用的源代码是“正确的”,正如项目所定义的那样。这里的一个示例威胁模型是回滚或冻结攻击,攻击者能够欺骗用户安装特定的旧版本的软件。所有的构建都可以通过验证返回到它们的源代码中——我们不知道它们是否被“授权”。
这就是哲学问题所在——对于一个开源社区来说,“授权”一个官方版本意味着什么?对于大多数项目来说,这是隐式的——访问发布页面的人创建一个发布。有些项目在合并前使用 pull 请求,并得到多个其他维护者的批准。维护人员可以在这里定义并遵循他们自己的决策过程,也可以不定义。这可以用 In-Toto 或电子邮件列表上的投票之类的东西正式编码到策略中,但通常不是这样。
如果你想解决这个威胁模型,请想出并记录声明一个发布的策略。公开地遵循这个过程。PR 评论或电子邮件列表上的 1+级评论在这里工作得很好。让它公开。这应该包括确切的 git 提交。文档如何验证这一切。Node.js 项目在这里做了一件令人惊叹的工作。
将此批准编码为另一个签名。如果第 1 部分中的自动签名验证了一个版本,那么这个代表维护者的手动签名就授权了这个版本。也将这个(不同的)公钥放置在存储库中。使用 KMS 时,IAM 角色仅限于维护人员。审计访问。可以在任何地方发布这些签名,包括创建初始批准的任何地方(拉请求、票据或电子邮件线程)。透明日志即将到来!
如果你真的很偏执/担心的话,还有其他的东西 这显然不包括TUF[6]。我认为 TUF 非常适合复杂的更新系统,但对于大多数小型项目来说,它仍然是压倒性的。时间戳协议对于任何自动更新都是必须的,但是它给小型项目带来了太多的操作复杂性和风险。你会自动更新任何东西给客户吗?使用 TUF。如果没有,你可以跳过它。
锁定你的构建系统。还有你的单片机系统。这比上面的任何东西都重要,但在你完成所有这些之后,回去并锁定系统。禁用所有访问和审计登录。使构建的。声明所有的输入。没有网络访问。没有网络访问。Kubernetes 发布组在这方面做得非常出色[7]。
将此批准编码为另一个签名。如果第 1 部分中的自动签名验证了一个版本,那么这个代表维护者的手动签名就授权了这个版本。也将这个(不同的)公钥放置在存储库中。使用 KMS 时,IAM 角色仅限于维护人员。审计访问。可以在任何地方发布这些签名,包括创建初始批准的任何地方(拉请求、票据或电子邮件线程)。透明日志即将到来!
这显然不包括 TUF。我认为 TUF 非常适合复杂的更新系统,但对于大多数小型项目来说,它仍然是压倒性的。时间戳协议对于任何自动更新都是必须的,但是它给小型项目带来了太多的操作复杂性和风险。你会自动更新任何东西给客户吗?使用 TUF。如果没有,你可以跳过它。
锁定你的构建系统。还有你的 SCM 系统。这比上面的任何东西都重要,但在你完成所有这些之后,回去并锁定系统。禁用所有访问和审计登录。使构建密封。声明所有的输入。没有网络访问。没有网络访问。Kubernetes 发布组在这方面做得非常出色。
这不包括撤销。键撤销在更新系统不好工作,特别是在 OSS。你的撤销系统是 Twitter 或 MITRE。撤销工件,而不是密钥。像对待其他易受攻击的工件一样对待受损或被篡改的工件。你的 CVE 扫描仪应该检测到这些,而不是你的 PKI。
我们在Sigstore[8]项目中使二进制透明性变得更容易和自动。我们将能够保护你和你的用户免受密钥入侵和有针对性的攻击,而无需你采取任何行动。你很快就可以直接集成自动个人密钥管理和离线签名时间戳。
大型的、公共的工件存储库应该准备入侵并计划恢复。PyPI 在这里做得很好[9]。TUF key delegation 是实现这一目标的最佳方式。使几个根密钥处于离线状态,需要仲裁对从属签名密钥进行签名。旋转。混合和匹配攻击在这里也很可怕。使用 TUF。
本文最初发表于medium.com[10]。
[1]
Security Scorecards: https://github.com/ossf/scorecard
[2]
in-Toto links: https://in-toto.io/
[3]
Grafeas Provenance: https://github.com/grafeas/grafeas/blob/f11fa45f2a57406a0b69eb3463ecbca9e127ccd2/proto/v1beta1/provenance_go_proto/provenance.pb.go#L55
[4]
Tekton Chains: http://github.com/tektoncd/chains
[5]
ITE-6: https://github.com/in-toto/ITE/pull/15
[6]
TUF: https://theupdateframework.io/
[7]
做得非常出色: https://github.com/nodejs/node#verifying-binaries
[8]
Sigstore: http://sigstore.dev/
[9]
PyPI 在这里做得很好: https://www.python.org/dev/peps/pep-0480/
[10]
medium.com: https://dlorenc.medium.com/how-to-sign-a-release-of-oss-e96ee94286fc