🐷

Google Cloud Build で Trivy を使用してコンテナイメージを脆弱性スキャンする

2022/11/25に公開

こんにちは、クラウドエースのシステム開発部/SREディビジョン所属の菊池と申します。

皆さん、コンテナを使っていますか?
コンテナを使うことで非常に効率よくアプリケーションの実装が可能になりました。
しかし、その一方で気になるのが、セキュリティです。
コンテナを利用する際は、DockerHub などからコンテナイメージを Pull してきて使うことが多いかと思いますが、
コンテナイメージ内には不正なプログラムや脆弱性が含まれる問題があります。
また、同じコンテナイメージを使い続けていても、日々新たな脆弱性の報告・修正がされ続けているので、
その都度対応するのでは、きりがありません。
そこで近年コンテナイメージの脆弱性スキャンツールが注目されつつあります。
本記事では Cloud Build と Trivy を使って、
コンテナイメージの脆弱性の有無でアプリケーションをデプロイする・しないとしたやり方を説明します。

Trivy とは

Trivy は包括的なセキュリティスキャナーです。
コンテイメージやファイルシステム・Git リポジトリ(リモート)・Kubernetes クラスターまたはリソース をスキャンし、
既知の脆弱性や IaC の設定ミス・使用中の OS パッケージとソフトウェアの依存関係などを知らせてくれます。
また、Trivy は CI で使用するように設計されており、コンテナレジストリにプッシュしたり、
アプリケーションをデプロイする前にコンテナイメージやその他のアーティファクトを簡単にスキャンできます。
詳しくは Trivy Document をご参照下さい

以下で紹介されているように、もともと Trivy は日本人の 福田 鉄平 氏が OSS として個人で開発を進めていましたが、
Aqua Security に売却し、福田氏も Aqua Security 社の社員として開発を継続しているみたいです。
OSS活動を通して掴んだ海外キャリア。英語力よりも技術力を大切にチャンスを掴む

Install

Trivy の CLI Installation から OS によって以下のインストールの仕方があります。
私は mac を使用しているので以下のコマンドでインストールできました。

brew install aquasecurity/trivy/trivy

正しくインストールできたか以下のコマンドで確認します、

trivy --version

trivy-version.png

実行結果

試しに適当なコンテナイメージを脆弱性スキャンしました。
Trivy での脆弱性スキャンに使う DB がダウンロードされてから、診断結果が返ってくるようです。
よく使われそうなコンテナでも脆弱性が多いことが確認できました。
image01.png

VSCode での実行

Trivy は VSCode の拡張機能があり、ローカル開発環境からでも簡単にスキャンできます。
Trivy Vulnerability Scanner をみると コンテナイメージの脆弱性スキャン以外にもアプリケーションの依存ライブラリ脆弱性スキャン, Terraform/Kubernetes の設定ファイルに対するセキュリティ不備なども検出できることが確認できます。
試しに簡易な Python アプリケーションの Dockerfile をスキャンしてみます。
サンプルコードには Cloud Run に Python サービスをデプロイするのコードを用いています。

[Extensions] から "Trivy" を検索

image03.png

"Trivy Vulnerability Scanner" がインストールされると、サイドバーにアイコンが追加されます。
[Run Trivy Now] を押下してDockerfileをスキャンしてみます。

image04.png

「FINDINGS EXPLORER」に指摘項目の一覧が表示されます。
指摘内容の詳細は「FINDINGS HELP」から確認できます。
“Image user should not be ‘root’” が検知され “Severity” は “High” でした。
“Resolution” や "More Information" が表示されるのは良いですね!

image05.png
[FINDINGS HELP] 下の [More Information] の
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
に従い、Doclerfile を修正し、再度 [Run Trivy Now] を押下すると
[FINDINGS EXPLORER] には何も表示されませんでした。
先ほど指摘された “Image user should not be ‘root’” が解決できたみたいです。

image06.png

Cloud Build を用いた CI/CD に Trivy を組み込む

cloudbuild.yaml を用いて コンテナのビルドとプッシュ、Cloud Run へのデプロイをする CI/CD を構築します。
CI/CD へ Trivy を組み込むことで、コンテナイメージの脆弱性スキャンをします。
そして、もしコンテナイメージに Severity が CRITIVAL の脆弱性があった場合は、Cloud Runへデプロイしない、
なかった場合は Cloud Run へのデプロイをする、ということをしていきます。

