今日から始めるセキュリティシフトレフト on AWS ④AWS CodeBuildとAWS Security Hubで効率良く脆弱性管理をする

# はじめに
セキュリティシフトレフト記事第四弾。

前回は、アプリケーションのSASTツールをどのようにAWS CodeBuild上で使うのが良いのかを検証した。

https://qiita.com/neruneruo/items/7021a808b9513bcb8f87

今回は、第二回の記事の内容に戻り、Trivyによって検出した脆弱性に関連した情報をAWS Security Hubを送り、問題管理を行えるようにしていく。AWS CodeBuildのバッチビルドの構成はそのままに、Trivyを動作させるビルド部分のみを変更するため、第二回の記事の内容を参照しておいていただきたい。

https://qiita.com/neruneruo/items/83ed70aa286ec511f900

# 脆弱性のあるDockerfileの準備
今回は、あえて脆弱性を仕込んだDockerfileを作って検知までしてみたいため、以下のようなDockerfileを準備する。Dockerfile内でnginx.confを差し替えていないので、実際の運用ができるものではないが、サンプルのため細かいことは気にしないでいただきたい。

```Dockerfile
FROM public.ecr.aws/nginx/nginx:1.27

RUN apt-get update \
  && apt-get install --no-install-recommends -y curl=7.88.1-10+deb12u7 \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

HEALTHCHECK CMD /usr/bin/curl http://localhost/

WORKDIR /

CMD ["nginx", "-g", "daemon off;"]
```

# AWS Security Hubの有効化
AWS Security Hubを以下のように有効化する。
```aws_securityhub_account```はその名の通り、アカウント全体の設定なので、既に有効化されている場合は特に作成する必要はない。

なお、AWS Security Hubはスキャン単位で課金されるため、今回はスキャンする量を減らすために```enable_default_standards = false```を設定しているが、実際の運用時には必要なセキュリティチェックは有効にしておこう。

> Security Hub の価格は、セキュリティチェックの数、検出結果の取り込みイベントの数、1 か月あたりに処理されるルール評価の量という 3 つの観点から設定されます。

https://aws.amazon.com/jp/security-hub/pricing/



```HCL
resource "aws_securityhub_account" "example" {
  enable_default_standards = false
  auto_enable_controls     = false
}
```

# AWS Security HubとTrivyを統合する
さて、Security Hubを有効化したら次はTrivyのレポートを受けられる状態にする。
他のプロダクトと統合する際は、Terraformの```aws_securityhub_product_subscription```のリソースを使用する。

具体的にどんなプロダクトと統合可能かの全容は以下のAWS公式のユーザーガイドに記載されている。

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-partner-providers.html

Trivyについても記載されているので、その値を```product_arn```に設定する。

```HCL
resource "aws_securityhub_product_subscription" "example" {
  depends_on = [aws_securityhub_account.example]

  product_arn = "arn:aws:securityhub:${data.aws_region.current.name}::product/aquasecurity/aquasecurity"
}
```
# AWS CodeBuildのIAM権限設定
今回、AWS Security Hubに情報を送信するにあたり、AWS CodeBuildのサービスロールに```securityhub:BatchImportFindings```の権限付与が必要になるため、忘れず設定しておこう。

```HCL
# (前略)
  statement {
    effect = "Allow"

    actions = [
      "securityhub:BatchImportFindings",
    ]

    resources = [
      "*",
    ]
  }
# (後略)
```



# AWS CodeBuildのBuildspecの設定
次に、AWS CodeBuildのBuildspecを準備していく。
第二回で作成したBuildspecは以下の通り簡易なものであったが、これを書き換えていく。その際、ハマりどころがいくつかあるので、ポイントを抑えて説明していく。

```yaml:21_trivy.yml
version: 0.2

env:
  variables:
    IMAGE_NAME: "container_image"
    IMAGE_TAG : "1.0"
    TRIVY_PATH: "/usr/bin/trivy"

phases:
  install:
    commands:
      # install Trivy
      - VERSION=$(curl --silent 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${VERSION}/trivy_${VERSION}_Linux-64bit.rpm
  build:
    commands:
      - docker build . -t ${IMAGE_NAME}:${IMAGE_TAG}
      - ${TRIVY_PATH} image --quiet --exit-code 1 ${IMAGE_NAME}:${IMAGE_TAG} 
```

## 変更後のBuildspecの全体像
変更後のBuildspecは以下の通りとなる。```phases.build```以前は変える必要がないのでそのままにして良い。

