検証済みパッケージだけをガイドに届ける

ShellBeginner
オンラインで実践に進む

はじめに

配布物の検証が役立つのは、その検証済みバイト列が稼働するリリースになる場合です。Waymark には、ウォーキングフェスティバルガイドを変更する前に未承認パッケージを拒否する、再利用可能な受信スクリプトが必要です。

短いシェルスクリプトを置き換え、拒否の判断を試し、主催者が要求した更新を公開します。この独立した環境には、元のガイドと専用のパッケージが用意されています。前のガイド付き実習では、署名、独立して信頼する鍵、要求されたメタデータ、アーカイブのダイジェストを学びました。今回は Python プログラミングを必要とせずに、これらを組み合わせます。

稼働中のガイドと受信スクリプトの契約を理解する

このステップでは、更新で変える内容と保持する内容を特定します。Event Guide を開き、Old Mill Walk を開いてください。元の event-guide の版は 2.0 で、集合時刻は 11:00 です。All walks で戻り、ルート表をダウンロードします。更新後も、詳細ページとダウンロードの両方が機能する必要があります。

ガイドのホームページでは、見出しの上に版が表示され、各コースのカードに集合時刻が表示されます。

更新前の稼働中の Waymark ガイド

Workspace に切り替えます。cd はディレクトリを変更します。次の絶対パスはどこからでも使えます。

cd /home/labex/project/waymark

cat はファイルを表示します。独立して提供された更新要求を読みます。

cat /opt/labex-waymark/trust/request.json

要求は製品 event-guide、バージョン 2.1 です。承認済み更新では Old Mill Walk の集合時刻を 14:00 に変更します。もう一方のコースは 09:30 のままです。初期スクリプトを読みます。

cat receive.sh

初期スクリプトはパッケージ自身からダイジェストを取得し、公開プログラムに渡します。別の配布元でも、一致するマニフェストとアーカイブを提供できます。このスクリプトは、署名も、主催者が要求した製品とバージョンもまだ検証しません。公開する前に置き換えます。

必要なインターフェースは次のとおりです。

bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY

第 1 引数は供給元のパッケージ 1 つです。第 2 引数はガイドの公開先ディレクトリで、稼働中のプロジェクトでは published です。両方とも絶対パスでも相対パスでもよく、名前が変更されたり空白を含んだりする可能性があります。パッケージラベルや公開先を固定値にしないでください。更新成功時はゼロを返します。拒否時は非ゼロを返し、既存の公開ファイルをすべて変更せずに保ちます。正当な更新では、以前のリリースと無関係なアップロードを保持します。

receive.sh の編集と一時ファイルの作成は可能です。元のパッケージ、独立した鍵と要求、準備済みの公開プログラム、その他のランタイムファイルは変更しないでください。受信スクリプトは、同じ準備済みツールと独立した信頼ファイルが利用できれば、単体で別ディレクトリへコピーしても動作する必要があります。評価では実際のガイドに加え、使い捨てディレクトリでもこの契約を検証します。

パスを入力し間違えた場合は、上の絶対パスの cd を実行し、ls candidates でラベルを確認してから再実行してください。チェックの失敗時は関連する証拠を調べます。環境のリセットは不要です。

公開前に停止する受信スクリプトを設計する

このステップでは、短いスクリプトを書く前に内容を理解します。Workspace を使い続けてください。

受信スクリプトは、関連する 3 ファイルを専用の一時ディレクトリへコピーし、そのコピーのマニフェストを検証し、製品とバージョンを独立した要求と比較して、コピーしたアーカイブと署名されたダイジェストを準備済みの公開プログラムへ渡します。ある候補を検証した後で、配布のために別の候補パスを開き直すことはしません。

公開プログラム /opt/labex-waymark/publish.py は、準備済みのアーカイブ処理コードです。渡されたアーカイブを 1 つのバイトバッファーに読み込み、そのバッファーを渡されたダイジェストと照合し、site.json と routes.csv だけを安全に展開して、同じバイト列を公開します。以前のリリースと uploads/ は保持されます。ただし、誰の署名を信頼するか、主催者がどの製品とバージョンを要求したかは判断しません。その判断は受信スクリプトが担当します。

