持ち込みバンドル:申請書から署名検証まで
目標
持ち込みの申請書から始めて、バンドルを取得し、相対パスのハッシュ一覧に署名し、秘密鍵のない媒体で移して、エアギャップ環境側で、署名が先、ハッシュが次の順序で検証したあと、バンドルだけでインストールして、記録を残します。
なぜ重要なのか
ハッシュの一覧だけを一緒に入れたUSBは、転送中の破損は検出できますが、すり替えは検出できません。すり替えた人が、一覧も直せば済むからです。一覧に署名して、公開鍵を別の経路であらかじめ渡しておいて初めて、「誰が作った一覧か」が保証されます。そして、検証スクリプトは、失敗したとき、0以外の値で終了して初めて、自動化が止まります。このPodは、インターネット(80/443)に届くので、ダウンロード側の役割を果たし、インストールの段階は、外部を閉じたプロキシでふさいだまま、バンドルだけで行います。
ステップ
/root/intake/request.txtに、持ち込みの申請を、생태계 이름 버전の2行で書いてください(プレースホルダーはエコシステム、名前、バージョンです):pypi requests 2.32.3、go rsc.io/quote v1.5.2。- バンドル
/root/intake/bundle/に、Pythonのwheel(pypi/、依存関係を含み、wheelだけ)と、Goモジュールプロキシ(goproxy/、新しいモジュールキャッシュのcache/downloadの内容)を取得してください。 - バンドルのディレクトリの中で、相対パスで
/root/intake/bundle/SHA256SUMSを作成してください(一覧と署名ファイル自身は除きます)。 /root/intake/keys/intake.key(Ed25519の秘密鍵)とintake.pubを作成して、SHA256SUMSに署名し、/root/intake/bundle/SHA256SUMS.sigに置いてください。- 媒体
/root/intake/media/bundle.tarを作成して(秘密鍵は入れません)、エアギャップ環境側の/root/intake/inside/に展開し、公開鍵は別に/root/intake/inside/trusted.pubとして置いてください。 - エアギャップ環境側の検証スクリプト
/root/intake/verify.sh <번들> <공개키>を書いてください(プレースホルダーはバンドルと公開鍵です)。署名が先、ハッシュが次、一覧にないファイルまで見て、失敗したら、0以外の値で終了する必要があります。 - 外部をふさいだ状態で、
/root/intake/inside/bundleだけでインストールしてください。venv/root/intake/venvにrequestsを、そして、/root/intake/goappのGoプログラム(rsc.io/quoteのquote.Hello()を出力)をビルドして、/root/intake/bin/helloにしてください。 - 持ち込み記録
/root/intake/record.jsonを残してください(キー:request、files、sha256sums_sha256、signer_pub_sha256、verified、installed)。
参考
- Ed25519の鍵:
openssl genpkey -algorithm ed25519 -out <키>・公開鍵:openssl pkey -in <키> -pubout -out <공개키>(プレースホルダーは、鍵と公開鍵です) - 署名:
openssl pkeyutl -sign -inkey <키> -rawin -in <파일> -out <서명>・検証:openssl pkeyutl -verify -pubin -inkey <공개키> -rawin -in <파일> -sigfile <서명>(プレースホルダーは、鍵、ファイル、署名、公開鍵です) - よくある間違い1: ハッシュの一覧を、絶対パスで作成してしまうこと。エアギャップ環境側で、別のディレクトリに展開すると、すべて壊れます。
- よくある間違い2: 検証をハッシュから行うこと。一覧までもすり替えられたバンドルが、合格してしまいます。
- よくある間違い3: 媒体に、秘密鍵や、信頼する公開鍵を、一緒に入れること。
持ち込み申請書は正確なバージョンで
/root/intake/request.txtに、pypi requests 2.32.3とgo rsc.io/quote v1.5.2の2行を書いてください。
1行に、エコシステム、名前、バージョンの3つの列です。範囲(>=)やlatestを書くと、取得する日ごとに結果が変わります。Goモジュールのバージョンは、vで始まります。
申請書のとおりにバンドルを取得する
/root/intake/bundle/pypi/にrequests 2.32.3と依存関係のwheelを、/root/intake/bundle/goproxy/にrsc.io/quote v1.5.2のモジュールプロキシの内容を、取得してください。
pipは、wheelだけを取得させるオプションと一緒にdownloadを、Goは、新しいモジュールキャッシュ(GOMODCACHE)で一度取得してから、そのcache/downloadを移せば済みます。Goは、取得するモジュールが使われる小さなモジュールが必要です。ステップ7で使う/root/intake/goappを、今作成しておいてください。
相対パスのハッシュ一覧
バンドルのディレクトリの中で、相対パスで/root/intake/bundle/SHA256SUMSを作成してください(一覧と署名ファイル自身は除きます)。
バンドルのディレクトリに入って、findでファイルを集めて、sha256sumに渡せば、パスが./で始まります。一覧ファイルが、自分自身を含まないように、名前で除外してください。作成したあと、同じディレクトリで、-cで確認します。
一覧に署名する
/root/intake/keys/intake.key(Ed25519)とintake.pubを作成して、SHA256SUMSの署名を、/root/intake/bundle/SHA256SUMS.sigに置いてください。
OpenSSL 3のgenpkeyで、ed25519の鍵を作成して、pkeyで公開鍵を取り出します。Ed25519は、原文をそのまま受け取るので、pkeyutlに-rawinが必要です。秘密鍵は、バンドルの外(keys/)に置きます。
秘密鍵のない媒体と、別に渡す公開鍵
/root/intake/media/bundle.tarを作成して、/root/intake/inside/に展開し、公開鍵は別に/root/intake/inside/trusted.pubとして置いてください。
tarは、bundleディレクトリ1つだけを入れます。展開すると、inside/bundleができるようにしてください。公開鍵が媒体の中にあると、すり替えた人が自分の鍵を入れれば済んでしまうので、信頼する鍵は、別の経路であらかじめ届いている必要があります。
署名が先、ハッシュが次: 検証スクリプト
/root/intake/verify.sh <번들> <공개키>を書いてください(プレースホルダーはバンドルと公開鍵です)。署名 → ハッシュ → 一覧にないファイルの順に見て、失敗したら、0以外の値で終了します。
採点は、皆さんのスクリプトを、バンドルのコピー2つ(ファイルの1バイトを変えたもの、そのファイルに合わせて一覧まで直したもの)に実行して、両方を拒否するかを見ます。2つ目は、ハッシュだけを見るスクリプトでは、防げません。
バンドルだけでインストールする
外部をふさいだ状態で、/root/intake/inside/bundleだけで、venv /root/intake/venvにrequestsを、/root/intake/goappをビルドして/root/intake/bin/helloを作成してください。
インストールの前に、ステップ6のスクリプトで、先に検証してください。pipは、インデックスを見ず、バンドルのディレクトリだけを見るように、Goは、バンドルのgoproxyディレクトリを、file://プロキシとして指し示し、新しいモジュールキャッシュでビルドします。
持ち込み記録を残す
/root/intake/record.jsonに、request(申請書の行の一覧)、files(バンドルの一覧のファイル数)、sha256sums_sha256、signer_pub_sha256(trusted.pubファイルのsha256)、verified(true)、installed(インストールしたものの一覧)を書いてください。
値は、手で写さず、ファイルから計算してください。filesは、SHA256SUMSの行数です。installedには、requestsとGoモジュールのように、実際にインストールやビルドに使ったものを、名前==バージョンまたはモジュール@バージョンの形で書きます。