trivy によるスキャンでファイルベースの Filtering に対応しました(EXPERIMENTAL な機能なので利用にはご注意下さい)

trivy は コンテナイメージのスキャンに加えて、CloudFormation や Terraform などの IaC 設定ファイルについてもセキュリティスキャンを実行することができます。

tfsec の技術とルールセットを取り込んでいて Terraform に限らない設定ファイルを trivy でチェック可能です。
IaC テンプレートに対してセキュリティ面で危ない設定が無いか確認する際は大変便利なので、私も活用させていただいてます。
今回 v0.45.0 にて、ファイル単位で特定チェックを無視するかどうかを設定できるようになりましたので、試してみました。

ファイルベース Filtering の何が嬉しいか

私は CloudFormation テンプレートに対する静的スキャンを利用しておりますが、特定リソースで特定チェックについて対応しない場合の取り扱いについて悩んでいる部分がありました。(S3 Server Access Logs の出力先として作成したバケットについて、Server Access Logs を有効化しない等)
tf ファイルであれば、下記のようなインラインコメントを利用した方法に対応しております。

#trivy:ignore:AVD-GCP-0051
resource "google_container_cluster" "one_off_test" {
  name     = var.cluster_name
  location = var.region
}

By Inline Comments

上記方法が yaml ファイルに対しては対応していなかったので、特定リソースで対応しないと決定した際にも .trivyignore を使ってリポジトリ全体での無視をするような運用をしておりました。
trivy 0.45.0 にて、.trivyignore.yaml というファイルを定義して読み込ませることでファイルベースの Filtering(特定検出結果の無視) ができるようになりました。
ただ、こちらは EXPERIMENTAL な機能であるため破壊的変更が入る可能性があるようです。

This feature might change without preserving backwards compatibility.
.trivyignore.yaml

とはいえ、個人的には渇望していた機能なので試してみました。
※ こちらも EXPERIMENTAL な機能ですが、Open Policy Agent を利用した Filtering にも対応しているようです。このために Rego を覚えて運用するのも大変に感じたので、今回はファイルベースの Filetering を試してみます。

試してみた

まず、下記 2 つテンプレートを用意しました。

何らかのコンテンツを格納する S3 バケット

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  BucketNamePrefix:
    Type: String

Resources:
  ContentsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${BucketNamePrefix}-contents-bucket"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LoggingConfiguration:
        DestinationBucketName: !ImportValue "server-access-logs-bucket-name"
        LogFilePrefix: !Sub "${BucketNamePrefix}-contents-bucket/"

アカウント内の S3 バケットの Server Access Logs を集約して格納するためのバケット

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  BucketNamePrefix:
    Type: String

Resources:
  ServerAccessLogsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${BucketNamePrefix}-serveraccesslogs-bucket"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
          - Id: Expire365days
            Status: Enabled
            ExpirationInDays: 365

Outputs:
  ServerAccessLogsBucketName:
    Description: Bucket Name for Server Access Logs
    Value: !Ref ServerAccessLogsBucket
    Export:
      Name: !Sub server-access-logs-bucket-name

リポジトリ全体として KMS 暗号化は不要として、S3-SSE で暗号化されていれば良いとします。
また、Server Access Logs 集約用バケットについては Server Access Logs と Versioning について無効で良いが、今後新しく追加されるリソースではチェックしたいためにこれらのチェックをリポジトリ全体では無効化したくないとします。
その場合、下記のように .trivyignore.yaml を定義することで特定ルールを無視することが可能です。

misconfigurations:
  - id: AVD-AWS-0132
    statement: "There is no problem with S3-SSE."
  - id: AVD-AWS-0089
    paths:
      - "cfn/s3-server-access-logs.yaml"
    statement: "It is the destination bucket for S3 Server access logs."
  - id: AVD-AWS-0090
    paths:
      - "cfn/s3-server-access-logs.yaml"
    statement: "It is the destination bucket for S3 Server access logs."

ファイル構造は下記です。

$ tree . -a
.
├── .trivyignore.yaml
└── cfn
    ├── s3-contents.yaml
    └── s3-server-access-logs.yaml

実際にスキャンしてみると、無事特定ルールを無視できました。

$ trivy conf . --ignorefile ./.trivyignore.yaml
2023-09-05T16:52:08.916+0900    INFO    Misconfiguration scanning is enabled
2023-09-05T16:52:09.420+0900    INFO    Detected config files: 2

s3-content2.yaml として Server Access Logs を無効化したバケットを追加して再度スキャンしてみます。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  BucketNamePrefix:
    Type: String

Resources:
  ContentsBucket2:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${BucketNamePrefix}-contents-bucket2"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

こちらではきちんと検出することができました!

> trivy conf . --ignorefile ./.trivyignore.yaml
2023-09-05T17:57:31.583+0900    INFO    Misconfiguration scanning is enabled
2023-09-05T17:57:32.087+0900    INFO    Detected config files: 3

cfn/s3-content2.yaml (cloudformation)

Tests: 11 (SUCCESSES: 10, FAILURES: 1, EXCEPTIONS: 0)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

LOW: Bucket has logging disabled
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Ensures S3 bucket logging is enabled for S3 buckets

See https://avd.aquasec.com/misconfig/avd-aws-0089
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 cfn/s3-content2.yaml:8-22
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   8 ┌   ContentsBucket2:
   9 │     Type: AWS::S3::Bucket
  10 │     Properties:
  11 │       BucketName: !Sub "${BucketNamePrefix}-contents-bucket2"
  12 │       PublicAccessBlockConfiguration:
  13 │         BlockPublicAcls: true
  14 │         BlockPublicPolicy: true
  15 │         IgnorePublicAcls: true
  16 └         RestrictPublicBuckets: true
  ..   
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

現状、 .trivyignore.yaml を利用する場合は明確に --ignorefile として指定する必要があるようです。
また、Dockerfile や k8s の manifest ファイル等のスキャンに加え、イメージスキャンの際も同様の Filtering を適用できるようです。

まとめ

楽にファイルベースの Filtering ができるようになって最高でした!
まだ実験的な機能のようですが、上手く活用できるとより便利に trivy を活用できると思います!