OpenPubkey を使用した Docker 公式イメージの署名

DockerCon 2023 では、 BastionZero と Docker が共同で開発し、最近オープンソース化して Linux Foundation に寄付したプロジェクトである OpenPubkey を、 Docker Official Images (DOI) の署名ソリューションの一部として使用する意向 を発表しました 。署名アプローチの詳細については、DockerCon の講演「Docker official Imagesでのソフトウェアサプライチェーンの構築」で説明しました。 

この投稿では、更新されたDOI署名戦略について説明します。 基本的なコンテナイメージ署名の仕組みから始めて、公開鍵と秘密鍵のペア、認証局、更新フレームワーク(TUF)、タイムスタンプログ、透明性ログ、Open ID Connect を使用した ID 検証など、現在一般的なイメージ署名フローに徐々に構築していきます。

これらの仕組みを説明した後、OpenPubkeyを最近のいくつかの機能強化を含めて活用して、フローをスムーズにし、検証者が信頼する必要のあるサードパーティエンティティの数を減らす方法を示します。

うまくいけば、この段階的な物語は、ソフトウェアアーティファクト署名に不慣れな人や、この提案が現在のアプローチとどのように異なるかを探している人に役立つでしょう。 いつものように、Dockerは開発者エクスペリエンスの向上、開発者が付加価値に費やす時間の増加、および苦労に費やす時間の削減に取り組んでいます。

この投稿で説明するアプローチは、Dockerユーザーが毎日使用するDOIイメージの整合性と出所を簡単に検証できるようにすることで、ソフトウェアサプライチェーンのセキュリティを改善できるようにすることを目的としています。

バナーオープンパブキーのお知らせ

コンテナー イメージへの署名

エンティティは、デジタル署名を作成してイメージに追加することで、コンテナー イメージをビルドしたことを証明できます。 このプロセスは署名と呼ばれます。 イメージに署名するために、エンティティは公開キーと秘密キーのペアを作成できます。 秘密キーは秘密にしておく必要があり、公開キーはパブリックに共有できます。

イメージが署名されると、秘密キーとイメージのダイジェストを使用して署名が生成されます。 公開キーを持っている人は誰でも、秘密キーを持つユーザーによって署名が作成されたことを検証できます (図 1)。

ドッカーの公式イメージへの署名図1
図 1: イメージは秘密キーを使用して署名され、署名されたイメージになります。 次のステップとして、対応する公開鍵を使用してイメージの署名を検証し、その信頼性を確認します。

コンテナー イメージに署名する方法を、単純なアプローチから始めて、イメージ署名の現在の現状まで構築し、Docker が提案するソリューションで終わる方法について説明します。 このソリューションが設計されたユースケースであるため、例としてDOIビルドプロセスの一部としてDocker公式イメージ(DOI)に署名します。

この投稿全体の図では、署名を表すために色付きのシールを使用します。 シールの色は、署名された秘密鍵の色と一致します(図2)。

ドッカーの公式イメージへの署名図2
図2:1234(赤)と5678(黄色)というラベルの付いた2つの異なる秘密鍵は、対応する一意の署名を生成します。

検証者が公開鍵でイメージ署名を検証した後に知っているのは、イメージが公開鍵に関連付けられた秘密鍵で署名されたことだけであることに注意してください。 イメージを信頼するには、検証者はキーペアの所有者の署名 ID を確認する必要があります (図 3)。

ドッカーの公式イメージへの署名図3
図3:署名されたイメージをレジストリにプッシュするDOIビルダーと、同じイメージをプルする検証者。 この時点で、検証者はどのキーがイメージに署名したかのみを認識し、誰がキーを制御しているかは認識しません。

ID と証明書

公開鍵と秘密鍵のペアの所有者をどのように確認しますか? これが証明書の目的であり、公開鍵と名前を含む単純なデータ構造です。 証明書は、サブジェクトと呼ばれる名前を公開キーにバインドします。 このデータ構造は通常、証明書の発行者と呼ばれる認証局 (CA) によって署名されます。 

