Skip to main content

Dockerイメージスキャン用GitHub Actionの設定

feature docker secrets

2023年5月19日

0 分で読めます

現在、多くのGitリポジトリでは、最終成果物としてDockerイメージを作成し、それをKubernetesのデプロイに使用します。セキュリティが大きな注目を集めている今(それには十分な理由があります)、CIで作成するDockerイメージをスキャンすることは不可欠です。

この記事では、GitHub Actionsを使ってDockerイメージをビルドし、セキュリティ上の脆弱性をスキャンする方法を紹介します。CIでビルドしたDockerイメージは、GitHubのDockerレジストリにもプッシュします。

「GITHUBを使ったCIでのDockerイメージスキャン」という文字の下にある、ラテアート入りのコーヒーカップ

GitHub Actionsのワークフローを作成する

これから作成するCIワークフローは、次のような構成です。

  • テスト

  • イメージをビルド

  • イメージをスキャン

2つ目のステップでビルドしたイメージをGitHub Dockerレジストリにプッシュし、3つ目のステージで再度そこからプルします。

テストジョブを作成する

GitHubリポジトリにCI設定がない場合、`.github/workflows/ci.yaml`に次の内容のファイルを作成します。

    name: ci
    on:
      push:
        branches:
          - master
      pull_request:

    jobs:
      test:
        runs-on: ubuntu-latest

        strategy:
          matrix:
            node-version: [ 18.x ]

        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v3

          - name: Setup Node environment 🧱: Node.js ${{ matrix.node-version }}
            uses: actions/setup-node@v3
            with:
              node-version: ${{ matrix.node-version }}

          - name: Install and test 🪲
            run: |
              npm ci
              npm test

`on`属性では、このワークフローを実行するイベントを指定します。この例では、`master`ブランチへのプッシュと、すべてのプルリクエストによってCIワークフローが実行されます。

ジョブのステップは次のとおりです。

  • Checkout 🔔: リポジトリからコードを取得します

  • Setup Node environment 🧱: Node.js 18環境をインストールします

  • Install and test 🪲: 依存関係をインストールし、テストを実行します

ここでのテストジョブは、Node.jsプロジェクトの単体テストを実行する例にすぎません。プロジェクトの言語や構成に合わせて自由に変更してください。

Dockerイメージのビルドにテストジョブは必須ではありませんが、適切な方法なので、この例では含めています。

Dockerビルドジョブを作成する

Dockerビルドジョブを作成するには、まずファイルの先頭にある`on`の下に、2つの環境変数を作成します。

  on:
      ...

    env:
      DOCKER_IMAGE_TAG: ${{ github.ref == 'refs/heads/master' && 'prod-' || 'dev-' }}${{ github.sha }}
      GITHUB_REGISTRY: ghcr.io
      GITHUB_REPOSITORY: ${{ github.repository }}

次に、`build_docker job`を作成しましょう。

    jobs:
      test:
        ...

      build_image:
        permissions:
          id-token: write
          contents: read
          packages: write
        runs-on: ubuntu-latest
        needs: [ test ]

        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v2

          - name: Log in to the Container registry 📦
            uses: docker/login-action@v2
            with:
              registry: ${{ env.GITHUB_REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}

          - name: Build and push Docker image 🐳
            uses: docker/build-push-action@v3
            with:
              push: true
              tags: |
                ${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }}

コードを見ていきましょう。DockerイメージをGitHubのレジストリにプッシュするため、permissionsセクションが必要です。

このジョブは最新のUbuntuバージョンで実行され、テストジョブの完了を必要とします。これにより、正常に動作するコードのDockerイメージだけを作成できます。テストジョブを省略した場合は、needsの行を削除してください。

次はステップです。

  • Checkout 🔔: リポジトリからコードを取得します。Dockerfileとそのコンテキストを取得するために必要です。

  • Containerレジストリにログイン 📦

  • Dockerイメージをビルドしてプッシュ 🐳: このステップでは、ファイルの先頭で定義した環境変数を使って、Dockerイメージのタグを指定します。

スキャンジョブを作成する

次に、Dockerイメージのスキャンジョブを追加しましょう。

    jobs:
      test:
        ...

      build_image:
        ...

      scan_docker_image:
        permissions:
          id-token: read
          contents: read
          packages: read
        runs-on: ubuntu-latest
        needs: [ build_image ]
        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v2

          - name: Log in to the Container registry 📦
            uses: docker/login-action@v2
            with:
              registry: ${{ env.GITHUB_REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}

          - name: Scan Docker image 🐳
            uses: snyk/actions/docker@master
            continue-on-error: true
            with:
              image: ${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }}
              args: --file=Dockerfile --severity-threshold=high --sarif-file-output=snyk.sarif
            env:
              SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

          - name: Upload Snyk report as sarif 📦
            uses: github/codeql-action/upload-sarif@v2
            with:
              sarif_file: snyk.sarif

このステップでは、`docker scan`の基盤となるセキュリティスキャンエンジン、Snykを使用します。利用するには無料アカウントを作成し、そのトークンをシークレットに保存する必要があります。

  • こちらから登録

  • 説明に従ってこちらからトークンを取得

  • 付録の説明に従い、`SNYK_TOKEN`という名前でGitHubリポジトリのCIシークレットに追加します

それでは、各ステップを見ていきましょう。

  • Checkout 🛎️: Dockerfileにもアクセスできると、スキャナーの性能が向上します。

  • Containerレジストリにログイン 📦: 先ほどプッシュしたDockerイメージを取得するためです。

  • Dockerイメージをスキャン 🐳: このジョブでDockerイメージをスキャンし、脆弱性を`snyk.sarif`というファイルに出力します。このファイル形式はGitHubに認識され、PRに表示できます。そのため、次のステップを追加します。

  • Snykのレポートをsarif形式でアップロード 📦: 前のステップで生成した`sarif`ファイルをGitHubにアップロードします

GitHubにアップロードされた脆弱性は、次のようにPRに表示されます。

Dockerfile上のコードスキャン注釈。重大度の高いcurlの脆弱性とチェックの失敗を表示。

まとめ


この記事では、テストの実行、DockerイメージのビルドとGitHubレジストリへのプッシュ、セキュリティ上の問題のチェック、そして脆弱性レポートのアップロードを行う3つのジョブからなるGitHub Actionsワークフローを作成しました。これにより、GitHubが脆弱性レポートを認識し、PRに表示できるようになります。

付録: GitHubでCIシークレットを追加する

リポジトリで次の操作を行います。

  1. Settingsタブをクリックします。

  2. 左側のメニューパネルで、SecurityセクションにあるSecrets and variablesをクリックします。

  3. 新しく表示されたメニュー項目から、Actionsをクリックします。

  4. 右上にある緑色のNew repository secretボタンをクリックします。

  5. Name欄に、`SNYK_TOKEN`などの任意の名前を入力します。

  6. Secret欄にシークレットを貼り付けます。たとえば、SnykのWebサイトからコピーしたSnykトークンを貼り付けます。

  7. 最後に、緑色のAdd secretボタンをクリックします。

ACCESS_TOKENとSNYK_TOKENのリポジトリシークレットを含む、Actionsのシークレットと変数を表示したGitHubリポジトリの設定ページ。