この記事はコネヒトアドベントカレンダー21日目の記事です。
コネヒト Advent Calendar 2023って? コネヒトのエンジニアやデザイナーやPdMがお送りするアドベント カレンダーです。 コネヒトは「家族像」というテーマを取りまく様々な課題の解決を 目指す会社で、 ママの一歩を支えるアプリ「ママリ」などを 運営しています。
はじめに
コネヒトのプラットフォームグループでインフラ関連を担当している@yosshiです。 今年の7月に入社してから早いもので半年が経ちました。時が経つのは本当に早いですね。
今回のブログでは、セキュリティスキャンツールであるtrivyを使って、自動的にIaC (Infrastructure as Code)スキャンを実行する仕組みを構築した話をしたいと思います。
弊社ではインフラ構成をTerraform利用して管理するようにしており、それをモノレポの構成で運用しています。
インフラリソースの作成・変更・削除をする際には必ず相互レビューを必須としているものの、人的なチェックのみに依存しているためファイルの設定ミスやセキュリティ上の見落としが潜在的なリスクとなっていました。
これらを事前に検知するツールを調べていたところtrivyの存在を知り、今回導入に至りました。
trivyとは
- コンテナイメージやアプリケーションの依存ライブラリ・OSのパッケージなどを迅速にスキャンし、セキュリティリスクを効率的に検出するセキュリティツールです。
- 当初はコンテナのセキュリティ問題に焦点を当てたツールとして開発されたようですが、後にTerraformやKubernetesなどの設定ファイルのチェック機能も追加されました。
- 設定ファイルのチェックでは、設定ミスやセキュリティのベストプラクティスに沿っていない構成などを検知することができます。
参考:https://github.com/aquasecurity/trivy
(参考)スキャン可能な対象 2023/12時点
- コンテナイメージ
- ファイルシステム
- リモートGitリポジトリ
- 仮想マシンイメージ
- Kubernetes
- AWS
(参考)検出可能な内容 2023/12時点
- OSパッケージとソフトウェア依存関係(SBOM)
- 既知の脆弱性(CVE)
- IaCの問題と設定ミス
- 機密情報と秘密
- ソフトウェアライセンス
上記の通りtrivyでは、Terraformのコードだけでなくさまざまなセキュリティスキャン行うことができます。
trivy自体の詳しい説明はここでは割愛するので、詳しくは公式サイトや他の方の記事などをご確認いただけると幸いです。
Github Actionsでの実装
では早速ですが実装内容の説明に移りたいと思います。 今回はTerraformコードのスキャンをCIに組み込んでいます。 弊社では CIツールとしてGithub Actionsを利用しているため、今回もこちらを利用します。
スキャン実行は、trivy公式で用意しているGithub Actions用のツール(tricy-action)があるので、こちらをそのまま利用しています。
背景
まず、前提条件となる弊社のディレクトリ構造を説明します。
弊社では、サービスで共通利用するリソース(base_system)と各サービスで利用するリソース(product_system)とでディレクトリを分けており、 product_sytem以下にはサービスごと関連するリソースが紐づいています。以下のようなイメージです。
. ├── base_system │ ├── common │ │ └── terraform │ ├── privilege │ │ └── terraform │ . │ . └── product_system ├── (サービス1) │ └── terraform ├── (サービス2) │ └── terraform . . .
実装方針と内容
実装したGithub Actionsのコードは以下の通りです。
主に以下のことをやっています。
- シェルスクリプト(sync-updated-dirs-to-work-dir.sh)の実行
- mainブランチとプルリクエスト中のブランチのコードの差分を検知
- 差分となっているディレクトリを作業用のディレクトリに同期
- 作業ディレクトリに対してスキャン実行
- 検出されたスキャン結果をPRのコメントに残す。
Github Actionsのコードは以下の通りです。
name: trivy-scan on: pull_request: types: [opened, reopened, synchronize] permissions: id-token: write contents: read pull-requests: write jobs: trivy_scan: name: Run Trivy Scan runs-on: ubuntu-latest steps: - name: Clone repo uses: actions/checkout@v4 - name: fetch origin/main for getting diff run: git fetch --depth 1 origin $GITHUB_BASE_REF - name: Sync Updated Dir to Work Dir env: GITHUB_TOKEN: ${{ secrets.github_token }} run: | bash utils/scripts/sync-updated-dirs-to-work-dir.sh - name: Trivy Scan uses: aquasecurity/trivy-action@master with: scan-type: 'config' severity: 'HIGH,CRITICAL' scan-ref: scan_work_dir output: trivy-scan-result.txt - name: Format Trivy Scan Result run: | if [ -s trivy-scan-result.txt ]; then # ファイルに内容がある場合 echo -e "## 脆弱性スキャン結果\n<details><summary>詳細</summary>\n\n\`\`\`\n$(cat trivy-scan-result.txt)\n\`\`\`\n</details>" > formatted-trivy-result.md else # ファイルが空の場合 echo -e "## 脆弱性スキャン結果\n脆弱性が検知されませんでした。" > formatted-trivy-result.md fi - name: Comment PR with Trivy scan results uses: marocchino/sticky-pull-request-comment@v2 with: recreate: true GITHUB_TOKEN: ${{ secrets.github_token }} path: formatted-trivy-result.md
詳しく見ていきます。
まずは変更差分となったディレクトリを調べるため、Github Actionsの中でシェルスクリプトを実行しています。
ここでは一時的なディレクトリを用意し、プルリクエスト上で変更が発生したディレクトリの内容を作業用のディレクトリに同期するという作業を行なっています。
Github Actionsの関連部分は以下です。
- name: Sync Updated Dir to Work Dir run: | bash utils/scripts/sync-updated-dirs-to-work-dir.sh
sync-updated-dirs-to-work-dir.sh
はrsyncをwrapしたもので、前述の通りプルリクエスト上で変更が発生したterraformディレクトリを、スキャン実行する一時的な作業用ディレクトリに同期する処理をしています。1
細かな実装は内部事情に特化したものとなっているため割愛しますが、例えば、base_system/common/terraform
, base_system/privilege/terraform
, product_system/(サービス1)/terraform
のディレクトリで変更が発生している場合、このスクリプトを実行することで作業用ディレクトリ(scan_work_dir)が作成され、以下のようなディレクトリ構造となります。
. ├── base_system │ ├── common │ │ └── terraform │ └── privilege │ │ └── terraform │ . │ . ├── product_system │ ├── (サービス1) │ │ └── terraform │ ├── (サービス2) │ │ └── terraform │ . │ . └── scan_work_dir (ここのディレクトリにtrivyによるスキャンを実行する) ├── base_system │ ├── common │ │ └── terraform │ └── privilege │ │ └── terraform └── product_sytem └── (サービス1) └── terraform
次に同期してきた作業用ディレクトリに対してスキャンを実行します。
ここでは前述の通り、公式が用意しているtricy-actionを利用しています。
- name: Trivy Scan uses: aquasecurity/trivy-action@master with: scan-type: 'config' severity: 'HIGH,CRITICAL' scan-ref: scan_work_dir output: trivy-scan-result.txt
オプションの内容は以下の通りです。
- scan-type: 'config'
config
はTerraformなどの設定ファイルをスキャンする際に使用する。
- scan-ref: scan_work_dir
- 作業用ディレクトリ
scan_work_dir
を指定している。
- 作業用ディレクトリ
- output: trivy-scan-result.txt
- スキャンした結果をテキストファイル
trivy-scan-result.txt
に出力する。 - このテキストファイルは次のアクションでフォーマットを整形して出力するために使用している。
- スキャンした結果をテキストファイル
- serverity
- 脆弱性の深刻度で'CLITICAL'と'HIGH'を指定しています。
- ここのレベルは、CVSS2によって定量化された脆弱性の深刻度をもとに設定されています。
他のオプションなどについてはtricy-actionのページをご確認ください。
次に、前のアクションで出力したテキストファイルを見やすく整形した上で、marocchino/sticky-pull-request-commentを使用しプルリクエストのコメント欄に出力しています。
プルリクエスト更新時には、既存の脆弱性に関するコメントを削除した上で新規コメントを残して欲しかったので、その点でmarocchino/sticky-pull-request-comment(recreate: trueのオプション指定)を利用することで楽に実装することができました。
# スキャンした結果を整える - name: Format Trivy Scan Result run: | if [ -s trivy-scan-result.txt ]; then # ファイルに内容がある場合 echo -e "## 脆弱性スキャン結果\n<details><summary>詳細</summary>\n\n\`\`\`\n$(cat trivy-scan-result.txt)\n\`\`\`\n</details>" > formatted-trivy-result.md else # ファイルが空の場合 echo -e "## 脆弱性スキャン結果\n脆弱性が検知されませんでした。" > formatted-trivy-result.md fi - name: Comment PR with Trivy scan results uses: marocchino/sticky-pull-request-comment@v2 with: recreate: true GITHUB_TOKEN: ${{ secrets.github_token }} path: formatted-trivy-result.md
スキャン結果
脆弱性が検知されなかった場合
脆弱性が検知された場合
「詳細」部分を開くと以下のような形で表示されています。
今回のケースだとCritical0件、High61件の脆弱性が検知されていることがわかります。
base_system/privilege/terraform/xxx.tf (terraform) ===================================================================== Tests: 75 (SUCCESSES: 14, FAILURES: 61, EXCEPTIONS: 0) Failures: 61 (HIGH: 61, CRITICAL: 0) HIGH: IAM policy document uses sensitive action 'autoscaling:Describe*' on wildcarded resource '*' ════════════════════════════════════════ You should use the principle of least privilege when defining your IAM policies. This means you should specify each exact permission required without using wildcards, as this could cause the granting of access to certain undesired actions, resources and principals. See https://avd.aquasec.com/misconfig/avd-aws-0057 ──────────────────────────────────────── base_system/privilege/terraform/xxx.tf:376 via base_system/privilege/terraform/xxx.tf:376 (aws_iam_policy.xxx.xxx) via base_system/privilege/terraform/xxx.tf:375-438 (aws_iam_policy.xxx.xxx) via base_system/privilege/terraform/xxx.tf:373-439 (aws_iam_policy.xxx) ──────────────────────────────────────── 373 resource "aws_iam_policy" "xxx" { ... 376 [ Version = "2012-10-17", ... 439 } ──────────────────────────────────────── ・ ・ ・(以下省略)
上記の例では、重要度「HIGH」の脆弱性が検知されています。
IAMポリシーは最小特権で付与するべきなので、ワイルドカード使うのはリスクがありますよ、という指摘のようです。
補足
検出された脆弱性の中で、この内容は指摘する対象から除外したい、というケースもあると思います。
その場合は.trivyignore
というファイルをトップディレクトリに置くことで勝手に参照して検出対象から除外してくれます。
以下のような形で記載します。
.trivyignore
AVD-AWS-0057 AVD-AWS-XXXX
(参考)
以下のようにtrivyignoresのオプションを利用することで、トップディレクトリに配置するだけでなく別のディレクトリに配置しているファイルを参照させたり、.trivyignore
以外の別のファイル名を指定することもできるようです。
- name: Trivy Scan uses: aquasecurity/trivy-action@master with: scan-type: 'config' severity: 'HIGH,CRITICAL' scan-ref: trivy_temp_dir output: trivy-scan-result.txt trivyignores: test-trivyignore
まとめ
今回はtrivyとGitHub Actionsを活用し、Terraformでのセキュリティ上のリスクを効果的に検知する仕組みを構築しました。
今回検知されたものについては、優先度の高いものから順次改善していきたいと思います。
また、冒頭に説明した通り、trivyでは他にも様々なものを検知してくれる機能があるので、 Terraformの設定だけでなく色々な場面での活用を検討していきたいと思います。