証明書は、対応するキーで作成された署名と共に配布できます。 つまり、イメージのコンシューマーは、イメージの署名に使用されるすべての公開キーの所有者を確認する必要はありません。 代わりに、はるかに小さな CA 証明書のセットに依存することができます。 これは、WebブラウザがHTTPSを使用する無数のWebサイトとの信頼を確立するために数十のルートCA証明書のセットを持っている方法に似ています。

DOI 署名の例に戻ると、1234 公開キーと Docker 公式イメージ (DOI) ビルダー名をバインドする証明書を配布すると、証明書を発行した CA を信頼している限り、誰でも 1234 秘密キーによって署名された イメージが DOI ビルダー によって署名されていることを確認できます (図 4)。

ドッカーの公式イメージへの署名 図4
図4:DOIビルダーは、証明書を返す認証局(CA)に身元の証明を提供します。 DOI ビルダーは、署名されたイメージと証明書をレジストリにプッシュします。 検証者は署名されたイメージと、そのイメージがDOIビルダーによって作成されたことを検証できます。

信頼ポリシー

証明書は、どの公開鍵がどのエンティティに属しているかという問題を解決しますが、どのエンティティがイメージに署名すること になっていた かをどうやって知ることができますか? このためには、信頼ポリシー、つまりイメージへの署名が許可されているエンティティを詳述する署名付きメタデータが必要です。 Docker公式イメージの場合、信頼ポリシーには、DOIビルドサーバーがイメージに署名する必要があることが記載されます。

悪意のある当事者がポリシーを変更できる場合、悪意のある当事者のキーが署名を許可されるべきではない画像に署名することを許可されているとクライアントに信じ込ませることができるため、信頼ポリシーが安全な方法で更新されるようにする必要があります。 安全な信頼ポリシーの更新を確実にするために、任意のファイルに更新を安全に配布するためのメカニズムである更新フレームワーク(TUF)(仕様)を使用します。

TUF リポジトリは、キーの階層を使用して、リポジトリ内のファイルのマニフェストに署名します。 マニフェストと呼ばれるファイル インデックスは、自動化を有効にするためにオンラインで保持されるキーで署名され、オンライン署名キーはオフラインのルート キーで署名されます。 これにより、オンラインキーが侵害された場合にリポジトリを回復できます。

TUF リポジトリ内のファイルに更新をダウンロードするクライアントは、最初に署名済みマニフェストの最新コピーを取得し、マニフェストの署名が検証されていることを確認する必要があります。 その後、実際のファイルを取得できます。

TUFリポジトリが作成されると、配布メカニズムが信頼されていない場合でも、選択した任意の方法で配布できます。 Docker Hub レジストリを使用して配布します (図 5)。

ドッカーの公式イメージへの署名 図5
図5:TUFリポジトリは、イメージがDOIビルダーによって署名されるべきであることを示す信頼ポリシーを提供します。 DOIビルダーは、証明書を返す認証局(CA)に身元の証明を提供します。 DOI ビルダーは、署名付きイメージ、CA からの証明書、および TUF ポリシーをレジストリにプッシュします。 検証者は、署名されたイメージと、そのイメージが信頼ポリシーで定義された ID によって作成されたことを検証できます。

証明書の有効期限とタイムスタンプ

前のセクションでは、証明書を ID から公開キーへの単なるバインドとして説明しました。 実際には、証明書にはいくつかの追加データが含まれています。 重要な詳細の1つは有効期限です。 通常、証明書は有効期限後に信頼されるべきではありません。 イメージの署名 (図 5 参照) は、添付された証明書の有効期限まで有効です。 署名の限られた寿命は、画像を長持ちさせる(証明書よりも長持ちさせる)必要があるため、望ましくありません。

この問題は、タイムスタンプ機関 (TSA) を使用することで解決できます。 TSA は何らかのデータを受け取り、そのデータを現在時刻にバンドルし、バンドルに署名してから返却します。 TSA を利用することで、TSA を信頼する人は誰でも、バンドルされた時間にデータが存在していたことを検証できます。

