コンテナの「中身」を誰が保証するのか?――サプライチェーン攻撃を無効化するデジタル署名の流儀
現場のエンジニア諸君、お疲れ様。今日もどこかでコンテナがデプロイされていることだろう。だが、君たちが docker pull しているそのイメージ、本当に「君たちがビルドしたもの」と同じものだろうか?
昨今、攻撃者はアプリの脆弱性そのものよりも、そのアプリが構築される「パイプライン」を狙う。悪意ある第三者がレジストリ上のイメージをすり替えたり、中間者攻撃で不正なバイナリを混入させたりすれば、いくらコードをセキュアに書いても一巻の終わりだ。
今日は、コンテナセキュリティの防波堤となる「脆弱性スキャン」と「署名検証(Cosign)」について、泥臭い実務の視点から解説する。
—
1. 脆弱性スキャン(Trivy)は「入り口」に過ぎない
まず前提として、Trivy 等を用いたスキャンは必須だ。だが、多くの現場で誤解されているのは「スキャンをパスすれば安全」という幻想だ。
スキャンはあくまで「既知の脆弱性(CVE)」に対する検知に過ぎない。未知のゼロデイ攻撃や、悪意を持って混入されたバックドアはスキャナーでは見抜けない。ここで重要になるのが「このイメージは、信頼できるビルドプロセスを経て生成されたものである」という証明(署名)だ。
—
2. Sigstore/Cosignによる署名:誰が保証するか
Cosign は、公開鍵暗号(ECC: 楕円曲線暗号)を利用してコンテナイメージにデジタル署名を行うツールだ。RSAよりも計算コストが低く、かつ同等の強度を持つECCの採用は、高負荷なCI/CD環境において非常に理にかなっている。
なぜ署名が必要なのか(攻撃シナリオ)
攻撃者がレジストリに侵入し、正規のイメージに cryptominer(仮想通貨マイナー)を仕込んだマルウェアを紛れ込ませたとしよう。署名がない場合、Kubernetesクラスターは「最新のタグがついているから」という理由で、その汚染されたイメージをそのまま実行してしまう。
Cosignで署名検証を強制すれば、「署名がない」または「署名が改ざんされている」イメージは、実行時に即座に拒否される。 これがサプライチェーン攻撃を物理的に遮断するロジックだ。
—
3. 実践:Cosignによる署名と検証のワークフロー
まずは、GitHub Actions等でイメージをビルドした直後に署名を行う手順を見ていこう。
署名(CI側:GitHub Actionsの例)
秘密鍵をGitHubのシークレットに保存し、ビルド後のイメージに署名する。
# .github/workflows/build.yml の一部
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Build and Push
run: |
docker build -t my-repo/my-app:latest .
docker push my-repo/my-app:latest
# Cosignによる署名(秘密鍵を使用)
- name: Sign the image
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
COSIGN_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
run: |
cosign sign --key env://COSIGN_KEY my-repo/my-app:latest
検証(Kubernetes側:Admission Controller)
次に、クラスタ側で「署名がないものは動かさない」設定だ。Kyverno や Policy Controller を使うのが定石だが、ここではシンプルに Cosign コマンドで検証する仕組みを理解してほしい。
# 公開鍵を使ってイメージの正当性を検証するコマンド
# 署名が一致しなければ、ここで exit code 1 が返る
cosign verify --key cosign.pub my-repo/my-app:latest
—
4. 現場の盲点:鍵管理と信頼の連鎖
ここで一つ、実務的な警告をしておく。「署名鍵の管理」こそが最大の弱点だ。
もし秘密鍵が流出したら、攻撃者は正規の署名付きマルウェアを作成できてしまう。
- 鍵のローテーション: 鍵は定期的に入れ替えろ。
- Keyless署名: 最近のトレンドは、Sigstoreの仕組みを活用した「Keyless署名(OIDCトークンを利用した署名)」だ。これなら秘密鍵の管理リスクをゼロにできる。
Keyless署名のコマンド例
# 秘密鍵をファイルとして保持せず、GitHubのOIDC IDトークンで署名する
cosign sign --yes my-repo/my-app:latest
—
5. まとめ:エンジニアとしての矜持
セキュリティとは、ツールを導入して満足するチェックリストではない。「どこでデータが改ざんされる可能性があるか」「誰がこのコードを承認したのか」を技術的に強制する仕組みを構築することだ。
1. Trivy で既知の穴を塞ぐ。
2. Cosign で「誰が作ったか」を保証する。
3. Admission Controller で、署名なきデプロイを即座に拒否する。
この3段構えを構築できれば、君たちのシステムは一般的なWebアプリよりも数段堅牢な領域に達するはずだ。
「面倒くさい」と感じるかもしれない。だが、インシデント対応で徹夜することに比べれば、この実装のコストなど微々たるものだ。自分の書いたコードが、誰の手によって実行されるのか。最後まで責任を持つのが、一流のエンジニアの流儀だよ。
さて、次は君たちのレジストリに cosign.pub を配置するところから始めようか。健闘を祈る。
コメント