エンジニア諸君、日々デプロイのプレッシャーと戦っていることだろう。
「脆弱性スキャン? CI/CDでTrivy回してるから大丈夫だよ」
もし君がそう思っているなら、今すぐその甘い認識を捨ててほしい。脆弱性スキャンを「導入すること」と「運用で守り切ること」の間には、埋めがたい深い溝がある。今日は、なぜ多くの現場が“形だけのセキュリティ”に陥り、いとも簡単に攻撃者の踏み台にされてしまうのか、そして、それを実務レベルでどう防ぐかについて、現場の泥臭い知見を交えて解説する。
なぜ「スキャンを通す」だけでは不十分なのか
攻撃者は、CI/CDパイプラインが「とりあえず脆弱性リストを出力して、警告だけで終わらせている」ことを知っている。彼らが狙うのは、パッチが公開されてから3日以内の「ゼロデイに近い既知の脆弱性」だ。
例えば、Webアプリのコンテナ内で古いバージョンの `libssl` や `glibc` が使われていれば、アプリケーション側の脆弱性がなくとも、コンテナエスケープやリモートコード実行(RCE)の足がかりにされる。Trivyのような優秀なツールを使っても、「警告が出てもビルドを止めない設定」にしていれば、それはセキュリティ対策ではなく、単なるログのゴミ箱だ。
実践:CI/CDで「脆弱なイメージ」を確実に遮断する
GitHub ActionsやGitLab CIにおいて、脆弱性スキャンの結果が「High」以上のレベルであれば、即座にパイプラインを強制終了させる必要がある。これは機械的にやるべきだ。
以下は、Trivyを用いたGitHub Actionsのワークフロー例だ。
.github/workflows/security-scan.yml
name: Build and Secure Scan
on: [push]
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build image
run: docker build -t my-app:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ‘my-app:${{ github.sha }}’
format: ‘table’
exit-code: ‘1’ # ここが重要!脆弱性が見つかれば終了コード1を返し、パイプラインを止める
ignore-unfixed: true # パッチが未提供のものは除外(現場の運用負荷を考慮)
severity: ‘CRITICAL,HIGH’ # 重要度の高いものだけをブロック対象にする
この設定のポイントは `exit-code: ‘1’` だ。これがないと、どれだけ深刻な脆弱性が見つかってもパイプラインは成功してしまう。「ビルドが通ること」を正義とするのではなく、「安全であること」を正義とするルールをチームに徹底させろ。
攻撃者の盲点:ベースイメージの選定
多くのエンジニアが `FROM node:latest` や `FROM python:3.9` といった雑な指定をしている。これが最大の弱点だ。`latest` タグは、いつ脆弱性を含んだイメージにすり替わるか分からない時限爆弾だ。
堅牢なDockerfileの作法(Pythonの例)
変更頻度が低く、セキュリティパッチが頻繁に当たる slim バージョンを推奨
FROM python:3.11-slim-bookworm
root権限でアプリを動かすのは御法度。攻撃時にホストOSまで権限が引き継がれる
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY requirements.txt .
不要なパッケージをインストールしない(攻撃面を最小化)
RUN apt-get update && apt-get install -y –no-install-recommends \
build-essential \
&& pip install –no-cache-dir -r requirements.txt \
&& apt-get purge -y –auto-remove build-essential
USER appuser
COPY . .
CMD [“python”, “app.py”]
現場で差がつく「インシデント防衛」の極意
最後に、一つだけ覚えて帰ってほしい。脆弱性スキャンは「過去のデータ」との照合だ。つまり、未知の脆弱性(ゼロデイ)に対しては無力だ。
だからこそ、インフラ側でのガードレールが必須となる。コンテナが万が一乗っ取られた際、外部との通信を制御する「Egressフィルタリング」を必ず実装しろ。
Nginx/WAFの設定の勘所(通信制御)
もしコンテナから外部へ通信する必要がないのなら、`iptables` やクラウド側のセキュリティグループで、コンテナからのアウトバウンド通信を「Deny All」に設定せよ。
Nginxで特定パスへのアクセスを制限する(WAFの代替的な実装)
location /admin {
# 信頼できるVPNのIPからのみ許可する(境界型防御の鉄則)
allow 192.168.1.0/24;
deny all;
}
最後に:セキュリティは「諦め」と「改善」の繰り返し
セキュリティは完成しない。今日完璧な設定でも、明日には新しいCVEが公表される。だが、CI/CDで脆弱性をブロックする仕組みを「当たり前」に組み込んでおけば、君たちはインシデントの火消しに追われるのではなく、新しい価値を創出する開発に集中できる。
「面倒だな」と思うその一手間が、数ヶ月後の君たちを救う。今日から、パイプラインの `exit-code` を確認してくれ。それが、プロのエンジニアの第一歩だ。

コメント