署名を TSA に送信し、現在のタイムスタンプを署名にバンドルさせることができます。 次に、証明書を検証するときに、バンドルされたタイムスタンプを「現在の時刻」として使用できます。 タイムスタンプは、署名の作成時に証明書の有効期限が切れていなかったことを証明します。 TSA の証明書も期限切れになり、その時点で TSA が作成した署名付きタイムスタンプもすべて期限切れになります。 TSA証明書は通常、長期間(10+年)持続します(図6)。

ドッカーの公式イメージへの署名 図6
図6:DOIビルダーは、証明書を返す認証局(CA)にIDの証明を提供します。 DOI ビルダーは、署名と現在の時刻を含む署名付きバンドルを提供するタイムスタンプ機関 (TSA) に画像署名を送信します。 DOI ビルダーは、署名されたイメージ、CA からの証明書、および TSA によって署名されたバンドルをレジストリにプッシュします。 検証者は、署名されたイメージと、そのイメージが特定の時間にDOI builderによって作成されたことを検証できます。

オープンID コネクト

これまで、CA が署名者の ID を確認する方法 (前の図の [ID の証明] ボックス) を無視してきました。 この検証の仕組みは CA によって異なりますが、1 つのアプローチは、OpenID Connect (OIDC) を使用してこの検証をサードパーティにアウトソーシングすることです。

OIDC フロー全体については説明しませんが、主な手順は次のとおりです。

  • 署名者は、OIDC プロバイダー (Google、GitHub、Microsoft など) で認証されます。
  • OIDC プロバイダーは、署名者が自分の ID を証明するために使用できる署名付きトークンである ID トークンを発行します。
  • ID トークンには、ID トークンを使用して署名者の ID を検証する対象者を指定する対象ユーザーが含まれます。 対象読者は認証局です。 ID トークンは、他の対象ユーザーによって拒否される必要があります。

CA は、OIDC プロバイダーを信頼し、ID トークンの対象ユーザー要求を検証する方法を理解する必要があります。

OIDC ID トークンは、OIDC プロバイダーの秘密キーを使用して署名されます。 対応する公開キーは、OIDC プロバイダーによってホストされている検出可能な HTTP エンドポイントから配布されます。

署名付きDOIはGitHubアクションを使用してビルドされ、GitHubアクションはGitHubアクションOIDCプロバイダーでビルドプロセスを自動的に認証できるため、IDトークンをビルドプロセスに利用できます(図7)。

ドッカーの公式イメージへの署名 図7
図7:DOIビルダーはOIDCを使用してGitHubアクションにIDを検証し、GitHubアクションはDOIビルダーがIDを検証するためにCAに送信するトークンを提供します。 CA は GitHub アクションでトークンを検証し、DOI ビルダーに証明書を返します。

重要な侵害

この投稿の冒頭で、システムの安全性を維持するために秘密鍵を非公開にする必要があると述べました。 署名者の秘密鍵が侵害された場合、悪意のある当事者は、署名者によって署名されていることを検証できる署名を作成できます。

これらのキーが侵害されるリスクを軽減するためのいくつかの方法を見ていきましょう。

エフェメラルキー

秘密鍵の侵害のリスクを減らす良い方法は、秘密鍵をどこにも保存しないことです。 キーペアはメモリ内で生成し、一度使用すると、秘密キーを破棄できます。 つまり、証明書も使い捨てであり、署名が作成されるたびに CA に新しい証明書を要求する必要があります。

透明性ログ

エフェメラルキーは署名キー自体に適していますが、他にも侵害される可能性のあるものがあります。

  • CA の秘密キー (実際には、これは一時的なものではありません)
  • OIDC プロバイダーの秘密キー (実際には、これは一時的なものではありません)
  • OIDC アカウントの資格情報

これらのキー/資格情報は非公開にする必要がありますが、偶発的な侵害が発生した場合に備えて、誤用を検出する方法が必要です。 この状況では、透明性ログ(TL)が役立ちます。

透過性ログは、追加専用の改ざん防止データ ストアです。 データがログに書き込まれると、ログのオペレーターによって署名された受信確認が返され、ログに含まれていることの証拠として使用できます。 ログを監視して、疑わしいアクティビティをチェックすることもできます。

