LoginSignup
2
2

More than 3 years have passed since last update.

【GithubActions】Dockerイメージの脆弱性を診断してECRへpushする

Posted at

概要

GithubActionsのワークフローで、Dockerイメージのセキュリティチェックを行う設定をメモとして残しておきます。
設定全容は以下となります。

ソースコード全容
ci.yaml
name: Check / Build / Push Docker Image

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  scan:
    name: Scan Image By Azure/container-scan
    runs-on: ubuntu-18.04
    steps:
      - name: Checkout code
        id: checkout
        uses: actions/checkout@v2

      - name: Build image from Dockerfile
        id: build
        run: |
          TAG=test:ci
          docker build -f ${YOUR_DOCKER_FILE} -t ${TAG} .
          echo "::set-output name=tag::${TAG}"

      - name: Run Vulnerability/Security Scanner
        id: run-scanner
        uses: Azure/container-scan@v0
        with:
          image-name: ${{ steps.build.outputs.tag }}
  build:
    name: Build And Push Docker Image
    runs-on: ubuntu-18.04
    needs: [scan]
    if: github.event_name == 'push'

    steps:
      - name: Checkout code
        id: checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        id: aws-cred
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${YOUR_REPOSITORY}
          DOCKER_FILE: ${YOUR_DOCKERFILE}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -f ${DOCKER_FILE} -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} .
          docker push ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}

      - name: Logout From Amazon ECR
        id: logout-ecr
        if: always()
        run: docker logout ${{ steps.login-ecr.outputs.registry }}

pushする前にチェックすること

ここでチェックしたい内容は、コンテナイメージの脆弱性およびセキュリティ診断です。
脆弱性の診断にはTrivyを、セキュリティ診断にはDockleをそれぞれツールとして利用します。
実際のところ、ユニットテストも通すでしょうが、本記事では省略します。

β版(Pre-release)で構わない

という場合は、Azure/container-scanを利用すると非常に簡単に設定することができます。
Azure/container-scanは内部的にTrivyとDockleを利用してコンテナイメージのチェックを行っており、今回チェックしたい内容と合致しています。
ただし、2020/08時点ではPre-release版のリリースタグが1つあるのみですので、実務でPre-release版の利用を禁止するなどのポリシーがある場合には利用することができません。その場合は後述の内容で設定します。

設定の一例

ci.yaml
jobs:
  scan:
    name: Scan Image By Azure/container-scan
    runs-on: ubuntu-18.04
    steps:
      # 1
      - name: Checkout code
        id: checkout
        uses: actions/checkout@v2
      # 2
      - name: Build image from Dockerfile
        id: build
        run: |
          TAG=test:ci
          docker build -f ${YOUR_DOCKER_FILE} -t ${TAG} .
          echo "::set-output name=tag::${TAG}"
      # 3
      - name: Run Vulnerability/Security Scanner
        id: run-scanner
        uses: Azure/container-scan@v0
        with:
          image-name: ${{ steps.build.outputs.tag }}

解説

  1. ソースコードのチェックアウトを実行
  2. Dockerfileを指定して、イメージのビルドを実行
    TAGの部分は適当な文字列で構いません。echo "::set-output"で、以降のstepでoutputsとして利用可能な変数の設定を行っています。(こちらのページjobs.<jobs_id>.outputsを参照)
  3. Azure/container-scanで脆弱性とセキュリティのチェックを行います。image-nameにステップ2のビルド時に設定したイメージタグを指定します。そのほかの引数は任意設定で、以下が指定可能。
変数 概要 デフォルト
severity-threshold 脆弱性として検知するレベルの設定。UNKNOWN, LOW, MEDIUM, HIGH, CRITICALの5つを設定でき、設定したレベルとそれより高いレベルの脆弱性を報告する。例えばMEDIUMを設定した場合、MEDIUM, HIGH, CRITICALが対象となる。 HIGH
run-quality-check イメージがベストプラクティス/CIS標準に従っているかチェックする。具体的な内容はこちら true

