署名検証でKubernetesのデプロイメントをセキュアにする方法

By 清水 孝郎 - JULY 12, 2022

SHARE:

本文の内容は、2022年7月12日にGuillermo Palaciootoが投稿したブログ(https://sysdig.com/blog/secure-kubernetes-deployment-signature-verification/)を元に日本語に翻訳・再構成した内容となっております。

Kubernetesクラスターでコンテナを実行する場合、デプロイするイメージを信頼することが、セキュリティを強化するための鍵となります。ミュータブルイメージの使用は、セキュアなKubernetesデプロイメントに対するリスクを表し、期待するものを確実に実行するための信頼できるメカニズムを持つことの重要性を浮き彫りにしています。

Secure Kubernetes Deployment with Signature Cosign ConnaisseurSignature Cosign Connaisseurを使用したセキュアなKubernetesデプロイメント

このブログでは、セキュアなKubernetesデプロイメントを実装する方法を段階的に学習します。 既知の機関による署名の検証とアドミッションコントローラーによる検証なしでは、クラスター内で何も実行されないようにするソリューションをセットアップできます。

デプロイするイメージに信頼が必要な理由

次のようなシナリオを考えてみましょう。私たちは大企業でセキュリティエンジニアとして働いていますが、最近データ漏洩がありました。フォレンジック調査の結果、この情報漏洩は、私たちのKubernetesプロダクションクラスターにデプロイされた悪質なコードに関連していることが判明しました。

コンテナイメージを検証するために、CI/CDパイプラインでイメージスキャンを使用していましたが、何らかの理由でコンテナが社内のOCIレジストリに公開され、クラスターにデプロイされました。私たちのチームは、デプロイメントにミュータブルタグを使用しているため、セキュリティツールもイメージを受け入れたようで、アドミッション・コントローラはイメージを受け入れました。しかし、イメージ名だけが検証されました。

コンテンツ信頼性ポリシーの実装では、イメージの署名と検証を要求する予定です。

これにより、今後Kubernetesクラスターにデプロイされるイメージは、私たちのパイプラインで先に検証されたものと同じであり、したがって、私たちのセキュリティプロセス内で認証されていることが保証されます。

ステップバイステップのセキュアなKubernetesデプロイメント

デプロイメントステップでKubernetesをセキュアにしたい場合、使用するコンテナに脆弱性がないことを確認したら、すべてのコンテナに署名する必要があります。

Secure Kubernetes deployment processセキュアなKubernetesのデプロイメントプロセス

コンテナに署名したら、それを公式レジストリと共有します。その結果、クラスターにデプロイできるのは、当社または公式レジストリが署名したコンテナのみとなります。

それでは、署名検証のプロセスをより深く見ていきましょう。

Cosignによるイメージ署名プロセス

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 generate keysCosignの鍵を生成する

全く新しい鍵ペア、cosign.key と cosign.pubを生成します。

秘密鍵は、CIシステムの一部としてシークレットマネージャーに保存されたパスワードで復号/署名できるように管理するとセキュアです。HashiCorp VaultAmazon KMSを使用することもできます。ただし、シークレットへのアクセスはパイプラインのスコープに限定し、使用しているCI/CDツールでグローバルにアクセスできないようにします。

私たちは署名プロセスのすべての要素を持っており、コンテナに署名できるのは私たちだけであることを保証するために秘密鍵を使用していますが、これは公開鍵によって検証することができます。

コンテナ署名のトラブルシューティング

まず最初に、書き込み権限のあるレジストリにイメージを公開する必要があります。書き込み権限のないレジストリからイメージに署名することはできません(UNAUTHORIZED: 認証が必要です)

cosign sign --key cosign.key alpine:3.5

unauthorized - autentication required cosign errorunauthorized – autentication required cosign error(認証が必要です)

また、レジストリにまだ公開されていないイメージに署名することはできません。そのため、ローカルイメージに署名しようとするとエラーになります(accessing entity: エンティティがレジストリに見つかりません。):
docker tag alpine:3.5 guillermopal/contenttrust:not_uploaded
cosign sign --key cosign.key guillermopal/contenttrust:not_uploaded

entity not found in registry cosign errorエンティティがレジストリに見つかりません 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 signedcosign docker push image signed

これができたら、イメージの署名に進みます。

cosign sign --key cosign.key guillermopal/contenttrust:signed

cosign sign key container imagecosign sign key コンテナイメージ

このコマンドは、OCIレジストリに新しいタグ SHAxxx.sig を作成します。タグの名前には、”signed” というタグでアップロードされたイメージのダイジェストが含まれていることが確認できます。

Docker Hub image signedDocker Hubイメージの署名

以上の簡単な手順で、イメージに署名する処理が終了し、イメージの署名はイメージと同じコンテナレジストリで確認できるようになりました。

署名の検証プロセス

署名されたイメージに対する署名の検証を実行するために、今度は公開鍵のcosign.pubを使用します。出力はこのようになります:

cosign verify --key cosign.pub guillermopal/contenttrust:signed | jq .

Cosign verify signature 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

Cosign verify no matching signatures error
さらに、署名はタグに依存するのではなく、イメージの内容に依存するため、変更されたイメージをアップロードして検証しようとすると、エラーが発生します。

docker tag alpine:3 guillermopal/contenttrust:signed
docker push guillermopal/contenttrust:signed
cosign verify --key cosign.pub guillermopal/contenttrust:signed

Cosign verify error no matching signaturesCosign verify エラー 一致する署名がない

それでも、イミュータブルタグを使用することは重要です。タグの変異は開発目的には便利かもしれませんが、実稼働環境で使用する際にはリスクが生じます。

イメージに署名することで、この問題を軽減することができますが、両方の戦略を同時に使用することで、セキュリティの層を厚くすることができます。

署名検証ステートメント

イメージに署名することは、サプライチェーン攻撃を防ぐことができるもう一つのセキュリティレイヤーです。とはいえ、すべてを解決してくれる魔法の杖ではないので、適切に実装する必要があります。

サプライチェーン攻撃は基本的に、攻撃者が最終的な成果物がビルドされる開発パイプラインにアクセスすることで成立します。いったんアクセスできてしまうと、悪意のあるアーティファクトを生成し、それを有効なアーティファクトとして配布することを妨げるものは何もありません。イメージ署名を入れることで、私たちはお客様に成果物を確認する方法を提供しました。

このプロセスを開発パイプラインから実施することは、開発パイプラインを危険にさらされないようにするために重要です。開発チームは、セキュリティによって検証された後、その品質を保証するために署名されたアーティファクトを生成することができます。

Cosignを使用したデプロイメントにおける署名の検証

署名してレジストリにアップロードした後、次のステップはこの署名付きイメージを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 kubectlhelm cosign install kubectl

helm install cosigned -n cosign-system sigstore/cosigned --devel --set cosign.secretKeyRef.name=mysecret

kubectl enable webhook cosignkubectl enable webhook cosignを実行します。

webhookをインストールしたら、有効化したいネームスペースごとに有効化する必要があります。機能を有効にするためには、対象のネームスペースに適切なラベルを追加するだけです。

kubectl label --overwrite namespace/testcontenttrust cosigned.sigstore.dev/include=true 

cosign deploy partcosignデプロイ部分

このようにした後、署名されていないイメージは実行できません(検証に失敗しました:一致する署名がありません)。

kubectl run test –-image=guillermopal/contenttrust:unsigned -n testcontenttrust

cosign run test kubectl cosign run test kubectl

では、署名済みのイメージを実行してみます。

kubectl run test –-image=guillermopal/contenttrust:signed -n testcontenttrust

kubectl run signed container successkubectl run 署名済みコンテナ成功

とはいえ、このWebhookにはブロックや通知の機能がなく、パラメータとして鍵やパスワードを指定する必要があるので、これを防いで検証したい公開鍵を指定するだけにしたいところです。

また、このWebhookでは、1つの署名しか検証できないことも大きな特徴です。

この問題を解決するため、あるいはこの機能を拡張する必要がある場合は、Connaisseur を使用します。

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

connaisseur installation via helmhelmによるconnaisseurのインストール

このように、デフォルトの設定では、docker hubから公式イメージを実行することはできますが、インターネット上の認証局で署名されたものは拒否されています。この場合、チェックはグローバルに適用されるので、ネームスペースにアノテーションを付ける必要はありません。ショーケースとして、デフォルトのネームスペースで実行してみます(トラストルート “default “がバリデータ “default “に設定されていない)。

kubectl run hello-world --image=docker.io/hello-world

kubectl run docker signed imagekubectl run docker signed image

kubectl run contenttrust --image=guillermopal/contenttrust:signed

trust root default not configured for validator error connaisseur deploy image containertrust root default が validator に設定されていない エラー、connaisseur のデプロイイメージコンテナ

以前に生成したcosignキーで署名されたイメージを実行したい場合は、connaisseurのデプロイを変更する必要があります。

ドキュメントを見ると、独自の公開鍵を追加する方法が説明されているセクションがあります。レポで提供されている values.yaml ファイルにアクセスし、デフォルトのバリデータを修正して publicKey を追加し、タイプを cosign に変更し、ホスト指定を削除するだけで、簡単に行えます。次の図は、修正したファイルの例です。

connaisseur values.yaml configuration connaisseur values.yaml コンフィギュレーション

その後、新しい値でチャートをデプロイします。

helm upgrade connaisseur helm --atomic --create-namespace --namespace connaisseur

helm upgrade connaisseur helmhelm アップグレード connaisseur helm

そして、これで署名済みのイメージを実行できるようになります。

kubectl run contenttrust --image=guillermopal/contenttrust:signed


run signed image success connaisseur

署名付きイメージの実行成功 connaisseur

これに加えて、さらに制限を加えてDocker Hubで署名されたイメージのデプロイを拒否することも可能です。そのためには、connaisseurのデフォルト値に付属している事前設定済みのバリデータを無効にする必要があります。helm/values.yamlファイルに移動して、dockerhub-basics validatorをコメントアウトするだけです。

remove default validator values.yaml connaisseur configurationデフォルトのバリデータを削除する values.yaml connaisseurの設定

この修正版をデプロイすると、Docker Hubの署名付きイメージをデプロイできなくなります(バリデータの設定が見つからない dockerhub-basics)。

update help connaisseur not default validatorupdate help connaisseur not default validator を実行します

kubectl run hello-world --image=docker.io/hello-world


unable to find validator docker error connaisseur バリデーターdockerエラーconnaisseurを見つけることができません

上記の例でわかるように、ソフトウェアのライフサイクルにイメージ署名を組み込むことは簡単で、パイプライン全体の信頼を確保することができます。


まとめ

Cosignを使用することで、外部サービスを必要としないシステムを簡単にデプロイでき、最初の信頼レベルを設定することができます。CosignはConnaisseurと共に、Kubernetesクラスターで動作しているイメージが検証されていることを保証します。

本番環境に移行すると、おそらくデプロイをブロックしないように、検出ポリシーとアラート機能を使用した警告だけから始めることになると思います。ブロックできるようになったら、簡単にポリシーを変更し、実施することができます。

これで、セキュアな Kubernetes デプロイを段階的に実装する方法と、署名検証に関わるすべてのアプリケーションをご理解いただけたと思います。



Sysdigでアラートとインシデントを管理する

サードパーティアプリケーションへのアラートについては、ドキュメントで定義されているように 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 validatorYAML connaisseur Sysdig バリデーター

認証トークンはSysdig Secureのプロファイル設定から取得することができます。すべてのリクエストで、自動生成されるイベントがあります。

リクエストの承認
admit request Sysdig dashboard
リクエストの拒否reject request Sysdig