次のシェル機能を使います。

  • #!/bin/bash はスクリプトのシェルを示します。bash receive.sh で実行しても Bash を明示的に選べます。
  • set -euo pipefail は、処理されないコマンド失敗、未設定変数、パイプライン失敗が発生したらスクリプトを停止します。署名の失敗後に公開へ進んではいけません。
  • $1 と $2 は 2 つの引数です。candidate=$1 は値を保存し、"$candidate" は空白を含めて 1 つの引数に展開します。$# は引数の数で、[ "$#" -ne 2 ] はその数が 2 ではないかを調べます。if、then、fi は条件分岐の区切りです。
  • $(command) はコマンドの出力を保存します。mktemp -d は一意の一時ディレクトリを作ります。trap '...' EXIT はスクリプト終了時に後片付けを実行します。ここで rm -rf -- "$stage" が削除するのは、生成した一時ディレクトリだけです。プロジェクトディレクトリに置き換えてはいけません。-- はオプションの解釈を終え、パスをデータとして扱わせます。
  • cp -- SOURCE SOURCE SOURCE DIRECTORY/ は 3 つのファイルをコピーします。末尾のスラッシュはコピー先ディレクトリを示します。
  • jq -r .product FILE は JSON の引用符なしで文字列を読みます。jq -e は結果が偽なら失敗ステータスを返します。--arg product "$product" は、$product という文字列変数をフィルターへ渡します。and はすべての条件が成立することを要求します。単一引用符は jq 用のフィルターテキストを保持します。> /dev/null は真偽値の表示を捨てますが、終了ステータスは保持します。
  • echo ... >&2 はエラーストリームにエラーを書き、exit 2 は非ゼロのステータスで終了します。

署名コマンドは、前の実習と同じ独立して固定された公開鍵を使います。--bundle はコピーした署名バンドルを指し、最後の引数はコピーしたマニフェストです。--insecure-ignore-tlog は、このオフラインの合成データに対する明示的な制限です。公開ログの検証を省略しますが、固定鍵による署名は検証します。この受信スクリプトは、公開 ID や透明性ログの検証器ではありません。

最後の Python コマンドは準備済みの公開プログラムを呼び出します。Python を書く必要はありません。コピーしたアーカイブ、検証済みマニフェストのダイジェスト、指定された公開先を受け取ります。ダイジェストがアーカイブのバイト列と一致しなければ、既存のガイドに触れる前に更新を拒否します。

再利用可能な受信スクリプトを保存する

このステップでは、確認した仕組みを使って受信スクリプトを書きます。Workspace の /home/labex/project/waymark から始めてください。

次のコマンドは receive.sh を置き換えます。cat > receive.sh は、渡されたテキストをファイルへリダイレクトします。<<'RECEIVER' はヒアドキュメントの開始です。RECEIVER だけの行までが、すべてファイル内容になります。この区切りを引用することで、$1、$stage、jq の変数が、後でスクリプトを実行するときまで保持されます。最後の区切りも含め、ブロック全体を貼り付けてください。

cat > receive.sh <<'RECEIVER'
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 2 ]; then
  echo "Usage: bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY" >&2
  exit 2
fi
candidate=$1
destination=$2
stage=$(mktemp -d)
trap 'rm -rf -- "$stage"' EXIT
cp -- "$candidate/manifest.json" "$candidate/manifest.sigstore.json" "$candidate/guide.tar.gz" "$stage/"
/opt/labex-waymark/bin/cosign verify-blob --key /opt/labex-waymark/trust/publisher.pub --bundle "$stage/manifest.sigstore.json" --insecure-ignore-tlog "$stage/manifest.json"
product=$(jq -r .product /opt/labex-waymark/trust/request.json)
version=$(jq -r .version /opt/labex-waymark/trust/request.json)
jq -e --arg product "$product" --arg version "$version" '.product == $product and .version == $version and .archive == "guide.tar.gz"' "$stage/manifest.json" > /dev/null
digest=$(jq -r .sha256 "$stage/manifest.json")
python3 /opt/labex-waymark/publish.py "$stage/guide.tar.gz" "$digest" "$destination"
RECEIVER

内容を読み返します。

cat receive.sh

公開鍵と要求が /opt/labex-waymark/trust/ から取得され、マニフェスト、バンドル、アーカイブがすべて "$stage" から取得されていることを確認してください。コピー後は、元の候補ディレクトリを再読込していないことに注目します。コピー後に供給元の配置を変更したり名前を変えたりしても、公開プログラムへ渡すバイト列を入れ替えることはできません。

終了区切りを省略してターミナルに継続プロンプトが出た場合は、RECEIVER だけを 1 行に入力して Enter を押します。貼り付けが間違っていた場合は、ヒアドキュメント全体を繰り返してください。置き換わるのは receive.sh だけです。不完全な受信スクリプトは実行しないでください。

準備済みのポリシーチェックは、通常アカウントで、使い捨ての公開ディレクトリに対してスクリプトを変更せずに実行します。実際に公開されたファイル、拒否するケース、保全動作を確認し、特定のコマンドの綴りを探すものではありません。

sudo python3 /opt/labex-waymark/verify.py policy