実行結果

コンソールにも結果は出ていますが、下図のような形で結果報告用のジョブが作成されており、一覧で表示されます。詳細リンク等は貼られないので、そこは自身で調べ直す必要があります。

スクリーンショット 2020-08-14 21.07.14.png

β版(Pre-release)は避けたい

TrivyおよびDockleによるチェックステップをそれぞれ定義します。
job単位で分けても良いですが、今回示す例は同一job内で別stepとして定義します。

設定の一例

ci.yaml
jobs:
  scan:
    name: Scan Image
    runs-on: ubuntu-18.04

    steps:
      # 1
      - name: Checkout code
        id: checkout
        uses: actions/checkout@v2
      # 2
      - name: Build image from Dockerfile
        id: build
        run: |
          TAG="${{ matrix.service }}:ci"
          docker build -f deployment/dockerfile/${{ matrix.service }}/Dockerfile -t ${TAG} .
          echo "::set-output name=tag::${TAG}"
      # 3-1
      - name: Install Trivy
        env:
          TRIVY_VERSION: 0.10.2
        run: |
          sudo apt-get install rpm
          wget https://github.com/aquasecurity/trivy/releases/download/v{TRIVY_VERSION}/trivy_{TRIVY_VERSION}_Linux-64bit.deb
          sudo dpkg -i trivy_{TRIVY_VERSION}_Linux-64bit.deb
      # 3-2
      - name: Run Vulnerability Scanner
        id: scan-vulnerability
        run: trivy image --severity "CRITICAL,HIGH" --exit-code 1 ${{ steps.build.outputs.tag }}
      # 4-1
      - name: Install Dockle
        env:
          DOCKLE_VERSION: 0.2.4
        run: |
          curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb
          sudo dpkg -i dockle.deb
          rm dockle.deb
      # 4-2
      - name: Run Security Scanner
        id: scan-security
        run: |
          dockle --exit-level warn --exit-code 1 ${{ steps.build.outputs.tag }}

解説

  1. ソースコードのチェックアウトを実行
  2. Dockerfileを指定して、イメージのビルドを実行
    TAGの部分は適当な文字列で構いません。echo "::set-output name=変数名::値"で、以降のstepでoutputsとして利用可能な変数の設定を行っています。(こちらのページjobs.<jobs_id>.outputsを参照)
  3. Trivyのインストールおよび実行
    3-1. Trivyをインストールします。READMEのバージョン指定版をコピペしていますが、download/のあとにvが必要でしたので付け足してあります。
    3-2. trivyコマンドを実行します。
    --severity "HIGH,CRITICAL"部分で、検知する脆弱性のレベルをHIGHとCIRITICALに限定し、
    --exit-code 1部分で、脆弱性を検知したときにCIが失敗となるように設定しています。
    その他のオプション詳細はこちらを参照ください。
  4. Dockleのインストールおよび実行
    4-1. Dockleをインストールします。READMEVERSIONをワークフロー定義ファイル内で指定する形としています。
    4-2. dockleコマンドを実行します。
    --exit-level warn --exit-code 1部分で、warnレベル以上の問題を検知したときCIが失敗となるように設定しています。

なお、TrivyについてはEXPERIMENTALですがactions用の設定も用意されています。(ソース)
こちらを利用した場合は、3を以下のように記載します。

ci.yaml
      # GoのInstallが必要らしい
      - name: Setup Go
        id: setup
        uses: actions/setup-go@v1
        with:
          go-version: 1.14
      # 実行
      - name: Run Vulnerability Scanner
        id: scan-vulnerability
        uses: aquasecurity/trivy-action@0.0.8
        with:
          image-ref: ${{ steps.build.outputs.tag }}
          format: table
          exit-code: 1
          severity: "CRITICAL,HIGH"

