egressview

Release署名鍵の運用手順

English

この手順は、正式な署名付きportable releaseの信頼起点を確立します。CIは毎回破棄する 試験鍵を使うだけで、releaseの真正性は保証しません。 release-signing/trusted-fingerprints.jsonにactiveな鍵が登録されるまでは、正式な project鍵で署名済みとは表現しません。

署名鍵

リリース鍵はAWS KMSの非対称鍵です。秘密鍵はKMS内で生成され取り出せないため、 保管・バックアップ・紛失・漏えいの対象になる鍵ファイルが存在しません。署名には ファイルの所持ではなく、認証されたAWS principalが必要です。

   
エイリアス alias/egressview-release
リージョン ap-northeast-1
鍵仕様 ECC_NIST_EDWARDS25519、SIGN_VERIFY
署名アルゴリズム ED25519_SHA_512、MessageType: RAW

署名できるのはEgressViewRelease permission setだけです。 この制限はIAMではなく キーポリシーにあります。アカウント管理用のstatementはkms:Signを意図的に含まないため、 管理者は鍵のrotation・ポリシー変更・削除予約はできても、その鍵でリリースに署名することは できません。他のロール、とりわけEC2インスタンスロールへkms:Signを付与しないでください。 署名はメンテナのworkstationから行うもので、サーバ側にこの権限は不要です。

リリース前にサインインします。

aws sso login --profile egressview-release

公開鍵とfingerprintの取得:

aws kms get-public-key --profile egressview-release \
  --key-id alias/egressview-release --query PublicKey --output text \
  | base64 -d > /tmp/egressview-release.der
openssl pkey -pubin -inform DER -in /tmp/egressview-release.der \
  -out /tmp/egressview-release.pub.pem
node scripts/release-key-fingerprint.js /tmp/egressview-release.pub.pem

fingerprintの算出は独立してもう一度実行し、全桁を比較してください。先頭や末尾だけの 比較は不可です。

Fingerprintの登録・公開

公開鍵とactive recordをrelease-signing/trusted-fingerprints.jsonへreview付きPRで 追加します。key ID、完全なSHA256:<64文字の小文字hex> fingerprint、生成日、公開鍵 pathを記録します。同じ完全なfingerprintを次の全経路で公開します。

Releaseに同梱した.pub.pemだけでは信頼起点になりません。リポジトリも同様です。 利用者は、このリポジトリの侵害が及ばない経路から取得したfingerprintと照合します。

署名・公開

メンテナのワークステーションで、1コマンドです。 タグをcheckoutしてから、

aws sso login --sso-session egressview
AWS_PROFILE=egressview-release npm run release:publish -- --tag v2.0.3

これが手順のすべてです。1コマンドにしているのは意図的です。 2.0.0・2.0.1・2.0.2はいずれも署名済み資産なしで公開されました。パイプラインが失敗したのではなく、実行されなかったのです。「リリースする」と「署名する」が、人が順に思い出して実行する別々の行為だったためです。3回連続で飛んだという事実は、覚えていることが管理策ではないことの証拠です。

順序が本質であり、コマンドがそれを強制します。

  1. HEADがタグと厳密に一致し、作業ツリーがclean、npm run release:checkが通ることを確認するまで着手しません。dirtyな状態から作ったリリースはタグが指すものではなく、後から署名しても直りません。
  2. 既に公開済みのタグは、ビルドと署名を費やす前に拒否します。
  3. KMS鍵でビルドと署名を行い、いま作ったものの由来を記録して、その証跡がその成果物を指していることを確認します。
  4. バンドルを検証し、さらに改竄3ケースが落ちることを証明します(アーカイブの改変、チェックサムの書き換え、署名の偽造)。
  5. fingerprintをtrust registryとDNS TXTレコードに照合します。DNSはこのリポジトリとは別系統の資格情報で提供されています。成果物の隣にしか公開されていないfingerprintは何も証明しません。
  6. draftとしてリリースを作成し、4点をアップロードします。
  7. その資産をGitHubから落とし直して検証します。手元のディスク上のものではなく、リリースページが実際に配信するものを見ます。
  8. ここまで通って初めて、draftを公開へ切り替えます。

draft先行が、未署名のリリースを「起こりにくい」ではなく「作れない」にしている点です。 どこかで落ちれば残るのはdraftであって、「検証手段が無い公開済みリリース」にはなりません。2.0.xが置かれていたのは、まさにその状態でした。

--dry-run を付けると、GitHub上に何も作らずに5番までを実行できます。

provenance(由来の証跡)

署名と並んで、リリースは.intoto.jsonlを持ちます。SLSA v1のprovenanceを収めたDSSEエンベロープで、同じ鍵で署名されています。署名は「この成果物は我々のものだ」と言い、provenanceは「どのcommitから作られたか」を言います。

ビルドはhermeticではなく、この文書はそのふりをしません。 リリースはメンテナのワークステーションで作られます(意図的です——下記参照)。したがってこれはSLSA Build L2であってL3ではなく、x-slsaBuildLevelとbuilderの注記にその旨を記録しています。ホスト型ビルダーのprovenanceと見比べる人が、このプロジェクトを知らなくても違いを見て取れるように。

