コンテナの「中身」を誰が保証するのか?――サプライチェーン攻撃を無効化する署名検証とスキャンの極意
現場でコードを書いていると、「動けば正義」という誘惑に駆られることがある。だが、セキュリティの最前線にいる我々から見れば、その「動いているもの」が、実は見知らぬ誰かが仕込んだバックドアかもしれないという現実に恐怖を感じるべきだ。
特にコンテナ環境において、「Docker Hubから落とした公式イメージだから大丈夫」と信じ込むのは、鍵のかかっていない玄関に財布を置くのと同じだ。今日は、CI/CDパイプラインに Cosign による署名検証と脆弱性スキャンを組み込み、「信頼できないコードを1バイトたりとも実行させない」ための防衛ラインを構築する方法を伝授する。
—
なぜ「署名」が重要なのか?(PoCのリスク)
攻撃者が狙うのは、リポジトリの改ざんや、中間者攻撃(MITM)によるイメージのすり替えだけではない。最も巧妙なのは、「正規のビルドプロセスを汚染する」ことだ。
例えば、CI環境の環境変数を盗み出した攻撃者が、ビルド中に悪意のあるライブラリを注入したとしよう。脆弱性スキャンが「既知の脆弱性」しか見ていない場合、ゼロデイ攻撃やバックドアはスルーされる。ここで重要なのが「署名」だ。
署名検証を行っていれば、CI/CDパイプラインという「信頼できる製造ライン」を通っていないイメージは、ランタイム側で「署名なし」として弾くことができる。これが、サプライチェーン防御の要だ。
—
実践:CI/CDへの統合ロードマップ
今回は GitHub Actions を例に、Trivy(脆弱性スキャン)と Cosign(署名検証)を統合するパイプラインを構築する。
1. 脆弱性スキャンの自動化 (Trivy)
まずはイメージに潜む既知の脆弱性を検知する。ビルド直後にスキャンし、CRITICAL な脆弱性があれば即座にパイプラインを停止させる設定だ。
# .github/workflows/build.yml
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# コンテナイメージのビルド
- name: Build image
run: docker build -t my-app:${{ github.sha }} .
# Trivyによるスキャン(重大な脆弱性があれば失敗させる)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
format: 'table'
exit-code: '1' # 重大な脆弱性が見つかればCIを落とす
severity: 'CRITICAL,HIGH'
2. コンテナイメージの署名 (Cosign)
次に、脆弱性がないと判定されたイメージに対して Cosign で署名を付与する。これには GitHub Actions の OIDC トークン(キーレス署名)を利用するのが今のベストプラクティスだ。管理が面倒な秘密鍵を扱う必要がない。
# Cosignによる署名付与
- name: Sign image with Cosign
run: |
# GitHub OIDCトークンを使用して署名
cosign sign --yes my-app:${{ github.sha }}
env:
COSIGN_EXPERIMENTAL: "true"
—
運用で死なないための「防御的」設定
署名を行うだけでなく、実行環境(Kubernetesなど)側でそれをチェックしなければ意味がない。Kyverno や Policy Controller を使い、署名のないイメージの実行を拒否するポリシーを適用する。
Kyverno ポリシーの例 (Kubernetes)
以下は、署名のないイメージを一切受け付けないためのマニフェストだ。
# policy.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
validationFailureAction: Enforce # 署名がなければPodの作成を拒否
rules:
- name: verify-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "my-registry.com/my-app:*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
(ここに署名検証用の公開鍵を配置)
-----END PUBLIC KEY-----
—
エンジニアへの警告:ツールは万能ではない
ここまで自動化しても、油断は禁物だ。以下の点は、現場のエンジニアが常に意識しておくべき「盲点」である。
1. 署名鍵の管理: もし秘密鍵をGitにコミットしたり、アクセス権が緩い環境変数に置いたりすれば、攻撃者は容易に「正規の署名」を行えるようになる。可能な限りキーレス署名を推奨する。
2. スキャンの鮮度: 脆弱性データベースは毎日更新される。一度スキャンを通過したイメージでも、1ヶ月後には致命的な脆弱性が発見される可能性がある。Runtime Security(Falcoなど)と組み合わせ、実行中の異常動作も監視すること。
3. SBOMの生成: Cosign はイメージだけでなく、SBOM(ソフトウェア部品表)にも署名できる。何が含まれているかを可視化する習慣をつけよう。
まとめ
セキュリティとは「一度設定して終わり」のタスクではない。インフラからアプリケーションまで、信頼の連鎖(Chain of Trust)を構築し続けるプロセスだ。
まずは今日のCI/CDに、脆弱性スキャンの exit-code: '1' を入れるところから始めてほしい。その「壊れるパイプライン」こそが、真に堅牢なシステムへの第一歩だ。現場のエンジニア諸君、安易な利便性に屈せず、泥臭く信頼を積み重ねよう。
コメント