sudo が必要なのは、準備済みチェックが隔離した通常アカウントを使うためで、受信スクリプトには不要です。このチェックは稼働中のガイドに触れません。最後の行では、要求された署名付きガイドの受け入れと、4 つの未承認ケースの拒否が確認されるはずです。失敗したら、報告された判断を調べ、次へ進む前に receive.sh を修正してください。

稼働中のガイドを更新する前に拒否を観察する

このステップでは、元のガイドが利用可能なまま、受信スクリプトが 3 種類の失敗で停止することを確認します。Workspace の /home/labex/project/waymark から始めてください。

まず、別の配布元が署名したパッケージを試します。署名検証エラーと非ゼロのステータスになる想定です。

bash receive.sh candidates/parcel-102 published

$? は直前のコマンドの終了ステータスを保持します。ほかのコマンドを実行する前に読みます。

echo $?

次に、本物ですが旧版のパッケージを試します。署名検証は成功し、その後、要求された製品とバージョンの比較が偽となるため、非ゼロのステータスになる想定です。

bash receive.sh candidates/parcel-101 published
echo $?

jq の比較は意図的に何も表示しません。非ゼロのステータスが拒否を示します。有効な署名があっても、独立した要求が優先されます。

最後に、変更済みアーカイブを試します。署名は成功しますが、公開プログラムがダイジェスト不一致を報告し、既存のガイドを保持する想定です。

bash receive.sh candidates/parcel-103 published
echo $?

Event Guide に切り替え、ページを更新します。版は 2.0、Old Mill Walk の集合時刻は 11:00 のままでなければいけません。All walks で戻り、ルート表をダウンロードし、Visitor notice を開いてください。すべてを拒否する受信スクリプトでも古いガイドは保たれるため、正当な更新の成功も必要です。

ファイルの欠落や接続エラーは、ここで想定するポリシー拒否とは異なります。パッケージパスの綴りを間違えたら、ワークスペースの絶対パスに戻り、candidates を一覧表示して再実行します。Web サイトを利用できない場合は、報告されたエラーを調べてください。それを拒否の成功とは呼べません。

承認された新しいガイドを公開して使う

このステップでは、検証済み更新を配布し、実際のページとダウンロードを使います。Workspace の /home/labex/project/waymark から始めてください。

承認済みパッケージを受信します。

bash receive.sh candidates/parcel-104 published

Verified OK と次の表示になるはずです。

Published event-guide 2.1. Previous releases and uploads kept.

すぐに終了ステータスを確認します。

echo $?

0 となるはずです。Event Guide に切り替え、ページを更新します。ホームページには event-guide の版 2.1 が表示されます。Old Mill Walk を開いて 14:00 を確認し、All walks から Riverside Welcome を開いて集合時刻 09:30 が保たれていることを確認します。All walks で戻り、新しいルート表をダウンロードします。2 行とも詳細ページと一致しなければいけません。Visitor notice を開き、元の案内が残っていることを確認します。

承認済みホームページには版 2.1 が表示されます。Old Mill Walk の集合時刻 14:00 に注目してください。

公開された承認済み Waymark ガイド

Workspace に切り替えます。稼働状態のセルフチェックは、稼働中の詳細ページ、ダウンロードされるルート表のバイト列、現在のガイドファイル、保持された旧リリースとお知らせを読みます。

python3 /opt/labex-waymark/verify.py live

次の表示になるはずです。

The running guide and download match the approved release. Previous releases and visitor notice are preserved.

ls で保持されたリリースディレクトリを一覧表示します。

ls published/releases

以前の event-guide-2.0-... と新しい event-guide-2.1-... のリリースがあるはずです。末尾の文字は実際のアーカイブダイジェストに由来し、覚える値ではありません。

稼働状態のチェックが失敗したら、メッセージを使い、受信判断の問題なのか、Web サイトが利用できないのか、元のファイルが変更されたのかを区別します。必要に応じて受信スクリプトを修正し、元の承認済みパッケージを再度受信してください。同じ有効な更新の繰り返しは安全です。公開プログラムは同一リリースを再利用し、以前のファイルを保持します。稼働状態のセルフチェックを再実行して結果を確認してください。

まとめ

配布物をそのまま信頼する受信スクリプトを、独立した配布元の鍵と主催者の要求を確認し、署名されたアーカイブのダイジェストを実際に公開するバイト列に結び付けるものへ置き換えました。署名者の不一致、本物の旧リリース、変更済みアーカイブは拒否され、元のサービスは利用可能なままでした。

承認済みの更新は、詳細ページと一致するルート表のダウンロードが機能する実際のガイドになりました。チャレンジでは同じツールをアクセシブルガイドに適用します。そこには、別製品向けの本物の署名付きパッケージも含まれます。