透明性ログを使用してすべての署名を保存し、TLレシートを署名にバンドルできます。 署名が有効なTLレシートにバンドルされている場合にのみ、署名を有効として受け入れることができます。 署名はエントリがTLにある場合にのみ有効であるため、偽の署名を作成する悪意のある当事者もTLにエントリを公開する必要があります。 TLは署名者が監視でき、署名者は作成していないログにシグネチャがあることに気付いた場合にアラームを鳴らすことができます(図8)。 ログは、関係する第三者が監視して、正しく見えない署名をチェックすることもできます(図9)。

透明性ログを使用して、CA によって発行された証明書を保存することもできます。 証明書は、TLレシートが付属している場合にのみ有効です。 これはTLS証明書の仕組みでもあり、TLレシートが添付されている場合にのみブラウザによって信頼されます。

TL レシートにはタイムスタンプも含まれているので、TL は追加機能を提供しながら、TSA の役割を完全に置き換えることができます。

ドッカーの公式イメージへの署名 図8
図8:DOIビルダーは、署名されたイメージと証明書をCAから透過ログ(TL)に送信し、TLはTLに署名を追加して、現在の時刻の領収書を返します。 モニターは、署名が特定の時間にDOIビルダーによって行われたことを観察できます。
ドッカーの公式イメージへの署名 図9
図 9: 悪意のある第三者が、ハッキングされた OIDC 資格情報を使用して CA から受け取った偽の証明書を使用してイメージに署名する例。 モニターは何かが正しくないことを識別することができます。

盗まれた秘密鍵と正当な証明書を使用した同様の攻撃も、この方法で検出できます。

署名の現状の概要

ここまでのすべては、アーティファクト署名の現状を説明しています。 これまでに説明したすべてのコンポーネントをまとめて要約してみましょう(図10)。 これらは:

  • OIDC プロバイダー (一部のエンティティの ID を確認するため)
  • ID を公開キーにバインドする証明書を発行する認証局
  • 署名者: 対応する秘密鍵でイメージに署名します。
  • 透明性ログ(TL):署名を保存し、署名されたタイムスタンプ付きの領収書を返します
  • TUF リポジトリ (信頼ポリシーを配布する)
  • 悪意のある動作を検出するための透明性ログモニター
  • レジストリ: すべてのアーティファクトを格納する
  • イメージの署名を検証するためのクライアント
ドッカーの公式イメージへの署名 図11
図10:前のすべての図に基づいて、DOIビルダーはOIDCを使用してGitHubアクションに自分自身を識別し、DOIビルダーがCAに送信してIDを確認するトークンを提供します。 CA は GitHub アクションでトークンを検証し、DOI ビルダーに証明書を返します。 DOI ビルダーは、署名されたイメージと証明書を CA から透明性ログ(TL)に送信し、透過ログ(TL)は署名を TL に追加し、現在の時刻の領収書を返します。 DOI ビルダーは、署名されたイメージ、CA からの証明書、および TL レシートをレジストリにプッシュします。 検証者は、署名されたイメージと、特定の時点で信頼ポリシーと一致する ID によってイメージが作成されたことを検証できます。 モニターは、署名が特定の時間にDOIビルダーによって行われたことを観察できます。

署名を検証するクライアントは、以下を信頼する必要があります。

  • The CA
  • ティッカー
  • OIDC プロバイダー (推移的には、CA が OIDC プロバイダーからの ID トークンを正しく検証することを信頼する必要があります)
  • TUFリポジトリの署名者

信頼できることはたくさんあります。 これらのエンティティのいずれかが侵害されたり、悪意を持って行動したりすると、システムのセキュリティが侵害されます。 透明性ログを監視することでこのような侵害を検出できたとしても、修復は困難な場合があります。 ソリューションの全体的なセキュリティを損なうことなく、これらの信頼ポイントのいずれかを削除すると、改善されます。

Dockerが提案する署名ソリューション

CA は、証明書を発行する前に、秘密キーの制御と ID の制御を確認する必要があります。 図 10 では、CA は ID 検証を OIDC プロバイダーにアウトソーシングしています。 OIDCプロバイダーを使用してIDを検証できますが、それを使用して秘密鍵の制御を確認できますか? できることがわかりました。