```yaml:21_trivy.yml
# (前略)
  build:
    commands:
      - docker build . -t ${IMAGE_NAME}:${IMAGE_TAG}
      # Settings are required to reference the AWS_ACCOUNT_ID environment variable in Trivy's ASFF template.
      - AWS_ACCOUNT_ID=$(echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :)
      - ${TRIVY_PATH} image
        --no-progress
        --db-repository public.ecr.aws/aquasecurity/trivy-db:2
        --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db:1
        --format template
        --template "@/usr/local/share/trivy/templates/asff.tpl"
        --output /tmp/tmp-report.asff
        --severity HIGH
        --ignore-unfixed
        --exit-code 1
        ${IMAGE_NAME}:${IMAGE_TAG} 
  post_build:
    commands:
      # Send report to AWS Security Hub on build failure
      - |
        if [ $CODEBUILD_BUILD_SUCCEEDING != "1" ]; then
          jq '.Findings' /tmp/tmp-report.asff > /tmp/report.asff
          aws securityhub batch-import-findings --findings file:///tmp/report.asff --region ${AWS_DEFAULT_REGION}
        fi
```

## Trivyのレポート形式をASFFに指定する
Trivyのレポートの形式は以下のページで詳細が書かれている。

https://aquasecurity.github.io/trivy/v0.28.1/docs/vulnerability/examples/report/

ここに

> If Trivy is installed using rpm then default templates can be found at ```/usr/local/share/trivy/templates``` .

と書いてある通り、ASFFのテンプレートは```/usr/local/share/trivy/templates/asff.tpl```に置かれている。GitHubにもあるので、参考までにリンクを貼っておく。

https://github.com/aquasecurity/trivy/blob/main/contrib/asff.tpl

気を付けなければいけないのは、このテンプレート内で```AWS_REGION```と```AWS_ACCOUNT_ID```の二つの環境変数を参照していることだ。
```AWS_REGION```はAWS CodeBuildのデフォルト環境変数で、「AWS CodeBuildを実行しているリージョン」が設定されてくるので、何も設定しなくて良い(もちろん、AWS CodeBuildとAWS Security Hubを別リージョンで動かしている場合は変更が必要)。
```AWS_ACCOUNT_ID```はデフォルト環境変数になっていないため、```AWS_ACCOUNT_ID=$(echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :)```で値を取り出して設定をしておく(AWS CodeBuildに環境変数で渡しても良いが、クロスアカウントでない限りはこちらの方が簡単だろう)。

## Trivyのデータベース更新のレジストリ変更
Trivyのデフォルトの脆弱性情報のデータベースは```ghcr.io```であり、AWS CodeBuildから何も指定せずに動作させると、GitHub側のRateLimitにぶつかってしまいうまく動作しない(しかも、このエラーログがRateLimitと分かるログでないので、ハマるとつらい)。
ということで、ECR Public GalleryのレジストリURLを```--db-repository```と```--java-db-repository```に設定しておこう。```--no-progress```オプションを付けるとDB更新の進捗が表示されなくなる。

## 重要度等の設定
すべての修正をしているとキリがないので、HIGHかCRITICAL以上のものを管理対象としよう。
これは```--severity HIGH```で設定可能だ。
また、修正されていない脆弱性を表示されてもつらいだけなので、```--ignore-unfixed```のオプションを使い、FIX済みで未適用のもののみAWS Security Hubに送信するようにする。

## post_buildの設定
最後に、```aws securityhub batch-import-findings```で、出力したASFFを送信する。
```.Findings```のJSON要素のみ必要であるため、```jq```コマンドで編集をしている。

これで、準備は完了だ。

# いざ、動かす！
```terraform apply```したらしばらくビルドの完了を待って、AWS Security Hubのコンソールを見てみよう。
以下の手順で、Trivyのレポートに絞って確認することができる。

①「統合」を選択
②検索ボックスで「Aqua」を検索
③「結果を参照」のリンクを押す

![キャプチャ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/ce919040-b487-460e-cf4e-e6d4440f3c11.png)

上記を行うと、以下のように検出された脆弱性の一覧を確認することができる(今回はHIGH以上は1件のみであった)。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/9516f78b-d229-bbe6-7a48-81752a84ea20.png)

「検出結果」のリンクを押下することで、より詳細な情報を確認が可能だ。
リソースのタブの「詳細」を開くことで、現在どのバージョンのパッケージを利用していて、パッチするためには何を指定すれば良いかもここから分かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/4402604d-4c2f-89ff-607e-bbe8643162c2.png)

指摘されているとおり、Dockerfileでlibheifのパッチ済みバージョンを追加インストールしよう。

```diff_docker:Dockerfile
FROM public.ecr.aws/nginx/nginx:1.27

RUN apt-get update \
  && apt-get install --no-install-recommends -y curl=7.88.1-10+deb12u7 \
+ && apt-get install --no-install-recommends -y libheif-dev=1.15.1-1+deb12u1 \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

HEALTHCHECK CMD /usr/bin/curl http://localhost/

WORKDIR /

CMD ["nginx", "-g", "daemon off;"]
```

これで、再度AWS CodeBuildを実行すると、今度は指摘がなく正常終了するはずだ。
エラーが出なくなったら、AWS Security Hub上のステータスもちゃんと更新をしておこう。

![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/60e34644-4f63-075b-6d7c-a27facab495d.png)

これで、より効率的に脆弱性管理をできるようになった！