Terraform でTrivy の脆弱性スキャンとSecurity Hub を統合したCIを構築する

# はじめに
本記事では、Trivy によるコンテナイメージの脆弱性スキャンとSecurity Hub を統合したCI(Continuous Integration)環境を構築するTerraform のコードをサンプルとして共有しています。コンテナイメージのセキュリティ対策の一例として参考になればと思います。

また、補足として、CodeBuild のビルド内のTrivy に関する処理について、簡単に説明をしています。

# Terraform で構築する全体構成図
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/f960688a-ec33-0061-df23-7dfb4b1ee4c6.png"　 width=80%>

# 構成の概要

https://aws.amazon.com/jp/blogs/security/how-to-build-ci-cd-pipeline-container-vulnerability-scanning-trivy-and-aws-security-hub/

アーキテクチャは上記のAWS Security Blog を元にしています。 

Dockerfile を含むコードをCodeCommit にプッシュするとCodePipeline が実行され、CodeBuild でビルドしたコンテナイメージを、Trivy で脆弱性スキャンをしています。

その結果、深刻度が CRITICAL な脆弱性を検出した場合は、ECR にはプッシュせずに、CIを停止させ、Findings(結果)をSecurity Hub に送信し、確認できるようにしています。

コンテナイメージに CRITICAL な脆弱性を検出しなかった場合は、安全なイメージとしてECR にプッシュしています。

### Trivy とは

https://aquasecurity.github.io/trivy/v0.28.0/getting-started/overview/
> It is designed to be used in CI. Before pushing to a container registry or deploying your application, you can scan your local container image and > other artifacts easily. See Integrations for details.
<font color="Red">CIで使用することを想定しています。</font>コンテナレジストリへのプッシュやアプリケーションのデプロイの前に、ローカルのコンテナイメージや他のアーティファクトを簡単にスキャンすることができます。詳細はIntegrationsを参照してください。

### Security Hub とは

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/what-is-securityhub.html
>Security Hubは、AWSアカウント、サービス、およびサポートされているサードパーティパートナー製品全体からセキュリティデータを収集し、セキュリティトレンドを分析して、最も優先度の高いセキュリティ問題を特定するのに役立ちます。

# Terraform のコードと構成
https://github.com/okubo-t/aws-tf-trivy-securityhub-ci

```terminal
$ tree aws-tf-trivy-securityhub-ci 
aws-tf-trivy-securityhub-ci
├── main.tf
└── modules
    └── codepipeline
        ├── buildspec.yml
        ├── cloudwatch_event.tf
        ├── cloudwatch_logs.tf
        ├── codebuild.tf
        ├── codecommit.tf
        ├── codepipeline.tf
        ├── ecr.tf
        ├── iam.tf
        ├── kms.tf
        ├── provider.tf
        ├── s3.tf
        └── variables.tf

2 directories, 13 files
```

# 使い方
## Terraform の動作確認環境

```terraform
$ terraform -version
Terraform v1.1.9
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v4.15.1
```

## 変数の設定
main.tf に下記の変数を設定します。
|変数名|説明|
|:---|:---|
|profile|各AWSのプロファイル名|
|region|リージョン|
|prefix|各AWSのリソースに付与するプレフィックス|
|env|環境を識別するプレフィックス|
|repository_name|CodeCommit のリポジトリ名|
|branch_name|ブランチ名|
|ecr_name|ECR のリポジトリ名|

```terraform:main.tf
module "codepipeline" {
  source = "./modules/codepipeline"

  Account = {
    profile = "YOUR AWS ACCOUNT PROFILE NAME"
    region  = "ap-northeast-1"
  }

  # Project Prefix
  prefix = "prefix"

  # Environment Prefix
  env = "test"

  # Codecommit Repository Name
  repository_name = "trivy-securityhub-ci"
  # Branch Name
  branch_name = "master"

  # ECR Repository Name
  ecr_name = "trivy-securityhub-ci"
}
```

## Terraform の実行
```terraform
$ terraform init
$ terraform plan
$ terraform apply
```

## Security Hub の有効化
Security Hub を有効化していない環境であれば、有効化します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/40c3e63f-1487-e660-a0de-0435f676084a.png"　 width=70%>

## Security Hub と Aqua Security の統合
Security Hub と Aqua Security を統合します。
**[Security Hub] > [統合] > Aqua Security で検索 > [結果を受け入れる]**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/96c40462-6664-2204-71d1-b84521d83dd1.png"　 width=70%>

## Dockerfile をCodeCommit にプッシュ
動作確認として、脆弱性を含むテスト用のDockerfile をCodeCommit にプッシュします。

https://github.com/aws-samples/aws-security-hub-scan-with-trivy/blob/master/Dockerfile

```Dockerfile:Dockerfile
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

FROM composer:1.7.2

RUN git clone https://github.com/aquasecurity/trivy-ci-test.git && cd trivy-ci-test && rm Cargo.lock && rm Pipfile.lock

CMD apk add --no-cache mysql-client
ENTRYPOINT ["mysql"]
```

## CodePipeline > CodeCommit > CodeBuild の実行
CodeCommit の変更イベントを検知してCodePipeline > CodeBuild が実行されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/8ea6b454-04f2-08b5-8ef5-ddfb5e77e4d7.png"　 width=55%>