記録するのはtagではなくcommitです。tagは動かせます。

node scripts/verify-provenance.js --artifact egressview-offline-<v>.tar.gz --provenance egressview-offline-<v>.tar.gz.intoto.jsonl --public-key egressview-offline-<v>.tar.gz.pub.pem

確認するのは2つで、重要なのは2つ目です。エンベロープの署名が正しいこと、そしてその中のdigestが手元の成果物のdigestであること。 別のファイルについての文書に有効な署名が付いていても、目の前のファイルについては何も証明しません。

その後ろのゲート

.github/workflows/release-gate.yml が、リリースの公開時と編集時、および週次で npm run release:verify-published を実行します。手動実行に検査対象を選ばせません — 実行する人が何を検査するか選べるものはゲートではないからです。古いタグを1つ調べたいときは、手元で npm run release:verify-published -- --tag <tag> を実行します。リリースページが配信するものをダウンロードして検証するので、GitHubのWeb画面など別経路で作られたリリースも、公開後に資産が削除・差し替えられた場合も捕まえます。AWSアクセスは不要です。

この手順より前に公開され、現在の検証器を通過できないリリースは、互換性のため旧名を維持した release-signing/unsigned-releases.json に種別と理由付きで記録してあります。未署名版に加え、署名はあるものの現在のパッケージ方針が拒否する拡張属性を含む旧版も対象です。ゲートは永久に失敗するのではなく既知の事実として報告します。常に失敗するゲートは、人が無視することを学ぶゲートです。 方針の発効日以降に公開されたリリースの登録はテストが拒否するため、このリストが新しい失敗を黙らせる抜け道になることはありません。

二度と公開してはいけないAgentの版

Agentは publish-agent-release.js で dl.egressview.com へ公開するもので、GitHubリリースではなく、プラットフォームごとに独自の版番号の系列を持ちます。ビルドまたは公開されたが二度と使ってはいけない版は、release-signing/withdrawn-agent-releases.json に後継と理由を添えて記録します。

publish-agent-release.js はこのリストを読み、dry runでも本番でも公開を拒否します。人の記憶の中にしかない取り下げは、取り下げではありません。 Windowsの 0.1.117 は、どんな大きさのDBでも終わらない移行を含んだまま6時間配信されていましたが、それを再び公開することを止めるものはリポジトリの中に何もありませんでした。

各項目は必ず後継を指します。「これを使うな」は、移る先が無ければ指示の半分でしかないからです。後継が自分より古い場合、あるいは後継自身が取り下げられている場合は、テストが拒否します。テストは公開スクリプトを読むのではなく実行します —— 最初に書いた版はソースを拒否メッセージで grep するだけで、検査を null に置き換えても探していた文字列はすべて残っていたため通ってしまいました。

署名はワークステーションに置いたままにします

GitHub Actionsに kms:Sign のOIDCロールを与えて人を完全に排除する案は魅力的ですが、それは署名できる主体を「SSOセッションを持つ人がワークステーションで」から「ワークフローで実行される何か」へ広げます。鍵ポリシーで署名を専用プリンシパルに限定し、trust anchorを意図的にリポジトリの外に置いているプロジェクトで、規律の問題をサプライチェーンの問題に置き換える取引です。鍵は今の場所に置き、ワークステーション側の作業を1コマンドにします。

offline:bundle の --private-key は引き続き利用でき、KMSの代わりにローカルの鍵ファイルを取ります。すべてのpull requestで使い捨て鍵により機構を動かし続けるCIのため、および独自に配布物を作る人のためのものです。公式リリースを作るものではありません。

リリースノート

成果物名、チェックサム、完全なfingerprint、署名鍵ID、検証ガイドへのリンクを必ず記載します。リリース番号とHubのバージョンが異なる場合(2.0.2はHub 1.10.0を含み、成果物名はHubのバージョンに従います)は両方を明記し、成果物名とノートが黙って食い違わないようにします。

Rotation・漏えい対応

計画rotationでは、旧鍵と新鍵で個別に署名したartifactを1 releaseだけ併記し、両fingerprintを 独立経路でも告知します。overlap releaseの公開後に旧鍵をretiredへ変更します。

overlap releaseは旧鍵で署名する必要があるため、rotation時にKMS鍵の削除を予約しないで ください。 待機期間が過ぎると削除は取り消せません。検証は同梱の公開鍵とopensslだけで 完結し鍵を必要としないので、旧鍵を残すコストは$1/月にすぎず、何かあった時に再度署名できる 余地を買えます。明確な理由がない限り保持してください。

漏えいの疑いがあればreleaseを直ちに停止します。鍵をrevokedにし、完全なfingerprintと incident日を全公開経路で告知し、KMS鍵を無効化するか、release permission setからkms:Signを外します。新鍵を作成し、review済みtagから影響artifactを再構築します。既存GitHub Releaseのartifactを、 明示的なincident noticeと新versionなしに差し替えません。