はじめに

AWS使ったシステムのコンテナの脆弱性スキャンに、Inspectorを採用しているケースは多いと思います。
ただ、Inspectorには以下のような課題がありました。

既知の脆弱性が含まれているコンテナであっても、ECRにPushしてみるまでは、検出できないんだよな・・・

私の経験上にはなるんですが、開発者の方々は開発で手一杯で、開発中のコンテナイメージに脆弱性が含まれているかまでは、あまり意識しておらず、Push後に脆弱性が検出されても、その修正までは直ぐには手が回らないケースが多いです。
その為、リリース前のセキュリティ監査で大量の脆弱性を指摘され、対応に追われる・・・は、よくあるパターンな気がします。
世に出てなかった新規の脆弱性は除き、既知の脆弱性は常に潰しながら開発を進めることが、ベストプラクティスだと私は考えています。

これを実現する方法として、Snykなどのサードパーティ製の脆弱性スキャンツールをCI/CDパイプラインを組む方法があるのですが、サードパーティ製のツールの採用は地味にハードルが高く、実際に取り入れることができていませんでした。
そんな中、2024年にInspectorとCodePipelineとの統合の発表、GitHubアクションとの統合の発表がありました。

今回は、後者のGitHubアクションとの統合を使って、コンテナイメージの脆弱性スキャンをCI/CD上で行う方法を試してみたいと思います。

試してみた

GitHubアクションとの統合ですが、具体的にはAWS公式から提供されているactionであるvulnerability-scan-github-action-for-amazon-inspectorを、Workflowに組み込む形になります。
このactionには、スキャンタイプとしてレポジトリ、コンテナ、バイナリー、アーカイブの4つがあります。
今回はコンテナイメージの脆弱性スキャンなので、コンテナタイプのスキャンを試します。

レポジトリタイプのスキャンの場合、コンテナイメージのビルドが不要なので、高速な脆弱性スキャンが可能ですが、レポジトリ内のコードを静的スキャンする形になるので、ベースコンテナイメージに含まれる脆弱性の検出ができません。
その為、コンテナイメージの脆弱性スキャンが目的なのであれば、スキャンタイプにはコンテナを選択することをオススメします。
レポジトリタイプのスキャンの場合、Dockerfileの解析も可能なので、コンテナタイプのスキャンの前段に、レポジトリタイプのスキャンを挟むのもありです。
この場合、静的スキャンで検出できる範囲の脆弱性であれば、コンテナイメージのビルド前に、CIを失敗させることができるので、無駄なコンテナイメージのビルドが防げます。

nginxのコンテナイメージのビルドが成功するか否かをテストしている以下のWorkflow(CI)にコンテナタイプのスキャンを組み込んで行きます。

name: CI

on:
  pull_request:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: write
  pull-requests: write

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Generate docker metadata
        uses: docker/metadata-action@v5.7.0
        id: meta
        with:
          images: nginx
          tags: type=sha,prefix=,format=short

      - name: Set up docker buildx
        uses: docker/setup-buildx-action@v3

      - name: Test build
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          platforms: linux/amd64
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Create comment file for build result
        run: |
          if [[ "${{ steps.build.outcome }}" == "success" ]]; then
            echo "### :white_check_mark: Build - Passed" > build_comment.md
          else
            echo "### :x: Build - Failed" > build_comment.md
          fi

      - name: Create pr comment for build result
        run: gh pr comment ${{ github.event.number }} --body-file build_comment.md
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Fail workflow if build failed
        if: steps.build.outcome != 'success'
        run: exit 1

OIDC用ロールの作成

公式ドキュメントにも記載されてる通り、GitHubアクションとの統合には、GitHubアクションにinspector-scan:ScanSbom権限を与える必要があります。
この権限をOIDC用のロールに付与します(OIDC用ロールの作成方法は本質からズレるので、割愛します)。

Workflowの設定

先程のWorkflow(CI)を以下のように書き換えます。

  • permissions: OIDC認証でid-tokenのwrite権限が必要
  • Test build: loadがfalseだと、Inspectorのスキャンが失敗(コンテナイメージが見つからず)するので、trueにする
  • Configure AWS credentials: 先程作成したOIDC用のロールを指定
  • Scan built image with Inspector: READMEに従って各パラメータを設定
  • Create pr comment for inspector scan result: スキャン結果(マークダウン)をPRにコメント
  • Fail job if vulnerability threshold is exceeded: 検知した脆弱性が指定した閾値を超えてる場合、Workflow(CI)を失敗させる

PRの作成

脆弱性が含まれているnginxをベースイメージに指定した以下Dockerfileを作業ブラウザにPushして、PRを作成する

FROM nginx:1.29

脆弱性の検知

PRが作成されると、先程設定したWorkflowが発火し、PRにスキャン結果がコメントされ、Workflow(CI)が失敗することが確認できます。

さいごに

既存のGithubアクションのWorkflowに少し手を加えるだけで、Inspectorを使ったコンテナイメージの脆弱性スキャンをCI/CD上で行うことが出来ました。
新規の脆弱性は引き続き、InspectorのECR継続スキャンで検知し、既知の脆弱性は今回紹介した方法などで、デプロイ前に潰しておくのが、ベストになりそうですね!