## CodeBuildでTrivy による脆弱性スキャン
Trivy のコンテナイメージのスキャンによって、脆弱性が検出され、ビルドのステータスが失敗となり、Findings がSecurity Hub に送信されます。

**ビルドログ**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/38940db3-be1a-3948-c9c6-9bf8db477cb2.png"　 width=100%>

**フェーズ詳細**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/caaa0a2b-ac25-e12c-214a-5d4da7029eaf.png"　 width=100%>

## Security Hub でFindings を確認
Security Hub で検出結果を確認できます。
**[Security Hub] > [統合] > [結果を参照]**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/ee76cb96-7372-e107-541e-fc4ffbcf7d1b.png"　 width=70%>

**検出結果**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/c6bf8907-50c3-52c1-fd25-7f03781dead7.png"　 width=100%>

 # 補足: CodeBuild のビルドの仕様について
Trivy を使った脆弱性スキャンを組み込んだビルドの各フェーズごとの処理について説明します。

参考:

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html

### buildspec
buildspec.yml は下記で構成しています。

```yml:buildspec.yml
version: 0.2

env:
  variables:
    DOCKER_BUILDKIT: "1"

phases:
  install:
    runtime-versions:
      docker: 19
    commands:
      - echo Trivy Install...
      # - TRIVY_VERSION=0.28.0
      - TRIVY_VERSION=$(curl -sS https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
      - rpm -ivh https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.rpm
      - trivy --version
      - echo Install completed on `date`
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - echo $AWS_DEFAULT_REGION
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t ${REPOSITORY_URI}:${IMAGE_TAG} .
      - echo Build completed on `date`
      - echo Trivy Scan started on `date`
      - AWS_REGION=$AWS_DEFAULT_REGION
      - AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID
      - trivy image --no-progress --format template --template "@/usr/local/share/trivy/templates/asff.tpl" -o tmp-report.asff --ignore-unfixed --exit-code 1 --severity CRITICAL ${REPOSITORY_URI}:${IMAGE_TAG}
  post_build:
    commands:
      - echo Trivy Scan completed on `date`
      - |
        if [ $CODEBUILD_BUILD_SUCCEEDING == "1" ]; then
          docker push ${REPOSITORY_URI}:${IMAGE_TAG} ;
          printf "[{\"name\":\"${IMAGE_REPO_NAME}\",\"imageUri\":\"%s\"}]" $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json ;
          echo Build stage successfully completed on `date`
        else
          cat tmp-report.asff | jq '.Findings' > report.asff ;
          aws securityhub batch-import-findings --findings file://report.asff --region $AWS_DEFAULT_REGION ;
        fi
artifacts:
  files:
    - imagedefinitions.json
```

### phases/install
このビルドフェーズでは、Trivy を下記コマンドでインストールしています。

```terminal
#TRIVY_VERSION=0.28.0 バージョンを固定したい場合は変数にバージョンを指定してください
TRIVY_VERSION=$(curl -sS https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
rpm -ivh https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.rpm
```
参考: Installation

https://aquasecurity.github.io/trivy/v0.28.0/getting-started/installation/

### phases/build
このビルドフェーズでは、ビルドしたイメージをTrivy でスキャンし、深刻度が CRITICAL な脆弱性を検出した場合は、イメージに問題があるとし、ビルドステージを失敗とします。（CIを停止とします。）
そして、Amazon Security Finding Format (ASFF) に準拠した Findings を生成しています。

```terminal
AWS_REGION=$AWS_DEFAULT_REGION
AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID
trivy image --no-progress --format template --template "@/usr/local/share/trivy/templates/asff.tpl" -o tmp-report.asff --ignore-unfixed --exit-code 1 --severity CRITICAL ${REPOSITORY_URI}:${IMAGE_TAG}
```
参考: Scan Option と ASFFについて

https://aquasecurity.github.io/trivy/v0.28.0/docs/references/cli/image/

https://aquasecurity.github.io/trivy/v0.28.0/docs/vulnerability/examples/report/#asff

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-findings-format.html

### phases/post_build
このビルドフェーズでは、コンテナイメージに CRITICAL な脆弱性を検出しなかった場合は、安全なイメージとしてECR にプッシュし、ビルドステージを正常とします。

CRITICAL な脆弱性を検出した場合は、生成したFindings をSecurity Hubに送信しています。
このサンプルでは、AWS CLI を使って送信しています。
```
if [ $CODEBUILD_BUILD_SUCCEEDING == "1" ]; then
   docker push ${REPOSITORY_URI}:${IMAGE_TAG} ;
   printf "[{\"name\":\"${IMAGE_REPO_NAME}\",\"imageUri\":\"%s\"}]" $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json ;
   echo Build stage successfully completed on `date`
else
   cat tmp-report.asff | jq '.Findings' > report.asff ;
   aws securityhub batch-import-findings --findings file://report.asff --region $AWS_DEFAULT_REGION ;
fi
```
参考: Upload findings to Security Hub

https://aquasecurity.github.io/trivy/v0.28.0/docs/integrations/aws-security-hub/

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html

# さいごに
初歩的な内容ですが、DevSecOps の仕組みを検討する上で少しでも参考になれば幸いです。