今回使用するファイルは以下の通りです。

.dockerignore
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache
main.py
import os

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    name = os.environ.get("NAME", "World")
    return "Hello {}!".format(name)

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
requirements.txt
Flask==2.1.0
gunicorn==20.1.0
Dockerfile
FROM python:3.10-slim

# Update Library
RUN apt-get update && apt-get upgrade -y

# Allow statements and log messages to immediately appear in the Knative logs
ENV PYTHONUNBUFFERED True

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install --no-cache-dir -r requirements.txt

# Add user and group, Select user
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
RUN mkdir -p /home/appuser && chown -R appuser /home/appuser
USER appuser

# Run the web service on container startup. Here we use the gunicorn
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
cloudbuild.yaml
steps:
  # Build the container image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}', '.']

  # Push the container image to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}']

  # Trivy
  - name: 'aquasec/trivy'
    entrypoint: '/bin/sh'
    args:
      - '-c'
      - | 
        echo "Starting checking vulnerabilities..."
        trivy image --exit-code 1 --severity CRITICAL --quiet ${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}:latest  

  # Deploy container image to Cloud Run  
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
    - 'run'
    - 'deploy'
    - 'helloworld-py'
    - '--image'
    - '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}'
    - '--region'
    - 'asia-northeast1'
substitutions:
  _LOCATION: LOCATION
  _REPOSITORY: REPOSITORY
  _IMAGE: IMAGE

cloudbuild.yaml 内で
trivy image --exit-code 1 --severity CRITICAL とオプションをつけることで、
Severity が CRITICAL な脆弱性を検知した際に、CI を止めることができます。
詳しくは Trivy cli をご参照ください。

また現時点でのディレクトリ構成は以下のようになっています。

$ tree -a
.
├──.dockerignore
├──cloudbuild.yaml
├──Dockerfile
├──main.py
└──requirements.txt

以下のコマンドを実行すると、ビルドが走ります。

gcloud builds submit --region=asia-northeast1

私の場合、以下の脆弱性が検知され、CI/CD が終了されました。
image07.png

Fixed version の列が空白になっているので、この脆弱性にはまだ修正されたバージョンが無いみたいです。
脆弱性の発見にコンテナイメージやライブラリのメンテンスが追いついていない可能性があります。
こういった時は修正されていない脆弱性を ignore する --ignore-unfixed オプションが使えます。

cloudbuild.yaml を以下のように修正します。

cloudbuild.yaml
steps:
  # Build the container image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}', '.']

  # Push the container image to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}']

  # Trivy
  - name: 'aquasec/trivy'
    entrypoint: '/bin/sh'
    args:
      - '-c'
      - | 
        echo "Starting checking vulnerabilities..."
-        trivy image --exit-code 1 --severity CRITICAL --quiet ${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}:latest
+        trivy image --exit-code 1 --severity CRITICAL --quiet --ignore-unfixed ${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}:latest  

  # Deploy container image to Cloud Run  
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
    - 'run'
    - 'deploy'
    - 'helloworld-py'
    - '--image'
    - '${_LOCATION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE}'
    - '--region'
    - 'asia-northeast1'
substitutions:
  _LOCATION: LOCATION
  _REPOSITORY: REPOSITORY
  _IMAGE: IMAGE

再度、以下のコマンドでビルドします。

$ gcloud builds submit --region=asia-northeast1

無事に Cloud Run へデプロイができました!

おわりに

今回は Dockerfile やコンテナイメージを脆弱性スキャンしてみました。
VSCode や既存のコンテナイメージを使って簡単に脆弱性スキャンができました。
Fix されていない脆弱性については trivy image --ignore-unfixed オプションを使って ignore しましたが、
下のように .trivyignore を使う方法もあります。
詳しくは Filter Vulnerabilities をご参照ください。

.trivyignore
# Accept the risk
CVE-2018-14618

# No impact in our settings
CVE-2019-1543

また、個人的には Terraform の IaC 設定ファイルや Kubernetes の Cluster にも脆弱性スキャンできる機能も気になるで、
引き続き検証してみたいと思います。

Discussion