Google Cloudとコンテナの継続的なセキュリティ
本文の内容は、2022年7月12日にGuillermo Palaciootoが投稿したブログ(https://sysdig.com/blog/secure-kubernetes-deployment-signature-verification/)を元に日本語に翻訳・再構成した内容となっております。
Kubernetesクラスターでコンテナを実行する場合、デプロイするイメージを信頼することが、セキュリティを強化するための鍵となります。ミュータブルイメージの使用は、セキュアなKubernetesデプロイメントに対するリスクを表し、期待するものを確実に実行するための信頼できるメカニズムを持つことの重要性を浮き彫りにしています。Signature Cosign Connaisseurを使用したセキュアなKubernetesデプロイメント
このブログでは、セキュアなKubernetesデプロイメントを実装する方法を段階的に学習します。 既知の機関による署名の検証とアドミッションコントローラーによる検証なしに、クラスター内で何も実行されないようにするソリューションをセットアップできます。
次のようなシナリオを考えてみましょう。私たちは大企業でセキュリティエンジニアとして働いていますが、最近データ漏洩がありました。フォレンジック調査の結果、この情報漏洩は、私たちのKubernetesプロダクションクラスターにデプロイされた悪質なコードに関連していることが判明しました。
コンテナイメージを検証するために、CI/CDパイプラインでイメージスキャンを使用していましたが、何らかの理由でコンテナが社内のOCIレジストリに公開され、クラスターにデプロイされました。私たちのチームは、デプロイメントにミュータブルタグを使用しているため、セキュリティツールもイメージを受け入れたようで、アドミッション・コントローラはイメージを受け入れました。しかし、イメージ名だけが検証されました。
コンテンツ信頼性ポリシーの実装では、イメージの署名と検証を要求する予定です。
これにより、今後Kubernetesクラスターにデプロイされるイメージは、私たちのパイプラインで先に検証されたものと同じであり、したがって、私たちのセキュリティプロセス内で認証されていることが保証されます。
デプロイメントステップでKubernetesをセキュアにしたい場合、使用するコンテナに脆弱性がないことを確認したら、すべてのコンテナに署名する必要があります。セキュアなKubernetesのデプロイメントプロセス
コンテナに署名したら、それを公式レジストリと共有する。その結果、クラスターにデプロイできるのは、当社または公式レジストリが署名したコンテナのみとなります。
それでは、署名検証のプロセスをより深く見ていきましょう。
Cosignは、GoogleがLinux Foundation Projectと共同で開発したsigstoreプロジェクトの一部です。私たちのシナリオでデプロイするイメージの信頼性の問題を解決するために、署名プロセスにCosignを使用します。
これは、シンプルでセキュアです。コンテナに署名する手順を紹介します。
公式リポジトリをクローンします:
$ git clone https://github.com/sigstore/cosign $ cd cosign $ go install ./cmd/cosign $ $(go env GOPATH)/bin/cosign
キーペアの生成は簡単で、コマンドを実行すると、KDFとしてscrypt、暗号化にはnacl/secretboxを使用して、パスワードの下で鍵が暗号化されます。
cosign generate-key-pair
Cosignの鍵を生成する
全く新しい鍵ペア、cosign.key
と cosign.pub
を生成します。
秘密鍵は、CIシステムの一部としてシークレットマネージャーに保存されたパスワードで復号/署名できるように管理するとセキュアです。HashiCorp VaultやAmazon KMSを使用することもできます。ただし、シークレットへのアクセスはパイプラインのスコープに限定し、使用しているCI/CDツールでグローバルにアクセスできないようにします。
私たちは署名プロセスのすべての要素を持っており、コンテナに署名できるのは私たちだけであることを保証するために秘密鍵を使用していますが、これはパブリックを通して検証することができます。
まず最初に、書き込み権限のあるレジストリにイメージを公開する必要があります。書き込み権限のないレジストリからイメージに署名することはできません(UNAUTHORIZED: 認証が必要です)
cosign sign --key cosign.key alpine:3.5
unauthorized - autentication required cosign error(認証が必要です)
また、レジストリにまだ公開されていないイメージに署名することはできません。そのため、ローカルイメージに署名しようとするとエラーになります(accessing entity: エンティティがレジストリに見つかりません。):
docker tag alpine:3.5 guillermopal/contenttrust:not_uploaded cosign sign --key cosign.key guillermopal/contenttrust:not_uploaded
エンティティがレジストリに見つかりません cosign エラー
この例では、最初のステップとしてalpineイメージをリタグし、それをレジストリにプッシュします。署名するイメージを識別するために "signed" というタグを使用します。
docker pull alpine:3.5 docker tag alpine:3.5 guillermopal/contenttrust:signed docker push guillermopal/contenttrust:signed
cosign docker push image signed
これができたら、イメージの署名に進みます。
cosign sign --key cosign.key guillermopal/contenttrust:signed
cosign sign key コンテナイメージ
このコマンドは、OCIレジストリに新しいタグ SHAxxx.sig
を作成します。タグの名前には、"signed" というタグでアップロードされたイメージのダイジェストが含まれていることが確認できます。Docker Hubイメージの署名
以上の簡単な手順で、イメージに署名する処理が終了し、イメージの署名はイメージと同じコンテナレジストリで確認できるようになりました。
署名されたイメージに対する署名の検証を実行するために、今度は公開鍵のcosign.pub
を使用します。出力はこのようになります:
cosign verify --key cosign.pub guillermopal/contenttrust:signed | jq .
Cosign verify の署名
署名のないコンテナを検証しようとすると、イメージに署名がされていないため、エラー(no_matching signatures)が発生することが予想されます。
docker pull alpine:3.7 docker tag alpine guillermopal/contenttrust:unsigned docker push guillermopal/contenttrust:unsigned cosign verify --key cosign.pub guillermopal/contenttrust:unsigned
さらに、署名はタグに依存するのではなく、イメージの内容に依存するため、変更されたイメージをアップロードして検証しようとすると、エラーが発生します。
docker tag alpine:3 guillermopal/contenttrust:signed docker push guillermopal/contenttrust:signed cosign verify --key cosign.pub guillermopal/contenttrust:signed
Cosign verify エラー 一致する署名がない
それでも、イミュータブルタグを使用することは重要です。タグの変異は開発目的には便利かもしれませんが、実稼働環境で使用する際にはリスクが生じます。
イメージに署名することで、この問題を軽減することができますが、両方の戦略を同時に使用することで、セキュリティの層を厚くすることができます。
イメージに署名することは、サプライチェーン攻撃を防ぐことができるもう一つのセキュリティレイヤーです。とはいえ、すべてを解決してくれる魔法の杖ではないので、適切に実装する必要があります。
サプライチェーン攻撃は基本的に、攻撃者が最終的な成果物がビルドされる開発パイプラインにアクセスすることで成立します。いったんアクセスできてしまうと、悪意のあるアーティファクトを生成し、それを有効なアーティファクトとして配布することを妨げるものは何もありません。イメージ署名を入れることで、私たちはお客様に成果物を確認する方法を提供しました。
このプロセスを開発パイプラインから排除することは、開発パイプラインも危険にさらされないようにするために重要です。開発チームは、セキュリティによって検証された後、その品質を保証するために署名されたアーティファクトを生成することができます。
署名してレジストリにアップロードした後、次のステップはこの署名付きイメージをKubernetesにデプロイすることです。署名付きイメージの考え方は、署名付きイメージのみがクラスターで実行できることをクラスター内で検証することです。
これを実装するための最初のオプションは、Cosigned Admission Webhookを使用することです。
Webhook をインストールするには、まず鍵のパスワードを秘密にしておくために環境変数にエクスポートします。次に、リポジトリの指示に従って、helmパッケージマネージャーを使ってインストールします。
kubectl create namespace cosign-system kubectl create secret generic mysecret -n cosign-system \ --from-file=cosign.pub=./cosign.pub \ --from-file=cosign.key=./cosign.key \ --from-literal=cosign.password=$COSIGN_PASSWORD helm repo add sigstore https://sigstore.github.io/helm-charts helm repo update
helm cosign install kubectl
helm install cosigned -n cosign-system sigstore/cosigned --devel --set cosign.secretKeyRef.name=mysecret
kubectl enable webhook cosignを実行します。
webhookをインストールしたら、有効化したいネームスペースごとに有効化する必要があります。機能を有効にするためには、対象のネームスペースに適切なラベルを追加するだけです。
kubectl label --overwrite namespace/testcontenttrust cosigned.sigstore.dev/include=true
cosignデプロイ部分
このようにした後、署名されていないイメージは実行できません(検証に失敗しました:一致する署名がありません)。
kubectl run test --image=guillermopal/contenttrust:unsigned -n testcontenttrust
cosign run test kubectl
では、署名済みのイメージを実行してみます。
kubectl run test --image=guillermopal/contenttrust:signed -n testcontenttrust
kubectl run 署名済みコンテナ成功
とはいえ、このWebhookにはブロックや通知の機能がなく、パラメータとして鍵やパスワードを指定する必要があるので、これを防いで検証したい公開鍵を指定するだけにしたいところです。
また、このWebhookでは、1つの署名しか検証できないことも大きな特徴です。
この問題を解決するため、あるいはこの機能を拡張する必要がある場合は、Connaisseur を使用します。
Githubページにあるように、Connaisseurは高度な動作を備えているアドミッション・コントローラーです。
コンテナイメージの署名検証と信頼の固定をクラスターに統合するためのKubernetesアドミッション・コントローラーです。
このアドミッション・コントローラーでは、分析対象となるネームスペースを決定したり、デプロイメントをAlerting OnlyまたはBlockingのいずれかに変更したりすることができます。
デフォルトでは、Connaisseurは公式のDocker Hubイメージとプロジェクトのイメージの署名を検証するよう設定されています。ここでは、私たちが生成した鍵で署名されたものも検証するように設定します。
インストール方法はcosignと同様です:
git clone https://github.com/sse-secure-systems/connaisseur.git cd connaisseur helm install connaisseur helm --atomic --create-namespace --namespace connaisseur
helmによるconnaisseurのインストール
このように、デフォルトの設定では、docker hubから公式イメージを実行することはできますが、インターネット上の認証局で署名されたものは拒否されています。この場合、チェックはグローバルに適用されるので、ネームスペースにアノテーションを付ける必要はありません。ショーケースとして、デフォルトのネームスペースで実行してみます(トラストルート "default "がバリデータ "default "に設定されていない)。
kubectl run hello-world --image=docker.io/hello-world
kubectl run docker signed image
kubectl run contenttrust --image=guillermopal/contenttrust:signed
trust root default が validator に設定されていない エラー、connaisseur のデプロイイメージコンテナ
以前に生成したcosignキーで署名されたイメージを実行したい場合は、connaisseurのデプロイを変更する必要があります。
ドキュメントを見ると、独自の公開鍵を追加する方法が説明されているセクションがあります。レポで提供されている values.yaml ファイルにアクセスし、デフォルトのバリデータを修正して publicKey を追加し、タイプを cosign に変更し、ホスト指定を削除するだけで、簡単に行えます。次の図は、修正したファイルの例です。connaisseur values.yaml コンフィギュレーション
その後、新しい値でチャートをデプロイします。
helm upgrade connaisseur helm --atomic --create-namespace --namespace connaisseur
helm アップグレード connaisseur helm
そして、これで署名済みのイメージを実行できるようになります。
kubectl run contenttrust --image=guillermopal/contenttrust:signed
署名付きイメージの実行成功 connaisseur
これに加えて、さらに制限を加えてDocker Hubで署名されたイメージのデプロイを拒否することも可能です。そのためには、connaisseurのデフォルト値に付属している事前設定済みのバリデータを無効にする必要があります。helm/values.yamlファイルに移動して、dockerhub-basics validatorをコメントアウトするだけです。デフォルトのバリデータを削除する values.yaml connaisseurの設定
この修正版をデプロイすると、Docker Hubの署名付きイメージをデプロイできなくなります(バリデータの設定が見つからない dockerhub-basics)。update help connaisseur not default validator を実行します
kubectl run hello-world --image=docker.io/hello-world
バリデーターdockerエラーconnaisseurを見つけることができません
上記の例でわかるように、ソフトウェアのライフサイクルにイメージ署名を組み込むことは簡単で、パイプライン全体の信頼を確保することができます。
Cosignを使用することで、外部サービスを必要としないシステムを簡単にデプロイでき、最初の信頼レベルを設定することができます。CosignはConnaisseurと共に、Kubernetesクラスターで動作しているイメージが検証されていることを保証します。
本番環境に移行すると、おそらくデプロイをブロックしないように、検出ポリシーとアラート機能を使用した警告だけから始めることになると思います。ブロックできるようになったら、簡単にポリシーを変更し、実施することができます。
これで、セキュアな Kubernetes デプロイを段階的に実装する方法と、署名検証に関わるすべてのアプリケーションをご理解いただけたと思います。
サードパーティアプリケーションへのアラートについては、ドキュメントで定義されているように helm 値を変更するだけです。最終的に、KubernetesクラスターからのアラートをSysdigで簡単に実装する方法を知りたい方は、次のステップを踏んでください。
sysdig.json
ファイルのテンプレートを $connaisseur_home/helm/alert_payload_templates
に作成します:
{ "events": [ { "timestamp": " {{ timestamp }}", "rule": "Check image signature", "priority": " {{priority}}", "output": "{{ alert_message }} for Image {{ images }}", "source": "Connaisseur AC", "tags": [ "foo", "bar" ], "output_fields": { "field1": "value1", "field2": "value2" } } ], "labels": { "image": "{{ images }}", "message": "{{ alert_message }}" } }
values.yamlを以下のような感じに修正します:YAML connaisseur Sysdig バリデーター
認証トークンはSysdig Secureのプロファイル設定から取得することができます。すべてのリクエストで、自動生成されるイベントがあります。
リクエストの承認
リクエストの拒否