这是关于Pantry系列博文的第三部分,Pantry是一个新的Haskell包的存储和下载系统。你可以看看第一部分和第二部分。
这个stack.yaml 文件有什么问题?
resolver: lts-12.0
不确定?好吧,试试这个。
resolver: lts-12.0
extra-deps:
- acme-missiles-0.3
好吧,这个问题比较容易指出:我们还没有确定我们应该使用哪个版本的cabal文件作为acme-missiles-0.3 。就目前而言,我们的构建计划是不可复制的。在未来的某个时间点,cabal 文件可能会被修改,而我们会得到一个不同的计划。解决这个问题是相当容易的。
resolver: lts-12.0
extra-deps:
- acme-missiles-0.3@rev:0
@rev:0 把我们固定在一个特定的版本上。然而,我们仍然有一个问题。让我们分析一下这个stack.yaml 文件是如何被Stack处理的。
解决acme-missiles的问题
Stack需要得到acme-missiles-0.3.tar.gz sdist tarball,以及修订版的acme-missiles.cabal 文件。为了完成这两个步骤,Stack将:
- 使用
hackage-security来下载01-index.tar文件,并使用Hackage的公钥来验证下载。这些密钥是硬编码到Stack中的,或者可以通过配置来覆盖。 - 找到
acme-missiles/0.3/package.json文件,以获得acme-missiles-0.3.tar.gz文件的 SHA256 和文件大小。 - 找到
01-index.tar文件中的第一个文件,文件路径为acme-missiles/0.3/acme-missiles.cabal,它对应于@rev:0位。
一切都很好。Hackage安全层可以防止恶意的中间人攻击,以及其他攻击。然而,它并没有阻止其他一些可能性:
- Hackage本身被编译并开始发送恶意代码
- 发生了一个错误,导致修改了sdist tarball(如上次提到的)。
- 由于某种未知的原因,决定改变sdist tarball或cabal文件的修改内容
要清楚的是:这并不是Hackage特有的。考虑一下下面的 Stack 配置。
resolver: lts-12.0
extra-deps:
- https://example.com/my-file.tar.gz
谁能说my-file.tar.gz 在某个时候没有被改变,即使我控制了这个域名?Stack没有办法用提供的信息来保证这种稳定性。
今天,Stack已经提供了一种更可靠的方式来指定cabal文件的修订。
resolver: lts-12.0
extra-deps:
- acme-missiles-0.3@sha256:2ba66a092a32593880a87fb00f3213762d7bca65a687d45965778deb8694c5d1,613
然而,我们仍然依靠Hackage元数据来确保sdist tarball未被修改。为什么不在散列方法上多下功夫呢?有了Pantry,我们就可以这样做了作为一个例子(我稍后会分享源代码)。
- hackage: ALUT-2.4.0.2@sha256:6fbceae566b3d63118c67db71645f48ba22b195c58328863d274a76fba086fc1,3895
pantry-tree:
size: 2402
sha256: 8985dfc0fe299d313690cd4db86c511340f805df5e6d3fab79c15d36ac5d8c71
我们已经讨论过树了。在这种情况下,8985dfc...哈希值是树的二进制表示法的哈希值,该二进制表示法的大小为2,402字节。任何遵循相同的Pantry算法的人,如果下载相同的ALUT-2.4.0.2.tar.gz ,并具有相同的cabal文件修订版,最终将得到相同的哈希值和文件大小。任何Pantry缓存服务器(我们仍然没有谈到!)将能够提供该信息。
"你真的希望我每次添加依赖关系时都要输入所有这些信息吗?"你可能会问。答案是:不,当然不是。那将是一种虐待狂。继续阅读。
解决解析器
弄清什么是lts-12.0 ,这个故事也差不多。Stack解析了这个字符串,并意识到它在寻找一个LTS快照,主要版本12,次要版本0,然后转到适当的URL,下载内容,将它们保存在本地......并希望它们在未来任何时候都不会改变。
我在运行那个 repo。我保证,除非有一个重大的错误需要修复(比如不正确的哈希值),否则我不打算修改这些文件。它们应该是可以重现的。但你不应该相信我。说真的,假设我想破坏你的项目:这是思考可重现构建的正确心态。
明天,我可以上传一个新版本的conduit ,里面有一个后门,修改lts-12.0.yaml 文件来使用它,下次你用非缓存下载的方式运行stack build ,你会得到我的bug。在你最初构建和测试的时候,一切都会运行得很好。但现在你被攻击的机会大了。
我现在听起来可能像个破唱片,但我想你能猜到这是怎么回事。这就对了:对快照文件也进行哈希处理。而不是resolver: lts-12.0 ,你会有如下的东西(确切的语法仍在变化中)。
resolver:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/12/0.yaml
sha256: a55695a7236e46740e369d778d83e44475ed4f1c80783071835dae43658bada6
size: 500006
你可能已经注意到,这是在使用一个与以前不同的 repo。这是因为Stackage的快照文件格式正随着新的基于Pantry的Stack而改变,与现有的自定义快照格式相同。我刚刚完成了对所有LTS Haskell和Stackage Nightly快照的转换,如果你有兴趣,可以随时看看。奖励:这些文件通过消除一堆不相干的信息而变得更小,我们将把这些信息与快照本身分开。
你有虐待狂吗?
所以回到这一点:谁在他们正确的头脑中想要正确地记录这种信息?显然没有人。但这正是工具真正擅长写的东西,而不是!以下是我的计划执行。
- 在Stack中增加对所有这些哈希值的支持,保留对无哈希值的配置格式的支持。
- 添加一个命令(也许是
stack freeze?欢迎使用自行车脱落),它可以将你的配置文件转换为包括哈希值,或者吐出哈希值的版本,你可以复制粘贴。后者可能会更好,以避免破坏YAML文件的注释。 - 当Stack检测到你的配置文件中有无哈希值时,它会添加一个警告,并建议运行
stack freeze
而这里是心理模型。当你最初说lts-12.0 ,你最终会受到来自上游的不良内容的影响。但当你最初选择任何上游快照或软件包时,你容易受到它们包含不正确或恶意代码的影响。确保你得到的是你可以信任的东西是你的责任,没有工具可以为你解决这个问题。
但是,一旦你审查了这些文件,你希望你的工具能确保这些文件永远不会从你手下被改变。最初指定简单的格式(例如,lts-12.0 ),测试你的配置,然后加入哈希值,就可以达到这个目的。幸运的是,我们的工具可以使这个过程(相对)不费力。
下一步是什么?
我还没有实现冻结命令,所以那是在地平线上。在pantry 分支中还有很多未实现的代码。但最有可能的是,我很快就会从Stack工作本身中抽身出来,开始着手开发一个新的Stackage策展人工具,它可以和Pantry一起工作,并使其他人更容易测试他们自己的快照。它也将使创建快照与Hackage以外的包更容易,以便更容易测试拟议的代码修改。请继续关注!