Amazon Web Services ブログ
コンテナにおけるデジタル署名
この記事は Cryptographic Signing for Containers (記事公開日 : 2022 年 6 月 23 日) の翻訳です。
導入
2021 年 5 月、米国大統領府は国家のサイバーセキュリティ向上に関する大統領令を発表しました。この命令の重点分野の 1 つにソフトウェアサプライチェーンのセキュリティ強化があり、サプライチェーンのリスクへの対処に関する要件として、以下が挙げられています。
- 強力なアクセス制御による開発環境の保護
- 自動コードスキャンによるソフトウェアの脆弱性の発見および修正
- コードの完全性の担保による信頼できるサプライチェーンの維持
- ソフトウェアコンポーネントの来歴 (例 : 作成元) に関する正確かつ最新のデータの保持
このブログ記事では、完全性と来歴という観点から、ソフトウェアサプライチェーンのセキュリティについて理解することを目的とします。特に、デジタル署名を用いることで、どのようにソフトウェアサプライチェーンにおけるコンテナイメージの完全性を担保するプロセスを簡素化できるのかについて説明します。加えて、デジタル署名を用いることで、どのようにコンテナイメージが信頼された発行元から提供されていることを検証し、またどのようにコードスキャンや承認ワークフローと統合して、安全なソフトウェアサプライチェーンを促進するのかについても説明します。
署名と検証は、実装および DevOps プロセスとの統合が容易であることが重要です。開発チームには、暗号化キーと証明書の管理に関して過度な負担をかけないことが理想です。このブログ記事では、主にコンテナイメージマニフェストと関連する成果物 (artifacts) への署名を取り上げますが、デジタル署名は、文書、認証トークン、ソフトウェアパッケージなどの署名および検証にも使用できます。
今日におけるコンテナのビルドには、イメージを作成し、Amazon Elastic Container Registry Public (ECR Public) や Amazon ECR プライベートレジストリなどのレジストリに置く作業が含まれます。開発者は、これらのリポジトリからコンテナをデプロイできます。多くの場合、コードパイプラインを使用してコンテナイメージをビルドし、デプロイします。オープンソースのコンテナイメージ (およびローカルでビルドしたイメージ) の完全性の検証を CI/CD パイプラインに組み込むことで、ソフトウェアサプライチェーンの攻撃リスクを低減し、複数のビジネスユニットでこれらのコンテナイメージを使用する企業に対して、継続的な信頼を提供できます。
まとめると、以下の疑問についてこれから確認していきます。
- デジタル署名とは何か。どのようにコンテナのビルドパイプラインで使用できるのか。
- 組織は、デジタル署名を用いることで、コンテナイメージが承認済みであり、セキュリティ標準を満たしていることをどのように確認できるのか。
- 開発者は、デジタル署名を用いることで、作成したコンテナイメージが検証され、使用が承認された後、改ざんされていないことをどのように確認できるのか。
デジタル署名とは
まず、デジタル署名とは何か、どのような利点があるのか、といった用語や概念を定義します。もしこのブログ記事をウェブブラウザで読んでいるのであれば、ブラウザのアドレスバーに鍵のアイコンが表示されているでしょう。これは、このページへの接続が TLS (トランスポートレイヤセキュリティ) によって保護されていることを示します。このとき、ブラウザは TLS 接続を確立するために使用される証明書を自動的に信頼します。なぜなら、この証明書はすべての主要なオペレーティングシステムとウェブブラウザにインストールされている信頼できるルート証明書に至るまでの、検証可能な信頼チェーンを持つからです。同様の信頼のメカニズムは、コンテナイメージのデジタル署名を作成する個人または組織のアイデンティティの検証にも使用されます。
典型的なデジタル署名は、署名されるデータの暗号化されたハッシュと署名者のアイデンディティ情報を提供する証明書の、(少なくとも) 2 つの要素で構成されます。簡単に言えば、ハッシュは、任意のサイズのデータを受け取り、固定長で一意の文字列を出力する一方通行 (すなわち不可逆) な関数です。ハッシュは決定論的であり、同じデータを与えると常に同じハッシュを生成し、データを変更すると (どんなに小さな変更でも) 元のデータとは異なるハッシュを生成します。後者の性質は、コンテナイメージの完全性を検証する際に重要です。もしイメージが少しでも変更されると、そのイメージマニフェストから生成されるハッシュも変化します。
元のファイルのハッシュは署名者の秘密鍵で暗号化されるため、この秘密鍵は慎重に扱う必要があります。この秘密鍵は署名者を一意に識別し、署名者以外の人が署名を生成できないことを保証します。図 1 に、一般的な署名プロセスの動作を示します。
多くの場合、署名には、その署名がいつ作成されたかを正確に示すタイムスタンプも含まれます。紙の契約書に署名することを想像してみてください。あなたに関する情報、おそらくフルネームと住所、そして署名と契約書に署名した日付が記載されているでしょう。
デジタル署名では、署名者の公開鍵 (通常は署名と一緒に証明書に含まれる) で署名を復号し、復号結果のハッシュを元のファイルのハッシュと比較することで署名を検証対象の成果物と照合し、署名後にデータが変更されたかどうかを判別できます。2 つのハッシュが一致しない場合、元のデータは署名後に何らかの方法で変更されたことになります。この仕組みを図 2 に示します。
前述のように、署名はいわゆる否認不可 (non-repudiation)、つまり署名者がファイルに署名したことを否定できないという機能も提供します。署名を生成するためには、署名鍵へのアクセスが必要です。つまるところ、ファイルの署名を検証する人は、そのファイルが実際に、例えば Amazon Web Services によって作成または署名されたことを合理的に確認できることを意味します。
この仕組みは、ソフトウェアサプライチェーンにおけるコンテナイメージのセキュリティを考える際に有用です。なぜなら、自分たちで作成したコンテナイメージに自ら署名できるからです。私たちは、パブリックなイメージリポジトリからイメージをダウンロードし、静的コード解析スキャンを実行して既知の脆弱性を含んでいないことを確認し、組織のセキュリティ標準を満たすようにイメージを構成できます。その後、コンテナイメージマニフェストに署名することで、イメージが信頼でき、組織での使用を承認済みであることを示すことができます。
組織によっては、1 つのイメージに複数の署名を作成したいと考えるでしょう。そうすることで、元のイメージがあるパブリックレジストリから取得した署名を保持したり、(同じ組織内の) 異なるチームの開発者がイメージに署名することで、特定のユースケースで承認されたことを示すことができます。
コンテナビルドプロセスに署名と検証を統合する
コンテナイメージをビルドする場合、大半のお客様はパブリックコンテナレジストリから取得したベースイメージから始め、プライベートレジストリに保存し、それを元に特定のユースケースやワークロードに合うように構成していきます。また、事前に承認されたイメージを含む独自のプライベートコンテナレジストリをホストして、同様にニーズに合わせてカスタマイズしていくことも可能です。しかし、いずれの場合においても、開発者がイメージレジストリからイメージを取得し、アプリケーションの要件や組織の標準に合うようにイメージを構成していく、というプロセスは同じです。
組織は通常、コンテナイメージをどのようにビルドし、保護するかについて、特定の要件を定めています。これには、既知の脆弱性や CVE (Common Vulnerabilities and Exposures) のチェック、イメージに対する承認済みの静的コード解析ツールの実行、あるいは組織が安全でないと判断した特定のサービスやパッケージの無効化などが含まれるでしょう。例えば、AWS のお客様は、しばしば ECR 拡張スキャン (ECR と Amazon Inspector の統合) を使用してコンテナイメージをスキャンし、既知の脆弱性や設定ミスを検出します。
ここからは、脆弱性をチェックし、お客様の要件に合うようにイメージを構成するこのプロセスを、ハードニング (hardening) と呼ぶことにします。ハードニングが完了し、組織での使用が承認されたイメージを、検証済みイメージ、あるいは承認済みイメージと呼びます。
しかし、組織では、承認済みイメージのみがそれぞれの環境に昇格可能であり、最終的に本番環境にデプロイできることを保証する仕組みが必要です。また、承認済みイメージが、承認後に改ざんされていないことを確認する仕組みも必要です。
信頼できるエンティティのみがレジストリにイメージをインポートできるようにするには、レジストリにイメージをプッシュできるユーザーを制限する方法があります。しかし、この方法は、レジストリにプッシュした後のコンテナイメージの完全性を検証する役には立ちません。イメージが改ざんされた可能性を調査する必要がある場合、レジストリのログを解析して、問題のイメージを誰がインポートしたのかを確認する必要があります。コンテナイメージは様々な環境や開発チームによって再利用されることが多いため、イメージのライフサイクルを包括的に把握するには、ロバストなロギングインフラストラクチャが必要です。また、アイデンティティとアクセス制御は組織の境界を越えませんが、イメージマニフェストに関連付けられた署名は、たとえ組織を離れたとしても、イメージとともに移動します。署名を使用せずにこれらの要素を解決することは、特に多くの開発者やプロジェクトを抱える大規模な組織にとっては、複雑さを急激に増大させることになります。
あるいは、イメージタグを使って、組織内での使用を承認済みであることを示す方法もあります (例 : myimage:approved)。しかし、繰り返しになりますが、タグはイメージが承認後に改ざんされたかどうかを検証する方法を提供しません。また、イメージをレジストリにプッシュできる開発者なら、誰でもイメージにタグを付与できます。したがって、特定の開発者やチームだけがイメージを承認済みであるとタグ付けできるようにする簡単な方法はありません。これに対して、署名を使えば、有効な署名鍵にアクセスできる開発者やチームだけが、イメージが承認済みであることを示す署名を生成できるようになります。
また、コンテナイメージの完全性を検証する簡単な仕組みとして、レジストリからイメージを取得した後、レジストリ内のイメージマニフェストダイジェストとイメージマニフェストのハッシュを比較する方法があります。この場合、イメージの完全性が維持されていることは確認できますが、否認不可の役には立ちません。否認不可を実現するには、イメージマニフェストが特定のエンティティによって署名されたことを証明する必要がありますが、誰でも生成できてしまうハッシュはこれを実現しません。イメージマニフェストにデジタル署名することで、署名鍵にアクセスできる人だけが署名を生成できること、そしてイメージの使用が承認済みであることを保証できるようになり、イメージがパブリックまたはプライベートレジストリにプッシュされた後でもイメージの完全性を検証できるのです。
お客様は、レジストリのアクセス制御、イメージのタグ付け、ハッシュを用いた検証を組み合わせて、完全性の検証や否認不可を実現できます。しかし、これらを効率的に実装することには複雑さが伴い、容易にミスも起こりやすくなります。デジタル署名は、標準的なアクセス制御やタグ付け、監視と組み合わせることで、完全性の検証や否認不可を実現し、徹底した防御を提供します。
まとめると、署名を使用することで、2 つの課題を解決できます。コンテナイメージが組織のハードニングプロセスを経て、組織内での使用を承認済みであることを示すこと、そして、利用者が承認後のイメージの完全性を検証するための仕組みを提供することです。ビルドまたはハードニングプロセスが完了した後、組織または開発チームの秘密鍵でイメージマニフェストにデジタル署名を行うことで、次のように宣言するのです。「このコンテナイメージは (署名者によって) 安全とみなされ、使用する準備が整いました。」この署名は、イメージの完全性を検証するだけでなく、承認ワークフローでも使用できます。
同じ組織内の異なるチームは、それぞれ独自のセキュリティ標準とリスク体制を持つことがあり、顧客によっては、同じコンテナイメージマニフェストに複数の署名を付与することがあります。これにより、特定のチームまたは部門がハードニングプロセスを経て、パイプライン上でイメージの使用を承認したことを示します。以下の図 3 に、高レベルのコンテナビルドパイプラインと、署名と検証を実装する場所を示します。
時は万物を変える…
もしかすると、すでにこのアプローチの問題点にお気づきの方もいるでしょう。ハードニングプロセスは、ある時点での設定に過ぎません。新しい脆弱性が発見されたり、組織のセキュリティ標準が変更されたりして、安全とみなされるべきものが時間とともに変化する可能性があります。このため、私たちはしばしば署名にタイムスタンプを追加し、コードやイメージがいつ署名されたのかを明確にし、古い署名と新しい署名を区別します。
この例では、「この署名はもはや有効ではありません」と示す能力、すなわち “失効” の潜在的な必要性も生じます。以前は信頼できたソフトウェアパッケージやイメージに新たな脆弱性が発見されたり、 署名者の秘密鍵が漏洩したりしたことで、失効が必要になることがあります。鍵が漏洩したり、署名鍵に過度なアクセス許可が設定されている場合に、失効は、脆弱なコードや悪意のあるコードを含むコンテナイメージを配布しようとして、エンティティが元の署名者のアイデンティティを誤って仮定してしまうことを防止できます。
鍵の漏洩は、必ずしも漏洩した鍵で署名された全てのソフトウェアが脆弱であることを意味しません。鍵が漏洩した後に署名されたコードこそが、明確に危険であると言えます。しかし、あるソフトウェアプロバイダーが、同じコンテナイメージの複数の後続バージョンに同じ信頼の起点 (署名鍵など) を使用していて、後でイメージマニフェストの署名に使用した署名鍵の漏洩が発覚したという状況を想像してみてください。
ソフトウェアプロバイダーは、悪意のあるアクターがその署名鍵を用いて自分たちになりすまし、脆弱なコードや悪意のあるコードを配布することを防ぐために、その鍵で作成された署名を取り消すかもしれません。しかし、パブリックレジストリからこれらのイメージを入手した利用者が、署名証明書が取り消されたことを知ると、その鍵で署名されたものはすべて信頼できないと判断し、厳密に言えばリスクのない以前のバージョンのイメージの使用も中止する可能性があります。失効の長所と短所は複雑な議論であり、ここでは十分に説明しませんが、このような考慮点が存在することは知っておいて損はありません。
SBoM、来歴、ソフトウェアサプライチェーンのセキュリティ
ソフトウェア部品表 (SBoM; Software Bill of Materials)
ソフトウェアサプライチェーンの議論におけるもう 1 つの重要な概念は、ソフトウェア部品表 (まれにシステム部品表とも呼ばれます)、SBoM という考え方です。SBoM は、製品全体を構成するコンポーネントのレシピまたはリストと考えることができます。
例えば、あなたはバイクを組み立てて販売するバイク屋さんを営んでいるとしましょう。あなたはバイクを組み立てるために購入すべき部品のリストを持っています。ホイール、ハンドルバー、フレーム、バイクのタイヤ用チェーンなどが必要です。その中で、より安価で高品質なチェーンを製造しているメーカーを見つけたとします。あなたは、このメーカーのチェーンを使ってバイクを作り、顧客に販売するようになります。
1 年後、実はこのチェーンは欠陥品で、破損して故障や怪我を引き起こしているという苦情を耳にします。あなたは、このチェーンが装着されたバイクを購入した顧客に、損害が発生する前に交換するように知らせたいと考えるでしょう。しかし、この欠陥のあるチェーンで組み立てられたバイクを購入した顧客を、どのように知ることができるでしょうか。うまくいけば、バイクの組み立てに使用したすべての部品が記載された部品表があるはずです。そうであれば、注文書を参照し、部品リストの中にそのチェーンが含まれているバイクを確認できます。同様に、欠陥のあるチェーンのリコールのお知らせを見た顧客は、提供された部品表を使って、購入したバイクが危険であるかどうかを自分で確認できます。
これは、ソフトウェアにも当てはまります。当初は安全だと信じていたソフトウェアパッケージが、後になって欠陥がある、あるいは悪意があることが発覚することもあるでしょう。ソフトウェアやコンテナイメージのビルドに関連する SBoM を用意し、ビルド内のソフトウェアコンポーネントの明確な表示を提供することが重要です。
ハードニングプロセスを思い出してください。当初はコンテナイメージでの使用を承認していたソフトウェアパッケージが、後になって新たに発見された脆弱性を含んでいることが発覚することがあるでしょう。SBoM を使用して、どのコンテナイメージに欠陥のあるコードや悪意のあるコードが含まれているかを判断し、脆弱性の修正やイメージと成果物に対する署名の失効を行う必要があります。
来歴
もうひとつの重要な概念である “来歴” について簡単に説明します。来歴とは、起源や所有権の歴史を意味します。美術品や骨董品を購入する際に、誰がその品物を発見、制作したのか、また、その品物がどのように保管されてきたのかを理解するためによく利用されます。
同様のロジックを、ソフトウェアのビルドにも適用できます。開発者は、アプリケーションやサービスのコンポーネントとして使用するソフトウェアパッケージや成果物の来歴や歴史を理解する必要があります。ソフトウェアの来歴は、環境内で実行されるコードの起源について、検証可能な証明を提供するための戦略として理解できます。言い換えれば、実行中のコードはすべて、適切な権限を持つ検証可能な既知の作者による、特定のコミットの集合に関連付けられるべきです。当然ながら、悪意のある人物による改ざんを防ぐために、来歴情報の完全性を担保する必要があります。
デジタル署名は、イメージマニフェストだけでなく、プロジェクトの SBoM、関連する設計の成果物、および来歴情報の完全性と否認不可を保証するために必要な、重要な制御の仕組みなのです。
署名、承認ワークフロー、安全な SDLC
これらの概念をすべてまとめて、多くの開発チームとプライベートレジストリを持つ大規模な組織における、仮想的なコンテナイメージビルドパイプラインを想像してみましょう。最初にプライベートレジストリを使用する開発チームは、パブリックレジストリから既存のベースイメージを取得し、その署名を使ってベースイメージの完全性を検証し、想定した発行者によって署名されていることを確認します。元の署名も、ベースイメージと一緒に開発者のプライベートレジストリに取り込まれます。次に、開発者はイメージに対して脆弱性スキャンツールやコードスキャンツールを実行し、セキュリティ標準や設計上の必要性に応じてイメージを修正します。
開発者は、パブリックイメージを出発点としてコンテナイメージを作成した後、自分たちのチームの署名鍵で新しいイメージマニフェストに署名し、その署名と関連する署名済みの成果物 (SBoM など) をプライベートレジストリにプッシュして、作成したコンテナイメージと一緒に保存する必要があります。パブリックレジストリから取得した元の署名は、元のベースイメージと一緒にプライベートレジストリに残りますが、新しいイメージマニフェストには、この特定の開発チームがイメージを作成し、ビルド環境での使用を承認したことを示す署名が付きます。
ビルドフェーズが完了した後、開発者はテストを開始したいところですが、まず新しいイメージをテスト環境に昇格させるための承認ワークフローを通過する必要があります。開発者は、テストに使用する前に、コードレビューや一連のスキャンを実行する必要があるかもしれません。その後、このプロセスを経たことを示すために、別の鍵でイメージに再度署名します。開発パイプラインでは、コード昇格プロセスの一部として、署名が存在することを検証することで、適切な署名が行われていないビルドが上位の環境に進むことを防止できます。
組織において、このようなコントロールゲートや承認ワークフローは、イメージがビルドからプロダクションに至る過程で数多く存在するでしょう。プロセス全体を通じてコンテナイメージの署名を使用することで、組織のソフトウェア開発ライフサイクル (SDLC; software development lifecycle) の手順で決められた時点におけるイメージの検証を実現できます。
まとめ
このブログ記事では、デジタル署名を定義し、コンテナイメージの署名がなぜ重要なのか、そしてイメージの署名がソフトウェアサプライチェーンや CI/CD パイプラインにどのように組み込まれるのか、について説明しました。また、署名の失効、SBoM、来歴、承認ワークフローなどの重要な概念も紹介しました。最後に、ソフトウェア開発ライフサイクルにイメージマニフェストの署名を統合する基本的な例を紹介しました。