イメージの固定と出所の統制
目標
クラスターに入ってくるイメージをダイジェストで固定し、出所を許可リストで絞り込み、プライベートレジストリの認証をServiceAccountのレベルで処理する、標準的な構成を作ります。
なぜ重要なのか
タグは変更可能なポインターです。同じv1.2タグに別のイメージをプッシュでき、そうすると、昨日検証したイメージと今日動いているイメージが違う可能性があります。ダイジェストは内容のハッシュなので、この問題を根本からなくします。事故が起きたときに「いま動いているものは正確には何か」にすぐ答えられるかどうかが、対応時間を左右します。
出所の統制はその次です。許可レジストリの一覧は、ポリシーエンジンのパラメーターにそのまま入り、imagePullSecretsはそのレジストリにアクセスするための認証情報です。Podごとに書くより、ServiceAccountに付けておけば、新しいワークロードが自動的に継承するため、漏れが減ります。
この環境にはインターネットがないため、実際にイメージを取得することも、KyvernoのようなポリシーエンジンのCRDをインストールすることもできません。そのため、ポリシーのマニフェストはファイルとして書いて採点し、残りは実際のオブジェクトで採点します。
ステップ
- ネームスペース
cks-supplyを作成し、その中にPodpinnedを作成してください。イメージはタグなしで、ダイジェストで固定します(registry.cks.local/shop/api@sha256:の後ろに64桁の16進数)。imagePullPolicyはIfNotPresentにします。 /root/cks-supply-chain/allowed-registries.txtに、許可レジストリのprefixを1行に1つずつ、次の順序のとおりに4行書いてください。registry.cks.local/、harbor.cks.local/library/、registry.k8s.io/、ghcr.io/cks-lab/cks-supplyにkubernetes.io/dockerconfigjsonタイプのSecretharbor-pullを作成してください。サーバーはharbor.cks.local、ユーザーはrobot$cks、パスワードはcks-lab-tokenにします。cks-supplyにServiceAccountdeployerを作成し、そのimagePullSecretsにharbor-pullを入れてください。/root/cks-supply-chain/verify-images.yamlに、署名検証ポリシーのマニフェストを書いてください。apiVersion: kyverno.io/v1、kind: ClusterPolicyで、名前はverify-image-signature、spec.validationFailureActionはEnforceとし、ルール1つ(spec.rules[0].nameはcheck-signature)が、verifyImages[0].imageReferences[0]でregistry.cks.local/*を対象にします。cks-supplyにPodlegacy-web(イメージnginx:latest)を作成して、固定されていないワークロードを再現してください。そのあと、/root/cks-supply-chain/find-unpinned.shに検知スクリプトを書き、実行結果を/root/cks-supply-chain/unpinned.txtに保存してください。結果は、cks-supplyネームスペースで、イメージが@sha256:で固定されていないコンテナを持つPodを、cks-supply/<파드이름>形式(プレースホルダーはPod名です)で、辞書順に並べて1行に1つずつ入れたものです。cks-supplyにDeploymentcheckoutを作成してください。replicasは2、セレクターとPodラベルはapp=checkout、serviceAccountNameはdeployer、コンテナ名はapp、イメージはregistry.cks.local/で始まり、@sha256:で固定されたもの、imagePullPolicyはIfNotPresentにします。
参考
- 64桁のダイジェストが必要なら、
head -c 32 /dev/urandom | sha256sumの代わりにecho cks | sha256sumのように、任意の文字列のハッシュを使っても構いません。形式さえ合っていれば大丈夫です。 kubectl create secret docker-registry harbor-pull --docker-server=... --docker-username=... --docker-password=... -n cks-supplykubectl patch serviceaccount deployer -n cks-supply -p '{"imagePullSecrets":[{"name":"harbor-pull"}]}'- よくある間違い1: イメージにタグとダイジェストを併記してしまいます(
nginx:1.27@sha256:...)。このラボでは、ダイジェストだけを残します。 - よくある間違い2: 許可リストのprefixの末尾のスラッシュを抜くと、
registry.cks.local.evil.com/のような名前が通過してしまいます。
タグの代わりにダイジェストで固定する
ネームスペースcks-supplyを作成し、その中にPodpinnedを作成してください。イメージはタグなしで、ダイジェストで固定します(registry.cks.local/shop/api@sha256:の後ろに64桁の16進数)。imagePullPolicyはIfNotPresentにします。
イメージの参照は<레지스트리>/<이름>@sha256:<64자리 16진수>の形です(プレースホルダーはレジストリ、イメージ名、64桁の16進数です)。タグとダイジェストを併記せず、ダイジェストだけを残してください。
許可レジストリの一覧
/root/cks-supply-chain/allowed-registries.txtに、許可レジストリのprefixを1行に1つずつ、次の順序のとおりに4行書いてください。registry.cks.local/、harbor.cks.local/library/、registry.k8s.io/、ghcr.io/cks-lab/
ポリシーエンジンのパラメーターにそのまま渡される一覧です。prefixの形で書き、末尾にスラッシュを付けないと、名前の似た別のレジストリが通過してしまいます。
レジストリ認証のSecret
cks-supplyにkubernetes.io/dockerconfigjsonタイプのSecretharbor-pullを作成してください。サーバーはharbor.cks.local、ユーザーはrobot$cks、パスワードはcks-lab-tokenにします。
kubectl create secret docker-registryの1行で済みます。タイプ名はkubernetes.io/dockerconfigjsonで、データのキーは.dockerconfigjsonです。
ServiceAccountにpull Secretを付ける
cks-supplyにServiceAccountdeployerを作成し、そのimagePullSecretsにharbor-pullを入れてください。
ServiceAccountのimagePullSecretsに入れておけば、そのSAを使うすべてのPodが自動的に認証されます。Podごとに書くより優れています。
署名・SBOM検証ポリシーのマニフェスト
/root/cks-supply-chain/verify-images.yamlに、署名検証ポリシーのマニフェストを書いてください。apiVersion: kyverno.io/v1、kind: ClusterPolicyで、名前はverify-image-signature、spec.validationFailureActionはEnforceとし、ルール1つ(spec.rules[0].nameはcheck-signature)が、verifyImages[0].imageReferences[0]でregistry.cks.local/*を対象にします。
このクラスターにはポリシーエンジンのCRDがないため、ファイルとしてのみ書きます。imageReferencesで対象のレジストリを選び、attestorsに公開鍵を入れる構造です。
ダイジェスト未固定のPodを検知する
cks-supplyにPodlegacy-web(イメージnginx:latest)を作成して、固定されていないワークロードを再現してください。そのあと、/root/cks-supply-chain/find-unpinned.shに検知スクリプトを書き、実行結果を/root/cks-supply-chain/unpinned.txtに保存してください。結果は、cks-supplyネームスペースで、イメージが@sha256:で固定されていないコンテナを持つPodを、cks-supply/<파드이름>形式(プレースホルダーはPod名です)で、辞書順に並べて1行に1つずつ入れたものです。
イメージの文字列に@sha256:がなければ、未固定です。jqのcontainsやtestで絞り込めます。initコンテナも一緒に見る必要があります。
総合: 固定・認証・出所をすべて備えたデプロイ
cks-supplyにDeploymentcheckoutを作成してください。replicasは2、セレクターとPodラベルはapp=checkout、serviceAccountNameはdeployer、コンテナ名はapp、イメージはregistry.cks.local/で始まり、@sha256:で固定されたもの、imagePullPolicyはIfNotPresentにします。
先に作成したServiceAccountと、許可レジストリの一覧の最初のprefixをそのまま使います。イメージはダイジェストで固定し、imagePullPolicyも一緒に指定してください。