概要
GithubActionsのワークフローで、Dockerイメージのセキュリティチェックを行う設定をメモとして残しておきます。
設定全容は以下となります。
ソースコード全容
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版の利用を禁止するなどのポリシーがある場合には利用することができません。その場合は後述の内容で設定します。
設定の一例
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 }}
解説
- ソースコードのチェックアウトを実行
- Dockerfileを指定して、イメージのビルドを実行
TAGの部分は適当な文字列で構いません。echo "::set-output"
で、以降のstepでoutputs
として利用可能な変数の設定を行っています。(こちらのページのjobs.<jobs_id>.outputs
を参照) -
Azure/container-scan
で脆弱性とセキュリティのチェックを行います。image-name
にステップ2のビルド時に設定したイメージタグを指定します。そのほかの引数は任意設定で、以下が指定可能。
変数 | 概要 | デフォルト |
---|---|---|
severity-threshold | 脆弱性として検知するレベルの設定。UNKNOWN, LOW, MEDIUM, HIGH, CRITICALの5つを設定でき、設定したレベルとそれより高いレベルの脆弱性を報告する。例えばMEDIUM を設定した場合、MEDIUM, HIGH, CRITICALが対象となる。 |
HIGH |
run-quality-check | イメージがベストプラクティス/CIS標準に従っているかチェックする。具体的な内容はこちら | true |
実行結果
コンソールにも結果は出ていますが、下図のような形で結果報告用のジョブが作成されており、一覧で表示されます。詳細リンク等は貼られないので、そこは自身で調べ直す必要があります。
β版(Pre-release)は避けたい
TrivyおよびDockleによるチェックステップをそれぞれ定義します。
job
単位で分けても良いですが、今回示す例は同一job
内で別step
として定義します。
設定の一例
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 }}
解説
- ソースコードのチェックアウトを実行
- Dockerfileを指定して、イメージのビルドを実行
TAGの部分は適当な文字列で構いません。echo "::set-output name=変数名::値"
で、以降のstepでoutputs
として利用可能な変数の設定を行っています。(こちらのページのjobs.<jobs_id>.outputs
を参照) -
Trivy
のインストールおよび実行
3-1.Trivy
をインストールします。READMEのバージョン指定版をコピペしていますが、download/
のあとにv
が必要でしたので付け足してあります。
3-2. trivyコマンドを実行します。--severity "HIGH,CRITICAL"
部分で、検知する脆弱性のレベルをHIGHとCIRITICALに限定し、--exit-code 1
部分で、脆弱性を検知したときにCIが失敗となるように設定しています。
その他のオプション詳細はこちらを参照ください。 -
Dockle
のインストールおよび実行
4-1.Dockle
をインストールします。READMEのVERSION
をワークフロー定義ファイル内で指定する形としています。
4-2. dockleコマンドを実行します。--exit-level warn --exit-code 1
部分で、warn
レベル以上の問題を検知したときCIが失敗となるように設定しています。
なお、Trivy
についてはEXPERIMENTALですがactions用の設定も用意されています。(ソース)
こちらを利用した場合は、3を以下のように記載します。
# 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のシークレットアクセスキー
設定の一例
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 }}
解説
-
needs
でジョブ間の依存性を指定
今回はscan
ジョブ、すなわち脆弱性チェックのジョブが正常に完了している場合に限り、本ジョブが稼働するような設定です。
また、if
を用いることでpush
イベントのときだけこのジョブが動くように制限しています。これは好き好きですが、個人的には脆弱性チェックはプルリクエストで実行し、イメージプッシュはdevelop
やmaster
ブランチへのpushをトリガーにするイメージがあるためです。if
で分けるのではなく、ファイルごと分けてしまうというのも一案かと思います。 - ソースコードのチェックアウトを実行
- AWSへのログインを行います。
SECRET
に登録しているキーはここで利用します。 - ECRへのログインを行います。この
step
が必要な理由は、push
するstep
にて対象リポジトリを指定する必要があるためです。amazon-ecr-login
では、outputs
のregistry
に設定されるためこれを利用していますが、対象のレジストリを取得できればこのactionを指定する必要性はありません。 - イメージのbuildとpushを行います。ステップ3、4にて認証処理は済んでいるため、ここでは
build
→push
を行うのみとなります。env
で環境変数にリポジトリやイメージタグの値を設定していますが、単に見やすいかなと思って設定しただけで、環境変数にする必然性はありません。run
内のコマンド部分で直接指定で問題ないです。 - ECRからログアウトします。
if: always()
を指定することでstep4が失敗した場合にも実行されるような設定となっています。
Tips
特定のDocker Image Checkpoints
を無視したい
例えば、CIS-DI-0006
(Healthチェックを設定してね)というチェック内容がありますが、ヘルスチェックはk8sに任せるからDockerfileでは書かなくてよい、ということでこのチェック内容は無視したいというケースが発生したとします。
その場合は、リポジトリに.dockleignore
というファイルを作成し内容にCheckpointsのIDを記載することでチェック対象から意図的に外すことができます。
# k8sで任せるので無視
CIS-DI-0006
イメージタグにブランチ名またはタグ名を指定したい
上記例ではワークフローをトリガーしたコミットSHAを指定していましたが、タグ名としてブランチ名またはタグ名を指定したいことはあると思います。
GithubActions
ではワークフロー実行元となったブランチやタグの名称をうまく取る手段は現状ないようですが、GITHUB_REF
(github.ref
)をうまく加工することで取得することができます。
- 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/} # タグの場合