NixOSの再現可能ビルド: 最小インストールISOの独立再構築に成功
(discourse.nixos.org)- NixOSの最小インストールISOがHydra配布版とビット単位で同一に独立再構築され、配布バイナリとソースが一致しているかを検証できることを示した
- 今回の検証では、ISOに含まれるパッケージだけでなくISO生成プロセス自体まで再現し、単純なパッケージ再現より広い範囲を確認した
- 再構築はNixOS 20.03 VirtualBox applianceから開始し、
nixpkgsリビジョン63678e9f3d3aを使用、--option substitute falseでバイナリキャッシュ依存を無効化 - 2020年のOVAやダウンロードした
gitに巧妙なバックドアがあった場合は依然として攻撃ベクトルになり得るため、完全にブートストラップされたシステムに基づく検証はまだ残されている - 最小ISOの再構築は重要なマイルストーンだが、一時的な回避策の除去、より多くのインストールメディアの再現、定期的な独立再構築インフラ、ビルド証明ツールが次の課題となる
最小ISOで検証された再現性
- Hydraが公開した
nixos-minimalISOビルドを独立に再ビルドし、ビット単位で同一の成果物を得た - 再現範囲は2つの軸に分かれる
- ISOに含まれるすべてのパッケージ
- ISOを作成するビルドプロセス自体
- ISOビルドに必要だがISO内には含まれないパッケージもあわせてビルドし、キャッシュ済みバイナリには依存していない
- 再現可能ビルドは、配布バイナリがソースに忠実であるか、Hydraのようなビルドパイプラインで改ざんされていないかを確認できる信頼の経路を提供する
再構築手順と限界
- 再構築は新しいVirtualBox appliance上でNixOS 20.03を起動して実施した
- CPUとメモリを十分に割り当て、ディスクを約65GBまで拡張
gitをインストール後、nixpkgsをクローンして63678e9f3d3aリビジョンをチェックアウト--option substitute falseにより、必要な項目をバイナリキャッシュから取得せずローカルマシンでビルド
- 手順には既知の問題を回避するための暫定措置が含まれる
- サプライチェーン信頼の観点での限界は残っている
- 2020年のOVAやダウンロードした
gitに巧妙なバックドアがあった場合、依然として攻撃ベクトルになり得る - 完全にbootstrappedされたシステム上で再構築する方が望ましいが、まだその段階には達していない
- 関連する進展はnixpkgs supply-chain security projectスレッドで続いている
- 2020年のOVAやダウンロードした
2021年の発表と今回の結果の違い
- 2021年には最小ISOが100%再現可能だという発表があったが、当時はISOビルドに必要なパッケージを個別に再現しただけで、実際のISO再構築には差異が残っていた
- 原因はHydraキャッシュとISO生成方式に関する未解決の問題だった
- その後、問題が修正される過程でPython 3.10のupstream問題のようなリグレッションが発生し、今週になってようやく全体のチェーンを検証できる状態に戻った
- 次の段階としては、一時的な回避策の除去、さらに多くのパッケージの再現、Gnome ISOのような他のインストールメディアの再現が挙げられる
- 定期的な独立再構築インフラと、trustixのようなビルド証明の共有・利用ツールも必要となる
1件のコメント
Hacker News のコメント
ソースから最小 ISOを再ビルドしたことは、ソースベースで再現可能にビルドされるシステムへ向かう道のりにおける印象的なマイルストーンです
Guix も最近、同じ道のりで直交的ながら同じくらい印象的な成果を出していて、ほかのバイナリコンパイラのブロブなしに、たった 1 つの再現可能な 357 バイトのバイナリからコンパイラツールチェーン全体をブートストラップしました
近いうちに、いつかこの 2 つが組み合わさって、ディストリビューション全体をソースから再現可能にビルドできるようになるかもしれません
https://guix.gnu.org/en/blog/2023/the-full-source-bootstrap-...
多くの人が理解していない核心は、成果物が 100% 信頼できることを証明するのではなく、成果物がソースに100% 忠実であることを証明する点にあります
つまり、隠れたバックドアのような怪しいことが見つかった場合、それを常に決定的に再現できるようになります
悪い側にとっては、逃げ場も隠れ場所もなくなるわけです
機械語 357 バイト全体を手で文書化して、人間が理解できるようにもできそうです
こういうことをやったことがないので愚かな質問かもしれませんが、なぜ再現可能性がデフォルトの動作ではないのか不思議です
同じソースからソフトウェアを 2 つコンパイルしたなら、毎回完全に同一になるのを妨げる要素が何なのか、よく分かりません
動く部分が多いのは分かりますが、差がどう生じるのかはまだよく理解できていません
よくある問題の一覧はここで見られます: https://reproducible-builds.org/docs/
全体としての核心は、開発者が再現されるかどうかをテストしていないことにあります
リリーステストに含めれば、通常は再現可能な状態が維持されます
明示的に再現不能にする代表例としては、タイムスタンプと作者情報があります
暗黙のうちにデフォルトの再現性を壊すところもあり、たとえば多くのランタイムはハッシュマップの項目順序を定義しておらず、コンパイラがそのハッシュマップを走査してバイナリを作ることもあります
順序に依存しないとは限らない処理があり、CPU の状態によって少しずつ違うバイナリになるものの、どれも正しい結果である場合があります
時には時刻や環境に依存するメタデータ、スレッドの実行順序のようなものも原因になります
よく分かっていなくて申し訳ないのですが、NixOS が存在する主な理由の 1 つは再現可能性だと思っていました
こうした問題はすでに解決済みだと思っていました
NixOS は 2 時間ほどしか使っておらず、Hyprland を試してみたかったのですが、Hyprland は多少設定が必要なので、ほかのディストリビューションより NixOS のほうが他人の設定を持ってきて使うのが簡単だろうと思っていました
ところが設定を見つけるのも難しく、適当な GitHub gist で 3 つほど見つけましたが、どれも動かず諦めました
一般的なディストリビューションのようにシステム環境全体に依存しないため、多くの場合すでに毎回同じバイナリが出ます
ただしビルドプロセス自体が複数のパッケージで非決定的になり得るので、これだけで直ちに完全な再現可能性が得られるわけではありません
あなたが考えている意味は、バイナリパッケージを簡単に再ビルドしたときに、同じ依存関係のバージョンやビルドオプションなどを使うということです
「自分のノート PC では動いたのに」というようなコンパイルエラーが新たに起きる余地がない、という意味です
ここで言っている意味は、すべてのビルド成果物がバイト単位で同一のバイナリになるということです
マシン名、コンパイル時刻、並列ビルドでファイルのコンパイルが終わる順序などに依存してはならず、こちらのほうがはるかに難しいです
慣れていないなら、「今すぐ動くものが欲しい」という状況で選ぶツールではほとんどありません
NixOS で「再現可能」という場合は、「同じ Nix コードで同じプログラムの動作が得られる」という意味に近いです
人々が Dockerfile に期待することに似ていて、「自分のマシンでは動いた」や「前回は動いた」という問題を解決しようとするレベルです
一方で「再現可能なビルド」は、異なるマシンで作った成果物がビット単位で同一であることを目標にします
こうなると、特定のソース集合からコードがビルドされたかを検証できるため、セキュリティの層が 1 つ生まれます
設定を探すときにどんな検索語を使ったのかも気になります
「nixos configuration」で検索すると https://github.com/search?q=nixos%20configuration&type=repos... のような結果があり、Hyprland だけを見ても https://github.com/search?q=wayland.windowManager.hyprland&t... のようにかなり多く見つかります
https://github.com/donovanglover/nix-config を見るとよい
Flake ベースの設定で、Hyprland やいろいろ良さそうなものが入っている
現在の NixOS は、気弱な人や時間の足りない人に向いたツールではない
いつか変わってほしいが、踏ん張って乗り越えれば利点は得られる
GitHub のコード検索を使ったのか気になる
関連する Home Manager オプションはここで探せる: https://mipmip.github.io/home-manager-option-search/?query=h...
その後 GitHub で検索すればよい: https://github.com/search?utf8=%E2%9C%93&q=lang%3Anix+hyprla...
一部のオプション検索は、よりカジュアルなユーザー向け、あるいはより上級ユーザー向けであることを示唆している場合がある
Nix / NixOS / Nixpkgs の再現可能性は ソースの再現可能性 だという点を覚えておく必要がある
ソースが変われば警告を受けるが、ビルドするたびに変わり得るバイナリの再現可能性とは異なる
Nix / NixOS / Nixpkgs のバイナリ再現可能性は、少なくとも体系的にはあまり十分にテストされていないほうだ
Guix、Arch Linux、Debian は Nix / NixOS / Nixpkgs よりもバイナリ再現可能性をうまく扱っている
資料: https://r13y.com/ (Nix*) / https://tests.reproducible-builds.org/debian/reproducible.ht... (Debian) / https://tests.reproducible-builds.org/archlinux/archlinux.ht... (Arch Linux) / https://data.guix.gnu.org/repository/1/branch/master/latest-... (Guix、読み込みが遅い場合があり、キャッシュされたコピーは https://archive.is/lTuPk)
入力再現可能性 は「入力に対する完全なキャッシュ無効化」を意味する
Nix と Guix は設計上これを完全に実行し、ときには過剰な再ビルドを引き起こすこともある
Debian と Arch Linux はこれを主要な関心事にはしておらず、特定のソースファイルが更新されたときにどのパッケージを再ビルドするかという問題を、手動の再ビルドトリガーのような暫定的な方法で処理している
出力再現可能性 は「ビルド過程が決定的で、常に同じバイナリを作る」という意味で、元記事のテーマである
Nix はパッケージをサンドボックス内でビルドするので助けにはなるが、万能の解決策ではない
この点では Nix も Debian、Arch Linux と同じ船に乗っている
実際、ディストリビューションは再現性を高めるパッチを upstream に頻繁に送り、他のディストリビューションもその恩恵を受けている
この文脈では https://reproducible.nixos.org が提示した他のリンクに対応するものであり、Nix のレポートがあまり詳細でないことには同意するが、だからといって Nix のバイナリ再現性がより悪いという意味ではない
「Nix は入力再現可能性だけは得意だが、バイナリ再現可能性は不得手だ」と読めるなら、それは間違い
まさにそのマイルストーンをここで祝っているところだ
この記事はバイナリだけでなく、それらが ISO としてパッケージングされる方法 までビット単位で再現する話だ
r13y.com は古い状態で、欠けていた1%未満も記憶では upstream の Python 回帰が原因だった
バイナリ自体の再現可能性は、ISO パッケージングを除けばすでに数年前に達成されていた
コア ISO を超えたパッケージまで行くと比較は複雑になる
パッケージの扱い方が微妙だが、この文脈では重要な形で異なっており、Arch の AUR にありそうな多くのパッケージが Nix では通常パッケージとして入っていて、大半の -bin upstream パッケージも Nix では単純に不要だ
一般に Nix は再現可能なビルドを作りやすくするが、Nix と無関係に常に可能なわけではなく、しばしばパッチが必要になる
Nix のデフォルトパッケージリポジトリが8万個以上ある一方で、Arch は AUR を除くと1万5千個未満である点まで合わせると、割合での比較はあまり有用ではない
非常によくある誤解の1つは、Nix store パスのハッシュがビルド出力に基づいていると思うことだが、実際には隔離された環境でバイナリをビルドするために使われたすべてのソースと入力、バイナリかどうかに関係なく、その全体に基づいている
そのため人々が期待するセキュリティ上の利点がそのまま生じるわけではないが、その代わり、再現可能にビルドされないソフトウェアでも、機能、コンパイラ設定、依存関係のバージョン、ユーザー、構成などが同一の、合理的に再現可能な配布形態として使えるようにしてくれる
同じソースから別々のマシンでビルドしたときに、バイナリが同じかを確認するものだ
要点は、バイナリがビルドのたびに変わらないことにある
テスト方法も、各ビルドを異なる時点、異なるハードウェア、異なるカーネルで2回実行すると書かれている
見ると 85.6% 再現可能 と出ている: https://reproducible.archlinux.org
公式リポジトリに8万個を超えるパッケージがある NixOS では、どれほどの作業が必要になるのか気になる
nixpkgs の目標や現実を踏まえると、まったく事実ではない
元記事は、複数のバイナリパッケージを含むバイナリの最小 ISOを再現する話です
OpenBSD プロジェクトが正反対の方向へ熱心に進んでいる点が皮肉で笑える
OpenBSD はインストールごとに固有でランダム化されたアドレスオフセットを持ちます
再現可能なビルドと固有のインストールという 2 つの目標が互いに直交しており、同時に達成可能だということは理解していますが、この二面性はいまだに面白いです
あるいは、プログラム起動時にオフセットをランダム化する方法でも、再現可能性を保ちながらセキュリティをさらに高められます
そうすれば実行するたびにオフセットが変わります
パッケージ自体は依然として再現可能であり得ます
すべてのランダム化は、パッケージをダウンロードしてチェックサムを検証した後、ローカルで実行されます
いまや他のほぼすべての Linux ディストリビューションが 90 年代からやってきたように、メンテナがパッケージに署名だけしてくれるとよいのですが
そうすれば、みんながビルドしているコードが、既知の個人によって提出・レビューされた同じコードなのかを、ある程度把握できます
署名が標準化されるまでは、価値あるものを保護する本番用途で Nix を使うのは想像しにくいです
大半はパッケージの内容について何ら保証していません
彼らの署名から意味のある保証を期待するのは、配送業者に製品サポートを期待するのに近いように感じます
悪意を持ってパッケージングされていないと信じる必要もありません
Nix は再現可能なビルドを行うので、バイナリキャッシュに依存したくないなら derivation を見て自分でビルドすればよいのです
根本的な中身が悪意あるものかどうかは、結局は開発者とユーザーの間の問題です
他のディストリビューションがその逆を信じさせているのだとしたら、それは誤解を招いているに近いと思います
思い浮かぶ例外は Tails くらいですが、Tails は Nix ほど幅広くはありません
見たところ Debian はもはやそうしておらず、ビルドシステムがビルドに署名しており、Fedora もしていませんし、Arch は確かではありませんが、していないように思います
NixOS のビルドシステムはすべてのビルド成果物を独自の鍵で署名し、ダウンロード時に署名を検証します
偏執的に考えるなら、Nix は少なくともすべてをソースから直接ビルドしやすくしてくれます
非常に印象的なマイルストーンで、実現した人たちに祝意を送ります
ISO を実際に再ビルドしたときにも差分が出ており、原因は Hydra キャッシュに残っていた問題と ISO 生成方法だったと書かれています
「ISO が生成された方法」をどう直したのか、説明できる人がいるのか気になります
以前に再現可能な ISO を作ろうとしたことがありますが、ファイルシステムの extent を決定的にすることができませんでした
その過程の最後の段階で
./result/isoディレクトリに ISO が生成されます探しているのはそのビルドが呼び出したコマンドのようですが、どの段階を探しているのかはよく分かりません
たとえば
xorrisoの呼び出しはここにあります: https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-...これをやるにはシステム時刻をごまかす必要があるのでは?
時刻は何らかの形でバイナリの中に入り込むことが多いです
あまりに一般的なので、多くのコンパイラがタイムスタンプをごまかすための事実上の標準変数である SOURCE_DATE_EPOCH を実装しています: https://reproducible-builds.org/docs/source-date-epoch/
これは Ken Thompson が「Reflections on Trusting Trust」で書いた問題の解決に役立つのでは?
システム全体をソースコードから完全にブートストラップできるなら、バックドアを仕込まれたコンパイラのようなものを入れるのは難しくなりそうです
理論上は、ISO がビルドされる環境の中に巧妙なバックドアがある可能性もあります
その問題を本当に解決したいなら、Diverse Double Compiling(https://dwheeler.com/trusting-trust/) や環境全体のブートストラップ(https://bootstrappable.org/)を見るとよいです
記事の「上記のアプローチにはブートストラップ問題はないのか?」セクションも関連しています
それでも、ビルドを再現するだけでも、そうした攻撃の可能性をどんどん低くするうえで大いに役立ちます
最近の仕事の関係で Red Hat エコシステムの中にいました
これは Fedora Silverblue、Ansible、Fedora Silverblue + Ansible のようなものと比べるとどうなのか気になります
Imagebuilder は再現可能性を主張していますが、私の知る限りでは、ほとんどの場合 rpm パッケージをソースではなくバイナリとしてインストールします
そのため、すべての入力パッケージも再現可能でなければ、厳密な再現可能性とは言えません
ソースからパッケージをビルドし、ディストリビューションイメージを作り、再現可能性を扱う説明がしっくり来なかったなら、おそらく主な対象読者ではない可能性が高いです
一方 Ansible は、OS が従うべき手順を指定します
SilverblueとNixは、Linuxディストリビューションであるという点以外は互いに直交的なものです
Silverblueは、不変ホスト上でコンテナだけを使うことでソフトウェアの配布方法を変えようとする試みです
Jsonnetと状態追跡を使ってNixをある程度まねるAnsibleの代替を探しているなら、Etchaを見る価値があります: https://etcha.dev
Nixは不変的です
新しい変更は完全に新規に作られ、ビルドが成功してから初めてすべてのパッケージが現在のシステムへ「シンボリックリンク」されます
Fedora Silverblueはostreeベースです https://github.com/ostreedev/ostree
ルートツリーに対してgitに似た動作をしますが、変更を適用するにはシステム全体を再起動する必要があります
Nixはパッケージをシンボリックリンクする方式なので、システムを再起動する必要はありません
より詳しい説明はこちらにあります: https://dataswamp.org/~solene/2023-07-12-intro-to-immutable-...