現場のエンジニア諸君、日々お疲れ様。
「脆弱性スキャンをCI/CDに入れたからうちは安全だ」と胸を張っているチームほど、実は最も危険な落とし穴にハマっている。今日は、単なる「スキャンツールの導入」という儀式で終わらせず、攻撃者がどこを見て、我々はどう防壁を築くべきか、その本質を話そう。
1. なぜ「スキャン」だけでは突破されるのか
まず現実を突きつけよう。TrivyやClairといったツールでイメージをスキャンするのは「健康診断」に過ぎない。攻撃者は、スキャンをすり抜けるために以下の手法を好んで使う。
- マルチステージビルドの悪用: ビルドステージにのみ脆弱なライブラリを残し、最終イメージには残さない(あるいはその逆)といった工作。
- ゼロデイのタイムラグ: スキャンツールの脆弱性データベース(CVE)に登録される前の、公開されたばかりの「公開済みエクスプロイトコード」を利用した攻撃。
攻撃者は、CI/CDパイプラインが「低リスク」と判断した瞬間、あるいは「まだパッチが出ていない」と分かっている既知の脆弱性(CVE-2023-XXXX等)を突き、本番環境のコンテナからホストOSへのエスケープを試みる。これを防ぐには、スキャン結果に基づいた「自動化された強制排除(Gatekeeping)」が不可欠だ。
2. CI/CDパイプライン:Trivyによる「防御の門番」設定
単にスキャン結果をログに出すだけでは意味がない。高リスク(Critical)な脆弱性が見つかったら、即座にパイプラインを「Fail」させ、デプロイを物理的に止める必要がある。
以下は、GitHub Actionsを用いた、現場でそのまま使えるセキュアなパイプライン定義だ。
# .github/workflows/security-scan.yaml
name: Build and Secure Scan
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# 1. コンテナイメージのビルド
- name: Build Image
run: docker build -t my-app:${{ github.sha }} .
# 2. Trivyによる脆弱性スキャン(CriticalのみでFailさせる設定)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
format: 'table'
exit-code: '1' # 脆弱性が見つかったらExit 1を返し、パイプラインを停止させる
ignore-unfixed: true # パッチ未提供のものは無視(運用負荷軽減のため)
severity: 'CRITICAL' # CRITICALのみを対象に厳格チェック
# 3. 成功した場合のみPush(ここで初めて配布可能な状態にする)
- name: Push to Registry
if: success()
run: docker push my-app:${{ github.sha }}
ポイント: exit-code: '1' が命綱だ。これがなければ、警告が出るだけでイメージはレジストリにプッシュされてしまう。現場で最も多いミスは、この「設定忘れ」による素通りだ。
3. なぜ暗号理論を知る必要があるのか?
君たちが扱うコンテナイメージは、ただのファイル群ではない。これらは、署名(Signature)による正当性の証明が必要だ。
RSAや楕円曲線暗号(ECC)がなぜ重要か? それは、「誰が作ったイメージか(真実性)」と「改ざんされていないか(完全性)」を保証するためだ。攻撃者がコンテナレジストリに侵入し、イメージをマルウェア入りのものに差し替えた場合、脆弱性スキャンは無力になる。
Cosign(Sigstore)を使って、イメージにデジタル署名を施すことを強く推奨する。
# イメージに署名する(秘密鍵はKMSで管理すること)
cosign sign --key cosign.key my-app:${{ github.sha }}
# デプロイ時に署名を検証する(署名がなければ起動させない)
cosign verify --key cosign.pub my-app:${{ github.sha }}
4. 現場で守るべき「3つの鉄則」
最後に、私が現場で徹底させている「防御の心得」を伝授する。
1. 最小特権の原則: コンテナは決して root ユーザーで実行してはならない。Dockerfileには必ず USER 1000 を追加せよ。万が一の脆弱性突入時、被害範囲を劇的に限定できる。
2. ベースイメージの固定: FROM python:latest と書くな。FROM python:3.11-slim@sha256:xxxx... とダイジェスト値を指定しろ。タグは書き換え可能だが、ハッシュ値は書き換えられない。
3. 定期的な再スキャン: ビルド時だけでなく、稼働中のコンテナに対しても24時間に一度はスキャンを走らせろ。ビルド時は「安全」でも、稼働中に新たなCVEが発見されることは日常茶飯事だ。
セキュリティとは、ツールを導入して終わりという「点」の作業ではない。デプロイのプロセス全体を「線」で繋ぎ、異常を許さない強固なルールを敷くことだ。
君たちの書くコードと構築するインフラが、強固な防御壁であることを期待している。何かあればまた相談してくれ。手を動かすエンジニアこそが、最強の守護者だ。
コメント