はじめに
サプライチェーンセキュリティは、ワークロードがクラスター内で実行される前から始まります。イメージに不要なツールが含まれていたり、出所が不明確であったり、承認されていないタグが付与されていたりすると、以前学習したランタイム制御のリスクが高まります。最小限の承認済みイメージには、小さなビルドコンテキスト、明確なベースイメージ、明示的なアプリケーションのエントリポイント、有用なメタデータラベル、そしてターゲットクラスターで実行可能であるという証拠が必要です。
この実験では、小さなローカルイメージのビルドコンテキストを作成し、labex-v134 minikube プロファイル内でイメージをビルドし、生成されたイメージのメタデータを検査し、imagePullPolicy: Never を指定してデプロイし、Kubernetes からランタイムの内容を検証します。この実験は実践的な範囲に留めています。まだイメージのスキャンやレジストリポリシーの強制は行いません。ここでは、サプライチェーンの最初の習慣である「小さく、名前が付けられ、検査可能なイメージを構築し、その中身を証明する」ことを学びます。
小さなイメージビルドコンテキストの作成
このステップでは、イメージの元となるファイルを作成します。ビルドコンテキストとは、イメージビルダーに送信されるディレクトリのことです。ビルドコンテキストを小さく保つことは重要です。余分なファイルがあるたびに、不要なものがイメージにコピーされるリスクが生じるからです。
実験ワークスペースから開始します。
cd /home/labex/project/minimal-approved-image
実験ノートを読みます。
sed -n '1,160p' IMAGE_BUILD_NOTES.md
CKS コンテキストとネームスペースを確認します。
kubectl config current-context | tee context-check.txt
kubectl get namespace cks-prep-image | tee namespace-check.txt
アプリケーションスクリプトを作成します。このスクリプトは、準備完了メッセージとランタイムの UID を出力し、Kubernetes が検査のためにコンテナを実行し続けられるようにスリープします。
cat > app.sh <<'EOF'
#!/bin/sh
set -eu
echo "minimal approved image ready"
echo "runtime uid: $(id -u)"
sleep 300
EOF
chmod 0755 app.sh
Dockerfile を作成します。busybox:1.36 は、この実験のための小さなベースイメージです。OCI スタイルのラベルは、この実験でなぜこのイメージが承認されているかを記録し、USER 65534:65534 はデフォルトで非ルートの数値ユーザーとしてイメージを実行するように設定します。
cat > Dockerfile <<'EOF'
FROM busybox:1.36
LABEL org.opencontainers.image.title="minimal-approved-app"
LABEL org.opencontainers.image.source="local-cks-prep"
LABEL security.labex.io.approved="true"
WORKDIR /app
COPY app.sh /app/app.sh
RUN chmod 0555 /app/app.sh
USER 65534:65534
ENTRYPOINT ["/app/app.sh"]
EOF
ビルドコンテキストのインベントリとアプリケーションスクリプトのチェックサムを保存します。
find . -maxdepth 1 -type f -printf '%f\n' | sort | tee build-context-files.txt
sha256sum app.sh | tee app-sha256.txt
sed -n '1,120p' Dockerfile | tee dockerfile-review.txt
ディレクトリには Dockerfile、app.sh、ノート、および保存した証拠ファイルが含まれるようになります。重要なイメージの境界線は Dockerfile 内の COPY 行です。app.sh だけがイメージ内の /app/app.sh にコピーされます。
Minikube プロファイル内でのイメージビルド
このステップでは、labex-v134 minikube プロファイル内でイメージをビルドします。Kubernetes は minikube ノードのランタイムからコンテナを実行するため、そこでビルドすることが重要です。外側の VM Docker デーモンでイメージをビルドしただけでは、minikube ノードからそれが見えない可能性があります。
明確なローカルタグを付けてイメージをビルドします。
minikube -p labex-v134 image build -t labex/minimal-approved:1.0 .
minikube プロファイル内のイメージを検査します。最初のコマンドは、イメージがノードランタイムに存在することを確認します。2 番目のコマンドは、設定されたユーザー、エントリポイント、ラベルなどの主要なメタデータを保存します。
minikube -p labex-v134 ssh -- 'docker image ls labex/minimal-approved:1.0' | tee built-image-list.txt
minikube -p labex-v134 ssh -- 'docker image inspect labex/minimal-approved:1.0' | tee built-image-inspect.json
jq を使用して、承認の証拠として重要なフィールドを抽出します。
jq '.[0].Config | {User, Entrypoint, Labels}' built-image-inspect.json | tee built-image-approval.json
built-image-list.txt にイメージタグが表示され、User が 65534:65534 に設定され、Entrypoint が /app/app.sh に設定され、承認ラベルが true に設定されていることが確認できるはずです。
Kubernetes での承認済みイメージの実行
このステップでは、Kubernetes でローカルイメージを実行します。イメージはレジストリにプッシュされるのではなく minikube プロファイル内でビルドされたため、Pod は imagePullPolicy: Never を使用します。この設定は、Kubernetes に対してローカルノードのイメージを使用するように指示し、存在しない場合は失敗するようにします。
Pod マニフェストを作成します。
cat > approved-image-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: approved-image-app
namespace: cks-prep-image
labels:
app: approved-image-app
supply-chain.labex.io/approved: "true"
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
containers:
- name: app
image: labex/minimal-approved:1.0
imagePullPolicy: Never
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
EOF
Pod を適用し、準備ができるまで待ちます。
kubectl apply -f approved-image-pod.yaml
kubectl wait --for=condition=Ready pod/approved-image-app -n cks-prep-image --timeout=120s
Pod のログと現在の Pod YAML を保存します。
kubectl logs approved-image-app -n cks-prep-image | tee approved-image-logs.txt
kubectl get pod approved-image-app -n cks-prep-image -o yaml | tee approved-image-pod.current.yaml
ログには minimal approved image ready および runtime uid: 65534 と表示されるはずです。これは、Kubernetes がイメージのエントリポイントを意図した非ルートユーザーとして実行していることを証明しています。
ランタイム内容の検証
このステップでは、Kubernetes から実行中のコンテナを検査します。イメージの承認はタグ名だけで終わらせてはいけません。ランタイムに期待されるアプリケーションパスが含まれており、イメージの一部であるはずのない明らかな余分なファイルが含まれていないという証拠が必要です。
アプリケーションディレクトリを検査します。
kubectl exec -n cks-prep-image approved-image-app -- sh -c 'ls -la /app; test -f /app/app.sh; echo APP_PATH_OK' | tee runtime-app-path.txt
ランタイムユーザーを確認し、不要なデバッグファイルがないか簡単なネガティブチェックを行います。
kubectl exec -n cks-prep-image approved-image-app -- sh -c 'id -u; test ! -e /app/debug-tool && echo NO_DEBUG_TOOL' | tee runtime-content-check.txt
Kubernetes の状態とイメージのメタデータを組み合わせた、コンパクトな最終承認記録を保存します。
kubectl get pod approved-image-app -n cks-prep-image -o json | jq '{image: .spec.containers[0].image, imagePullPolicy: .spec.containers[0].imagePullPolicy, podSecurityContext: .spec.securityContext, containerSecurityContext: .spec.containers[0].securityContext}' | tee final-pod-approval.json
jq '.[0].Config | {User, Entrypoint, Labels}' built-image-inspect.json | tee final-image-approval.json
最終的な証拠には、ローカルの承認済みイメージタグ、imagePullPolicy: Never、非ルートのランタイム設定、/app/app.sh エントリポイント、および承認ラベルが含まれている必要があります。
まとめ
この CKS 対策コースの最初のサプライチェーンワークフローである「最小限の承認済みイメージの構築と証明」を練習しました。小さなビルドコンテキストを作成し、labex-v134 minikube プロファイル内で labex/minimal-approved:1.0 をビルドし、イメージのメタデータとラベルを検査し、imagePullPolicy: Never を指定してローカルイメージをデプロイし、Kubernetes からランタイムユーザーとアプリケーションの内容を検証しました。今後のサプライチェーン関連の実験では、マニフェストスキャン、Helm 出力のレビュー、SBOM とチェックサムの証拠、信頼されたレジストリ制御、ビルドシークレットのクリーンアップなど、この基礎の上に構築していきます。



