お久しぶりです、ANDPADのtomtwinkleです。 この記事はANDPADアドベントカレンダー2日目の記事になります。
みなさん、Trivy 使ってますか?
Trivyとはコンテナイメージの脆弱性をチェック出来るツールです。
Golang の net/http
辺りは度々DoS脆弱性が見つかることがあり、それに早めに気づけて更新出来ているため、大変助かっております。
おや? Trivy DB の様子が……?
そんなTrivyですが、数ヶ月前くらいからTrivy DBのダウンロードに失敗するケースが増えてきました。
OCI repository error: 1 error occurred: * GET https://ghcr.io/v2/aquasecurity/trivy-db/manifests/2: TOOMANYREQUESTS: retry-after: 1.039236ms, allowed: 44000/minute"
原因はエラーを見ての通り GitHub Container Registry から Trivy DB を取得する際の rate-limit ですね。
そこで Githubのrate-limitに関するドキュメントを確認してみると
未認証TOKEN(GITHUB_TOKEN) の rate-limitは 60 requests per hour per repository
だそうです。
はて、以前ドキュメントを見たときは 1,000 requests per hour per repository
だったような……?
念の為 https://api.github.com/rate_limit
で現在の rate limitを確認できるので確認して見ましょう。
❯ curl https://api.github.com/rate_limit { /* 省略 */ "rate": { "limit": 60, "remaining": 60, "reset": 1732764408, "used": 0, "resource": "core" } }
ドキュメントの記載通り limit: 60
になっていそうですね。
Trivy の discussions でも同じ事象に遭遇したユーザーによる議論が繰り広げられています。
Trivy DB 取得時の rate-limit 対策
PAT(personal access token) を利用する
Github の rate-limit に引っかかっているので一番単純な対策としては
rate-limit が 5,000 requests per hour per repository
に増える PAT(personal access token) を利用することです。
- name: Get image tag id: get-image-tag shell: bash run: | # 何らかの方法でimage tagを取得、この例だとcommit hash IMAGE_TAG=${{ github.sha }} echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - name: Configured to use Github PAT shell: bash run: | git config --global url.https://${GITHUB_PAT_USER}:${GITHUB_PAT}@github.com/.insteadOf https://github.com/ - name: Run Trivy vulnerability scanner shell: bash run: | trivy image \ --exit-code 1 \ --ignore-unfixed \ --severity CRITICAL,HIGH \ --pkg-types os,library \ --no-progress \ "${{ env.ecr-arn }}:${{ steps.get-image-tag.outputs.IMAGE_TAG }}"
めでたしめでたし〜
……ちょっと待ってください。
そもそも利用するPATが増えると管理コスト増えてしまうという問題があります。 PATの利用期限を No expiration (無制限) にするのは何らかの要因でtokenが漏洩した場合や、担当者不在で管理外のtokenが発生する場合にtokenを無効化されるタイミングがないなど、セキュリティリスクに繋がります。 そのためしっかり管理したいのですが数が増えると面倒です。なるべく増やしたくない。
Amazon ECR の imageを利用する
Trivyの README を眺めている と Amazon ECR の方にもaquasecurityが試験的に運用しているRepositoryがありました。
こちらを利用すれば少なくとも GitHub の rate-limit に影響を受けずに利用できそうです。
更に Trivy v0.56.0 以降では db-repository
Option に複数のRepositoryが指定できるようになっていました。
これは良さそう。
- name: Get image tag id: get-image-tag shell: bash run: | # 何らかの方法でimage tagを取得、この例だとcommit hash IMAGE_TAG=${{ github.sha }} echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - name: Run Trivy vulnerability scanner shell: bash run: | trivy image \ --exit-code 1 \ --ignore-unfixed \ --severity CRITICAL,HIGH \ --pkg-types os,library \ --no-progress \ --db-repository "ghcr.io/aquasecurity/trivy-db:2","public.ecr.aws/aquasecurity/trivy-db" \ "${{ env.ecr-arn }}:${{ steps.get-image-tag.outputs.IMAGE_TAG }}"
ログを見るとまず ghcr.io/aquasecurity/trivy-db:2
にアクセスし rate-limitに引っかかっている場合は
public.ecr.aws/aquasecurity/trivy-db:2
をダウンロードに行っています。良いのでは?
... 2024-10-01T14:00:39+06:00 INFO [db] Need to update DB 2024-10-01T14:00:39+06:00 INFO Downloading vulnerability DB... 2024-10-01T14:00:39+06:00 INFO Downloading artifact... repo="ghcr.io/aquasecurity/trivy-db:2" 2024-10-01T14:00:41+06:00 ERROR Failed to download artifact repo="ghcr.io/aquasecurity/trivy-db:2" err="oci download error: failed to fetch the layer: GET https://ghcr.io/v2/aquasecurity/trivy-db/blobs/sha256:23d1b901e7534020d5ac5f238b090ad66dcb78afef7301ed7d8ebe6b974ab5f1: TOOMANYREQUESTS: retry-after: 1.254886ms, allowed: 44000/minute" 2024-10-01T14:00:41+06:00 INFO Trying to download artifact from other repository... 2024-10-01T14:00:41+06:00 INFO Downloading artifact... repo="public.ecr.aws/aquasecurity/trivy-db:2" 53.85 MiB / 53.85 MiB [--------------------------------------------------------------------------------------] 100.00% 1.45 MiB p/s 37s 2024-10-01T14:01:22+06:00 INFO Artifact successfully downloaded repo="public.ecr.aws/aquasecurity/trivy-db:2" ...
これで暫くは大丈夫そうですね! めでたしめでたし!
Trivy DB を Cacheする
めでたしめでたし、と思いきやもう少し続くのじゃ
Amazon ECR を参照すれば今回の問題は一応は解決するのですが そもそもTrivy DB の Update Interval は 6時間毎 であり、imageのbuild毎にTrivyのチェックのためだけに毎回ダウンロードする必要性はありません。 今回はGithubのrate-limitの問題でしたが、Amazon ECRのrate-limitが厳しくならないとも言い切れないです。
ならば、普段はTrivy DBのcacheを利用して6時間毎に取りに行けば良いのでは?
というわけで営業時間内の6時間ごとにcacheするGithub Actionsを作ってみました。
Trivy DB をCacheする Github Actions
name: Update Trivy DB Cache on: schedule: # At the start of 10:00 JST and 19:00 JST per 6 Hour on Monday through Friday # 10:00 JST and 16:00 JST - cron: '0 1-10/6 * * Mon-Fri' workflow_call: # Allow workflow triggering workflow_dispatch: # Allow manual triggering jobs: update-trivy-db-cache: runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Get current date id: date shell: bash run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Check Trivy Cache id: trivy-cache uses: actions/cache@v4 with: key: cache-trivy-${{ steps.date.outputs.date }} path: ${{ github.workspace }}/.cache/trivy lookup-only: true - name: Setup oras if: steps.trivy-cache.outputs.cache-hit != 'true' || github.event.schedule != '' uses: oras-project/setup-oras@v1 - name: Download and extract the vulnerability DB if: steps.trivy-cache.outputs.cache-hit != 'true' || github.event.schedule != '' shell: bash run: | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db oras pull public.ecr.aws/aquasecurity/trivy-db tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db rm db.tar.gz - name: Download and extract the Java DB if: steps.trivy-cache.outputs.cache-hit != 'true' || github.event.schedule != '' shell: bash run: | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db oras pull ghcr.io/aquasecurity/trivy-java-db:1 tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db rm javadb.tar.gz - name: Cache DBs if: steps.trivy-cache.outputs.cache-hit != 'true' || github.event.schedule != '' uses: actions/cache/save@v4 with: path: ${{ github.workspace }}/.cache/trivy key: cache-trivy-${{ steps.date.outputs.date }}
ざっくりと schedule と workflow_call で起動し、scheduleの場合は毎回cacheを更新 workflow_call で別のGithub Actionsから呼ばれた場合はcacheの有無を確認して無ければ更新するみたいな処理です。
弊社の場合、フレックス制なので勤務時間はバラバラなのですが、私の観測範囲では大体の開発メンバーは 10:00 - 19:00 付近を稼働時間としているようです。 その範囲で余計にworkflowが動きすぎないようにしています。
何らかの要因でずっとcacheを見てしまうという自体を避けるため、日付(YYYY-MM-DD) をcacheのKeyとしています。 こうしておくことで日付が変われば参照するcacheがなくなり、更新処理が走ります。
さらに、念の為手動でWorkflowを実行できるように workflow_dispatch も有効にしておくと吉でしょう。
今回は public.ecr.aws/aquasecurity/trivy-db
から取得していますが、
oras pull ghcr.io/aquasecurity/trivy-db:2
で取得できなかったら oras pull public.ecr.aws/aquasecurity/trivy-db
を参照する動作にするとより良さそうです。
Trivy DB の cacheを利用する Trivy Github Actions
name: Trivy Scan on: push: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: # この例では Amazon ECR の arn を指定 ecr-arn: '<image repository>' permissions: id-token: write contents: read jobs: trivy_db_update: name: Download and extract the vulnerability DB uses: ./.github/workflows/_trivy-db-update.yaml trivy_scan: needs: trivy_db_update runs-on: ubuntu-24.04 steps: - name: Get current date id: date shell: bash run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Checkout code uses: actions/checkout@v4 with: token: ${{secrets.GITHUB_TOKEN}} fetch-depth: 0 - name: Get image tag id: get-image-tag shell: bash run: | # 何らかの方法でimage tagを取得、この例だとcommit hash IMAGE_TAG=${{ github.sha }} echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - name: Login to Amazon ECR uses: aws-actions/amazon-ecr-login@v2 - name: Manual Trivy Setup uses: aquasecurity/setup-trivy@v0.2.2 with: cache: true version: v0.56.2 - name: Set Up Trivy Cache id: trivy-cache uses: actions/cache@v4 with: key: cache-trivy-${{ steps.date.outputs.date }} path: ${{ github.workspace }}/.cache/trivy - name: Echo Cache Hit shell: bash run: | echo "cache hit: ${{ steps.trivy-cache.outputs.cache-hit }}" - name: Run Trivy vulnerability scanner shell: bash run: | trivy image \ --exit-code 1 \ --ignore-unfixed \ --severity CRITICAL,HIGH \ --pkg-types os,library \ --cache-dir ${{ github.workspace }}/.cache/trivy \ --no-progress \ --skip-db-update \ --skip-java-db-update \ --offline-scan \ --skip-check-update \ "${{ env.ecr-arn }}:${{ steps.get-image-tag.outputs.IMAGE_TAG }}"
Github Actionsとしてはちょい長いですが重要なのは以下の3点です。
- 実行前にcacheがないと困るので先ほど作成した trivy-db-update の Github Actionsを呼び出す点
- cache時のKeyと同じく日付(YYYY-MM-DD)の cache key で actions/cache からcacheを取得する点
- Trivy の Option で DBの更新 を行わないでcacheを利用したOffline Scanするように設定する点
--cache-dir
で cacheディレクトリを指定し
--skip-db-update
--skip-java-db-update
--offline-scan
の指定でTrivy DBのダウンロードを抑制しながらcacheを利用するOffline Scanを指定しています。
--skip-check-update
は cliの最新版チェックの無効化です。
aquasecurity/setup-trivy
で事前に cli のBinary をダウンロードしcacheすることで毎回TrivyのBinaryをダウンロードしてこないようにしています。
ここまでやれば、Trivy DBのサーバー負荷を減らせるためTrivy DBに優しいGithub Actionsになったと思います!
めでたしめでたし!!!
アンドパッドでは CI/CDでピタゴラスイッチを書くのが楽しいGoエンジニアも絶賛募集中です!