OpenPubkey は、OIDC アイデンティティを公開鍵にバインドするためのプロトコルです。 それがどのように機能するかについての詳細は OpenPubkeyの論文にありますが、以下は簡単な説明です。 

OIDC では、要求の一部として OIDC プロバイダーに送信する一意の乱数をお勧めします。 この番号はナンスと呼ばれます。

ノンスが送信された場合、OIDC プロバイダーは ID トークンと呼ばれる署名付き JWT (JSON Web トークン) でそれを返す必要があります。 署名者の公開鍵のハッシュとランダムなノイズとしてナンスを構築することで、これを有利に使用できます(ナンスは依然としてランダムでなければならないため)。 その後、署名者は、OIDC プロバイダーからの ID トークンを公開キーとランダム ノイズでバンドルし、その秘密キーを使用してバンドルに署名できます。

結果のトークン (PK トークンと呼ばれます) は、検証者が OIDC プロバイダーを信頼している限り、特定の時点での OIDC ID の制御と秘密キーの制御を証明します。 つまり、PK トークンは、この時点までのすべての署名フローで CA によって提供される証明書と同じ役割を果たしますが、CA への信頼は必要ありません。 このトークンは、証明書と同じ方法で署名と共に配布できます。

ただし、OIDC IDトークンは、短時間で検証および破棄されるように設計されています。 トークンを検証するための公開キーは、OIDC プロバイダーによってホストされている API エンドポイントから入手できます。 これらのキーは頻繁に (数週間または数か月ごとに) ローテーションされ、現在、無効になったキーによって署名されたトークンを検証する方法はありません。 そのため、履歴キーのログを使用して、ローテーションされた OIDC プロバイダー キーで署名された PK トークンを検証する必要があります。 このログは検証者にとって追加の信頼ポイントであるため、ある信頼ポイント(CA)を削除し、別の信頼ポイント(公開鍵のログ)に置き換えたように見える場合があります。 DOIについては、信頼ポリシーの配布に使用されるTUFリポジトリとの別の信頼点をすでに追加しています。 このTUFリポジトリを使用して、公開鍵のログを配布することもできます。

ドッカーの公式イメージへの署名 図10
図 11: OIDC を使用すると、DOI ビルダーは GitHub アクションに対して自身を識別し、OIDC ID を公開鍵にバインドする ID トークンを提供します。 DOI ビルダーは、署名された画像と PK トークンを透過性ログ(TL)に送信し、透過性ログ(TL)は署名を追加して、現在の時刻のレシートを返します。 DOI ビルダーは、署名されたイメージ、PK トークン、および TL レシートをレジストリにプッシュします。 検証者は、署名されたイメージと、特定の時点で信頼ポリシーと一致する ID によってイメージが作成されたことを検証できます。 モニターは、署名が特定の時間にDOIビルダーによって行われたことを観察できます。

OpenPubkey の機能強化

最初に定式化されたように、OpenPubkeyは、説明したようにコード署名ワークフローをサポートするようには設計されていません。その結果、ここで説明する実装にはいくつかの欠点があります。 以下では、それぞれの欠点とそれに関連する解決策について説明します。

OIDC ID トークンはベアラー認証トークンです

OIDC ID トークンは、OIDC プロバイダーによって署名された JWT であり、トークンのベアラーがトークンのサブジェクトとして認証できるようにします。 これらのトークンを公開するため、悪意のある当事者がレジストリから有効な ID トークンを取得し、それをサービスに提示して ID トークンのサブジェクトとして識別できることを意味します。

理論的には、OIDC仕様によれば、消費者はトークンを信頼する前にIDトークンのオーディエンスをチェックする必要があるため、これは問題にはなりません(つまり、トークンがサービスFooに提示された場合、サービスFooはトークンがサービスFooを対象としていることを確認する必要がありますオーディエンスクレーム)。 ただし、OIDCクライアントライブラリがこのチェックを行わないという問題がありました。

