【実務・中級編】コンテナイメージの脆弱性スキャンとCI/CDパイプラインへの統合 – アプリケーションセキュリティ & 安全な開発防御ガイド

現場のエンジニア諸君。今日も「動けばいい」という甘い誘惑と戦っていることだろう。

だが、厳しい現実を突きつけよう。君たちがDockerfileのベースイメージに何気なく指定したalpine:latestやnode:18。その中には、公開から数時間で悪用コードが出回る「既知の脆弱性」が山ほど眠っている。攻撃者は君たちがCI/CDのパイプラインを回している間に、その隙を突く準備を整えているんだ。

今日は、ただ「スキャンしました」で終わらせない、実務で使えるコンテナセキュリティの防壁構築について話そう。

—

1. 脆弱性スキャンを「お守り」にしないために

多くの現場では、TrivyやClairを導入して「満足」している。だが、重要なのはスキャン結果をどう扱うかだ。「スキャンした結果、重大な脆弱性があったらデプロイを止める」。このゲート設定こそが、我々が守るべき最後の砦だ。

攻撃者は、特定のCVE(共通脆弱性識別子)に対してPoC(概念実証コード)がGitHubに上がった瞬間に、そのライブラリを使っているサービスを自動スキャンで探し出す。君たちがパッチを当てて再デプロイするまでのわずか数時間が、彼らにとっては狩りの時間なんだ。

2. CI/CDパイプラインへの実装:Trivyによる「強制停止」

GitHub Actionsのワークフローで、脆弱性が検出されたらパイプラインを異常終了させる設定例を見せよう。これを導入するだけで、セキュリティ意識の低いコードの混入を物理的に防げる。

.github/workflows/security-scan.yml
name: Container Security 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:latest .

  • name: Run Trivy vulnerability scanner

uses: aquasecurity/trivy-action@master
with:
image-ref: ‘my-app:latest’
format: ‘table’
# CRITICALな脆弱性が見つかったら、exit-code 1を返してパイプラインを落とす
exit-code: ‘1’
ignore-unfixed: true
severity: ‘CRITICAL’ # MEDIUMやHIGHは運用状況に応じて調整せよ

このexit-code: '1'が重要だ。これがないスキャンは、ただの「警告通知」であり、攻撃者に対する何の防波堤にもならない。

3. なぜ「ベースイメージの選定」が全てなのか

コンテナの脆弱性は、アプリのコードよりも「OS層」から発生することが多い。特にlatestタグの使用は、セキュリティ管理における最大の怠慢だ。

対策:Distrolessイメージへの移行
Distrolessは、シェルやパッケージマネージャさえも削ぎ落とした最小限のイメージだ。万が一アプリが突破されても、攻撃者はOSのコマンド(curlやwget)を叩いて外部から悪意あるスクリプトをダウンロードすることすらできない。

ダメな例:OS機能が豊富すぎて攻撃者に踏み台にされやすい
FROM node:18-buster

良い例:Distrolessで攻撃の表面積を極限まで減らす
FROM gcr.io/distroless/nodejs18-debian11

COPY . /app
WORKDIR /app
CMD [“server.js”]

4. 現場で役立つ「脱出不可能な」セキュリティTips

最後に、インフラ設計者として君たちに一つだけアドバイスがある。どんなにスキャンを厳格にしても、アプリの脆弱性(SQLiやRCE)がゼロになることはない。だからこそ、「コンテナが乗っ取られた後の被害」を最小化することがCISSPの基本だ。

不必要な権限の剥奪(IAM/Kubernetes Security Context)

コンテナを常に root で動かしていないか? もし root で動かしていれば、コンテナ内の脆弱性が即座にホストOSの権限奪取に繋がる。

KubernetesのDeployment設定例
spec:
containers:

  • name: my-app

securityContext:
runAsNonRoot: true # root権限での実行を禁止
runAsUser: 1000 # 特定の一般ユーザーIDで実行
allowPrivilegeEscalation: false # 特権昇格を禁止

—

最後に:セキュリティは「泥臭い作業」の積み重ねだ

「最新のツールを入れれば安泰」などと考えるのは、素人の発想だ。セキュリティの本質は、開発パイプラインの全ての工程において「何が起きたら困るか」を想像し、自動化によってその「困ること」を物理的に実行不可能にすることにある。

今日伝えた exit-code: 1 の設定、そして Distroless への切り替え。これらをまずは君の現在のプロジェクトに適用してみてほしい。もしビルドが落ちるなら、それは君のシステムが今まで「無防備な状態」で公開されていたという何よりの証拠だ。

修正は痛みを伴うが、インシデント対応で深夜に叩き起こされるよりは、ずっとマシだと思わないか?

健闘を祈る。何かあればまた相談してくれ。

コメント

タイトルとURLをコピーしました