Google Cloudとコンテナの継続的なセキュリティ
本文の内容は、2020年6月23日にÁlvaro Iradierが投稿したブログ(https://sysdig.com/blog/toctou-tag-mutability/)を元に日本語に翻訳・再構成した内容となっております。
タグの可変性により、複数の機能およびセキュリティの問題が発生する可能性があります。コンテナランドでは、タグは特定の時点で作成されたイメージバージョンにおける変化する事を前提としたリファレンスです。タグは予期せず、いつでも変更される可能性があります。この記事では、それらを防ぐ方法を学びます。
タグの可変性により、とりわけ、チェック時間と使用時間(TOCTOU)の問題が発生します。CI/CDパイプラインまたはKubernetesのアドミッションフェーズで検証されるイメージは、クラスターにデプロイされているイメージとは異なり、イメージスキャンのセキュリティチェックをバイパスします。
たとえば、レジストリに直接アクセスできる開発者は、この事実を悪用して、イメージのスキャン後にタグを変更し、プロダクションクラスター内で悪意のあるバージョンのマイクロサービスを実行し、機密情報を漏らしたり、バックドアを仕掛ける可能性があります。
不変タグを使用すると、これらの問題を回避できますが、タグの可変性は多くのシナリオで非常に便利であり、不変タグはレジストリで広くサポートされていません。
この記事では、タグの変更可能性の影響、利点と欠点、および予期しない問題を防ぐためのいくつかの実践と手法について説明します。また、Sysdigイメージスキャナーとアドミッションコントローラーツールを使用してKubernetesデプロイメントを保護する方法についても説明します。
タグの可変性とは何かを説明する前に、いくつかの重要な概念を定義しましょう。
したがって、次のようなコマンドで特定のイメージを使用してコンテナを実行します。
docker run my-registry.com/some-organization/a-repository:tag1
しかし、正確には何がイメージのフードの下にあるのでしょうか?イメージは、以下に説明するイメージマニフェストと呼ばれる単なるJSONドキュメントです。
マニフェスト、イメージレイヤー、およびイメージ設定は、レジストリ内にバイナリBLOBとして保存されます。それらに対処するために、「20feefdbe22e555a5922072161f0bf42fdee25f38c026997c0f4c025e3edbe00」のように、SHA256ハッシュを使用しますが、この記事では、明確にするために最初の桁のみを使用します:20feefdb...
これらのハッシュダイジェストは便利ではないので、それらを参照するタグを定義できます(少なくともマニフェストに)。上記のシナリオでは、tag1は1ab50232のダイジェストを含むマニフェストファイルへのリンクです。たとえば、そのイメージをダウンロードするためにそのマニフェストを参照する場合は、 myregistry/myimage:tag1を使用できます。
マニフェストには、イメージ設定への参照と、イメージファイルシステムを構成するレイヤーのダイジェストが含まれています(e74c2290 ...、71bb4d21 ...、等)。
ダイジェストはオブジェクトのコンテンツに基づいて作成されるため、特定のダイジェストは常に同じオブジェクトを識別します。それらは不変です。
これはコンテンツの重複を避けるのに最適です。たとえば、2つの異なるイメージで同じコンテンツを持つ2つのレイヤーは、同じダイジェストを持ちます。そのレイヤーを再利用して、スペースと帯域幅を節約できます。
ダイジェストの制限を回避するため、マニフェストファイルへの単なるポインタであるタグを使用します。そして、ポインターとして、それらは時間とともに変化する可能性があります。
ミュータントタグ(またはミュータブルタグ)は、マニフェストを介して特定のイメージを参照するタグですが、別の時点で別のイメージを参照することもできます。
不変タグは特定のイメージに関連付けられたタグであり、後で変更することはできません。
ミュータントタグの作成は、かなり一般的で簡単なプロセスです。
新しいイメージ(つまり、myimage:tag1)をビルド、タグ付け、プッシュします。
イメージをレジストリにプッシュすると、クライアントはすべてのレイヤーをアップロードします。
次に、マニフェストファイル(この例では1ab50232...)と、リポジトリmyimageでそのマニフェストを参照するタグtag1を作成します。
別のイメージを作成し(Dockerfileコマンドまたはイメージのコンテンツを変更)、同じタグ名tag1を使用してタグを付け、レジストリにプッシュします。イメージのコンテンツが変更されたため、イメージレイヤーが異なり、新しいマニフェストがダイジェスト17b345ca...で作成されます。
プッシュが終了すると、tag1は別のマニフェストを参照するようになります。タグ tag1を変更しました。
ビルド/プッシュプロセスを繰り返して、タグを変更することができます!
古いマニフェストは引き続きレジストリで使用できるため、イメージタグの代わりにマニフェストダイジェストを使用してそれらを取得できます。以下を使用できます。
また、複数のタグが同じマニフェストを指すことができます。この例では、myimage:tag1とmyimage:othertagがこの時点で同じイメージを参照していることがわかりますが、変更される可能性があります。
明らかに、複数のユースケースがあるので、ミュータブル/ミュータントタグは便利です。例えば:
"latest"(指定されていない場合はデフォルトのタグ)を使用して、常に最新バージョンのイメージを指すか、"alpine"や "slim"などのバリアントの最新バージョンを常に指すイメージバリアントを指します。
また、ミュータントタグを使用して、"dev", "prod", "qa"などのさまざまな環境でバージョンを追跡することも一般的です。これらのタグは、新しいバージョンがこれらの環境のいずれかにデプロイされると更新されます。
バージョン管理を簡単にするために、:1または:2.0のようなタグは、マイナーバージョンまたはパッチバージョンのエイリアスにすることができます。これらのタグを使用すると、ユーザーは予期しない動作や構成の変更なしで、最新のメジャーバージョンまたはイメージをプルできますが、バグとセキュリティ修正が含まれた最新のマイナーバージョンを取得していることを確認できます。
開発者や運用チームが直面するいくつかの典型的な問題は、タグの変更とコンテナのデプロイに関連しています。タグが期待されるイメージに対応している保証がないため、イメージタグを使用してコンテナをデプロイする場所はすべて、非決定的なデプロイメントを実行しています。これは、単純なdocker run、またはdocker-compose、Kubernetesなどのオーケストレーションツールに適用されます。
例を見てみましょう:
その結果、本番環境でアプリケーションがクラッシュします。
Kubernetesの場合、ポッドのスケジュールとボラティリティ、またはホストキャッシュのステータスに応じて、同一のコンテナに対して異なるイメージを実行する可能性があります。
以下のスクリーンキャストは、ノードの1つでポッドのスケジュールを遅らせることで問題を再現しています。cordonノードの1つでスケジューリングを無効にし、タグを変更してからノードを変更するために使用しuncordonますが、ノードが後でクラスターに追加された場合、またはポッドが追放されて別のノードで再スケジュールされた場合も同じ問題が発生します。
スクリーンキャストは、英語版のブログページにてご覧いただけます。
確定的で反復可能なデプロイメントを実現するために、ピーターとローラはタグの代わりにダイジェストを使用できます。myimage:tagの代わりにmyimage@sha256:<digestValue>を使用することで、コンピューターと本番環境に同じマニフェストを確実にデプロイし、この種の予期しないバージョンの変更を回避できます。
たとえば、myimage@sha256:latestの代わりに myimage@sha256:1ab50232...を使用します。
Kubernetesの動作に関する1つの注意:ポッドがスケジュールされ、イメージがpullされた後、 Kubernetesはタグをイメージダイジェストに解決します。ポッドリソースを作成する前に解決されていれば、いくつかの問題から救われたでしょう。また、KubernetesにはさまざまなImagePullポリシーがあるため、Alwaysキャッシュされたイメージを使用する代わりに、常にレジストリからダウンロードします。
これまでのところ、これらの問題はタイミングの悪さ、予期しない更新などに関連していますが、悪意のあるアクティビティやセキュリティの脅威には関連していません。TOCTOUはTime-of-check Time-of-useの略であり、チェックが使用とは異なる瞬間に行われるとTOCTOUバグが発生し、システムのセキュリティが低下する可能性があります。
クラスターにデプロイする前にすべてのイメージをスキャンするKubernetes Admission Controllerを使用している場合でも、攻撃者がTOCTOUバグを悪用してクラスターで悪意のあるソフトウェアを実行する可能性があるケースを分析してみましょう。アドミッションコントローラは、コンテナ内の不正行為や脆弱性を検出する必要があるため、セキュリティポリシーに準拠していないイメージを禁止できます。
これは、このようなバグを悪用する攻撃者のワークフローの例です。
イメージのスキャンには、イメージのサイズによって数秒から数分かかる場合があり、スケジューリングにも時間がかかる場合があるため、その間にプッシュを実行するのに十分な時間があります。
悪意のあるイメージをビルドしてリポジトリにプッシュし、ローカルでmyimage:latestにタグを付け直すことで、悪意のあるイメージを事前に準備できます。
$ docker build -t myregistry.com/maliciousimage . Uploading context 18.829 MB Step 1/2 : FROM busybox ---> 769b9341d937 ... Successfully built 99cc1ad10469 $ docker push myregistry.com/maliciousimage The push refers to repository [myregistry.com/maliciousimage] e9f56d359a24: Pushed ... latest: digest: sha256:8360f9950adc9... size: 735
$ docker tag myregistry.com/maliciousimage myimage:latest
そして、スキャンがステップ4でプルをトリガーした直後に、タグ付けされた悪意のあるイメージをプッシュします。
$ docker push myimage:latest e9f56d359a24: : Mounted from maliciousimage ...
latest: digest: sha256:8360f9950adc9... size: 735
これにより、以前にプッシュされたのすべてのレイヤーが再利用maliciousimageされるため、それらは再度アップロードされません。
DockerレジストリAPIで説明されているように、イメージをプッシュするために必要なすべての手順を実行し、最終的なマニフェストアップロードは保持する ように、Containerdなどのリポジトリクライアントを変更することで攻撃を自動化することもできます。タグの変更を引き起こすこの最後のステップは、イメージスキャナーからのWebhook、Kubernetes監査ログからのイベントなどによってトリガーされます。
タグの変異に関連する問題は非常に一般的で再現が簡単です。予期しないイメージの更新に遭遇したときの不運のためか、意図的に引き起こされたためです。
デジタル署名されたイメージの使用、パブリックリポジトリまたは信頼されていないリポジトリの回避、イメージの取得元の厳密な制御の実装などの一般的なセキュリティプラクティス以外に、いくつかのトラブルを回避するためのいくつかの優れたプラクティスについて話しましょう。
本番環境、重要な環境、または簡単なテストではないものへの導入では、 "latest" または同様のタグのようなタグを避けてください。特定のバージョンのタグなど、より安定したタグを使用してください。ただし、これらのタグも変更できないという保証はありません。
さらに、 "latest" は単なる慣習です。存在すること、または最新の既存バージョンを指すことが保証されていません。3年前のバージョンを指す可能性があります。
イメージのタグ付けプロセスを制御している場合は、ミュータントタグを作成しないように、既存のタグをプッシュしないでください。一部のレジストリには不変のタグを作成するオプションがあり、既存のタグをプッシュしようとすると失敗します。
タグの不変性のオプションがない場合は、Docker APIを使用してタグでマニフェストを取得し、その存在を確認してプッシュを中止するなど、開発プロセスまたはCI/CDパイプラインに追加のメジャーを追加できます。
8360f9950adc9...名前付きタグの代わりに、@sha256:のようなイメージダイジェストを使用します。これは、使用するイメージが常に同じであることを保証します。
を使用docker images --digestsしてイメージダイジェストを表示したり、でデプロイされたポッドを検査したりできますkubectl inspect pod。
Sysdig Admission ControllerはSysdig Secureをイメージスキャナーとして使用してポッドを検証しますが、スペックを変更してイメージタグの代わりにダイジェストを使用します。これにより、スケジューラは以前にスキャンされたものとまったく同じイメージを確実にプルします。心配しないでください。元のイメージとタグは参照用にアノテーションに保持されるため、失われることはありません。
ビルド後のスキャンや、イメージがレジストリにプッシュされたときなど、他の変更不可のアドミッションコントローラーや他のアプローチを使用すると、クラスターがTOCTOUの問題にさらされる可能性があります。
ガベージコレクションが期待どおりに機能しないなど、ミュータントタグには他にも問題があります。または、Registry APIがダイジェストによってイメージを削除するが、タグによってリストされたイメージがいくつかの混乱とバグを引き起こす可能性があるという事実があります。
1.10以降のHarbor、2019年7月以降のECR などの一部のレジストリでは、タグを不変にする構成設定を提供しています。Harborでは、リポジトリごとに、またはワイルドカード式を使用してルールを定義できます。不変性を有効にすると、レジストリは既存のタグのプッシュを許可しません。
可変または不変のタグ?
可変性は便利ですが、気づいていない場合や管理する準備ができていない場合も危険です。
詳細については、Virtual Rejekts 2020での私のプレゼンテーションとSysdig Admission Controllerに関するブログ投稿やスライドを確認できます。
したがって、「大きな力には大きな責任が伴います」、「知識は力です」。これらの2つの原則に従って、ミュータントタグの力を解き放ち、Sysdig SecureイメージスキャンやSysdig Admission Controllerなどの知識、優れた実践、ツールを適用して、タグを制御し、予期しない問題を防ぎます。