ECRへpush

GithubActionsではneedsを利用することでジョブ間の依存関係を定義することが可能です。
これを利用して、脆弱性診断で問題がなければイメージをプッシュするというワークフローを作成できます。

あらかじめ、対象リポジトリのシークレットにAWSへの認証情報を登録しておきます。

  • AWS_ACCESS_KEY_ID:AWSのアクセスキーID
  • AWS_SECRET_ACCESS_KEY:AWSのシークレットアクセスキー

設定の一例

ci.yaml
jobs:
  build:
    name: Build And Push Docker Image
    runs-on: ubuntu-18.04
    # 1
    needs: [scan]
    if: github.event_name == 'push'

    steps:
      # 2
      - name: Checkout code
        id: checkout
        uses: actions/checkout@v2
      # 3
      - name: Configure AWS credentials
        id: aws-cred
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
      # 4
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
      # 5
      - name: Build and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${YOUR_REPOSITORY}
          DOCKER_FILE: ${YOUR_DOCKERFILE}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -f ${DOCKER_FILE} -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} .
          docker push ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}
      # 6
      - name: Logout From Amazon ECR
        id: logout-ecr
        if: always()
        run: docker logout ${{ steps.login-ecr.outputs.registry }}

解説

  1. needsでジョブ間の依存性を指定
    今回はscanジョブ、すなわち脆弱性チェックのジョブが正常に完了している場合に限り、本ジョブが稼働するような設定です。
    また、ifを用いることでpushイベントのときだけこのジョブが動くように制限しています。これは好き好きですが、個人的には脆弱性チェックはプルリクエストで実行し、イメージプッシュはdevelopmasterブランチへのpushをトリガーにするイメージがあるためです。ifで分けるのではなく、ファイルごと分けてしまうというのも一案かと思います。
  2. ソースコードのチェックアウトを実行
  3. AWSへのログインを行います。SECRETに登録しているキーはここで利用します。
  4. ECRへのログインを行います。このstepが必要な理由は、pushするstepにて対象リポジトリを指定する必要があるためです。amazon-ecr-loginでは、outputsregistryに設定されるためこれを利用していますが、対象のレジストリを取得できればこのactionを指定する必要性はありません。
  5. イメージのbuildとpushを行います。ステップ3、4にて認証処理は済んでいるため、ここではbuildpushを行うのみとなります。
    envで環境変数にリポジトリやイメージタグの値を設定していますが、単に見やすいかなと思って設定しただけで、環境変数にする必然性はありません。run内のコマンド部分で直接指定で問題ないです。
  6. ECRからログアウトします。if: always()を指定することでstep4が失敗した場合にも実行されるような設定となっています。

Tips

特定のDocker Image Checkpointsを無視したい

例えば、CIS-DI-0006(Healthチェックを設定してね)というチェック内容がありますが、ヘルスチェックはk8sに任せるからDockerfileでは書かなくてよい、ということでこのチェック内容は無視したいというケースが発生したとします。
その場合は、リポジトリに.dockleignoreというファイルを作成し内容にCheckpointsのIDを記載することでチェック対象から意図的に外すことができます。

.dockleignore
# k8sで任せるので無視
CIS-DI-0006

イメージタグにブランチ名またはタグ名を指定したい

上記例ではワークフローをトリガーしたコミットSHAを指定していましたが、タグ名としてブランチ名またはタグ名を指定したいことはあると思います。
GithubActionsではワークフロー実行元となったブランチやタグの名称をうまく取る手段は現状ないようですが、GITHUB_REFgithub.ref)をうまく加工することで取得することができます。

ci.yaml
- name: Get Version
  id: version
  run: |
    echo ::set-output name=source_branch::${GITHUB_REF#refs/heads/} # ブランチの場合
    echo ::set-output name=source_tag::${GITHUB_REF#refs/tags/}     # タグの場合

References

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2