この問題を解決するには、ID トークンから OIDC プロバイダーの署名を削除し、ギユー キスクアター (GQ) 署名に置き換えることができます。 このGQ署名により、署名付きトークンを共有せずにOIDCプロバイダーの署名があったことを証明でき、この証明はOIDCプロバイダーの公開鍵と残りのIDトークンを使用して検証できます。 GQ 署名の詳細については、 元の論文OpenPubkey リファレンス実装を参照してください。 私たちは、 ザカリー・ニューマンの論文で議論されたものと同様のアプローチを使用しました。

OIDC ID トークンには個人情報を含めることができます

GitHub Actions などの CI システムからの OIDC ID トークンを使用する場合、トークンに漏洩する可能性のある個人情報が存在する可能性は低いです。 たとえば、GitHub Actions OIDC ID トークンで利用できる完全なデータは、 GitHub で文書化されています

リポジトリ名や Git コミット ダイジェストなど、このデータの一部は、Docker ビルド プロセスが生成する署名されていない来歴構成証明に既に含まれています。 人間の ID を表す ID トークンには、より多くの個人データが含まれる場合がありますが、間違いなく、これは消費者が信頼ポリシーの一部として検証したい種類のデータでもあります。

重要な侵害

署名者の秘密鍵が侵害された場合(これは一時的な鍵であるため、確かにありそうにありません)、攻撃者が画像に署名し、署名を公開PKトークンと組み合わせるのは簡単です。 前述のように、透明性ログはこの種の侵害を検出するのに役立ちますが、さらに進んでそもそもそれを防ぐことができます。

元のOpenPubkeyフローでは、署名者の公開鍵とランダムノイズからナンスを作成し、対応する秘密鍵を使用して画像に署名します。 ただし、ナンスに画像のハッシュも含めると、すでに署名した画像もOIDCプロバイダーによって事実上署名されます。 つまり、PK トークンは、他のイメージに署名するために再生できない 1 回限りのトークンになります。 したがって、一時的な秘密鍵を侵害することは、攻撃者にとってもはや役に立ちません。

OpenPubkey は ID トークンで nonce クレームを使用します

完全な OIDC フローは、GitHub アクションでは利用できません。 代わりに、ビルド プロセスがオプションのオーディエンス (aud) 要求を使用して ID トークンを要求できる単純な HTTP エンドポイントが提供されます。 認証中にOIDCプロバイダーに任意のデータに署名させる必要があります。 これを行うには、クレームの意図された使用を妨げない限り、ID トークン要求の 1 つになるデータを OIDC プロバイダーに送信します。 GitHub Actions では aud クレームを任意の値に設定できるため、この目的に使用できます。

次は何ですか?

Dockerは、より広範なオープンソースコミュニティがソフトウェアサプライチェーン全体のセキュリティを改善できるようにすることを目的としています。 優れたセキュリティには、優れた使いやすいツールが必要であると強く感じています。 あるいは、Bounce Security の創設者兼 CEO である Avi Douglen 氏が雄弁に述べているように、「ユーザビリティを犠牲にしたセキュリティは、セキュリティを犠牲にしてもたらされる」のです。 

この投稿で説明するアプローチは、セキュリティと信頼を犠牲にすることなく、コンテナイメージへの署名を可能な限り簡単にすることを目的としています。 全体的なアプローチを簡素化し、複雑なインフラストラクチャ要件を排除することで、10 年前に Linux コンテナーの広範な採用を可能にしたのと同じ方法で、コンテナー署名の広範な採用を促進することが目標です。 

オープンソースコミュニティと暗号化の実践者:署名に対するこのアプローチについてどう思うか教えてください。 OpenPubkey GitHub 組織内のさまざまなリポジトリにわたる予備的な実装を確認できます。さまざまなリポジトリで問題を開いたり、 OpenSSFコミュニティの議論に参加したりしてください。 

皆様からのフィードバックをお待ちしており、ソフトウェアサプライチェーンのセキュリティを向上させるために協力することを楽しみにしています。

さらに詳しく

スティックフィギュア 画像ライブラリ ユーリ・チャン.

フィードバック

「OpenPubkeyを使用してDockerの公式イメージに署名する」に関する0の考え