SecurityHubのGuardDutyサンプル検出結果をAWS CLI(シェルスクリプト)でまとめて解決/抑制してみた

SecurityHubのGuardDutyサンプル検出結果をAWS CLI(シェルスクリプト)でまとめて解決/抑制してみた

2025.08.15

クラウド事業本部の梶原@シン福岡オフィスです。

GuardDutyのサンプル検出結果をぽちっとすると大量の検出結果が生成されます。ぽちっとすると大量の検出結果が生成されます。(大事なry
2025年8月15日時点で363件(東京リージョン)生成されるます。

1件だけ生成したい場合は、以下をご参考ください

Amazon GuardDutyで1つのサンプルイベントのみ発生させる方法
https://dev.classmethod.jp/articles/create-a-single-sample-findings-in-guardduty/

生成してしまったものは正直しょうがないのですが、AWS Security Hub CSPMの検出結果にも大量に検出され、実際の検出結果と混在されてしまい。見通しがかなり悪くなってしまいます。
そこでAWS Security HubでGuardDutyのサンプル検出結果が、コンソールで1件ずつ(まとめても20件)「解決済み(RESOLVED)」や「抑制済み(SUPPRESSED)」にするのは大変です。ということで、AWS CLIを使ったスクリプトで、ページネーション対応でサンプル検出結果を一括処理する方法を紹介します。

ちなみに、AWSコンソールでサンプル検出結果のみを絞り込む場合は、検出結果の例=Trueで絞り込めます。
ぽちぽちする場合は、こちらでも対応可能です。

検出結果 _ Security Hub CSPM _ ap-northeast-1 - Google

おまけとして、最後にService Control Policies (SCPs)を使ってサンプル検出結果の生成自体を防ぐ方法もご紹介します。

尚、スクリプトの生成には生成AIを使用しています。動作検証は十分に行っていますがすべての環境での動作を保証するものではありませんので、ご容赦ください。


前提条件

  • AWS CLIがインストール済みで、適切な権限(securityhub:GetFindings, securityhub:BatchUpdateFindings)が設定されていること。
  • jqがインストール済み(例: apt install jqbrew install jq)。AWS CloudShellにはインストール済です。
  • Security HubとGuardDutyが有効であること
  • リージョン毎に検出されるので、リージョン毎に実施してください。

スクリプトの使い方

下記記載のスクリプトをupdate_guardduty_samples.shに保存し、実行権限を付与します。AWS CloudShellでの動作を確認済みです。

chmod +x update_guardduty_samples.sh
./update_guardduty_samples.sh [RESOLVED|SUPPRESSED] [dry-run]
  • 引数1: 更新ステータス(RESOLVED または SUPPRESSED)。デフォルトはRESOLVED
  • 引数2: dry-runを指定すると、更新は実施せずに対象の取得だけを行います。

出力例 (dry-run時):

$ ./update_guardduty_samples.sh RESOLVED dry-run
[DRY RUN] Would update 100 findings to RESOLVED.
[DRY RUN] Would update 100 findings to RESOLVED.
[DRY RUN] Would update 100 findings to RESOLVED.
[DRY RUN] Would update 63 findings to RESOLVED.
総更新件数: 0 (未処理: 0)

出力例 (本実行時):

$ ./update_guardduty_samples.sh RESOLVED
Updated 100 findings (Unprocessed: 0).
Updated 100 findings (Unprocessed: 0).
Updated 100 findings (Unprocessed: 0).
Updated 63 findings (Unprocessed: 0).
総更新件数: 363 (未処理: 0)

ページネーション対応実施してますので、100件以上でも処理できるようにしています。

スクリプト全文

以下がスクリプトになります。

#!/bin/bash

# スクリプトの使用方法: ./update_guardduty_samples.sh [RESOLVED|SUPPRESSED] [dry-run]
# 例: ./update_guardduty_samples.sh RESOLVED dry-run
# dry-runを指定すると、更新せずに対象件数を確認。
# 前提: AWS CLIがインストールされ、適切な権限が設定済み。jqが必要。

STATUS="${1:-RESOLVED}"  # デフォルトはRESOLVED
DRY_RUN="$2"
REGION="ap-northeast-1"       # リージョン(変更してください)
MAX_RESULTS=100          # ページサイズ
SLEEP_INTERVAL=1         # APIレート制限対策の遅延(秒)
NOTE_TEXT="サンプル検出結果のため${STATUS}"
NOTE_UPDATED_BY="update-guardduty-samples-script"

if [ "$STATUS" != "RESOLVED" ] && [ "$STATUS" != "SUPPRESSED" ]; then
  echo "Usage: $0 [RESOLVED|SUPPRESSED] [dry-run]"
  exit 1
fi

if [ "$DRY_RUN" != "dry-run" ] && [ ! -z "$DRY_RUN" ]; then
  echo "Usage: $0 [RESOLVED|SUPPRESSED] [dry-run]"
  exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
  echo "Error: jq is required but not installed. Install jq (e.g., apt install jq or brew install jq)."
  exit 1
fi

# 変数初期化
NEXT_TOKEN=""
TOTAL_UPDATED=0
TOTAL_UNPROCESSED=0

while true; do
  # get-findings実行
  if [ -z "$NEXT_TOKEN" ]; then
    RESPONSE=$(aws securityhub get-findings \
      --filters '{"ProductName":[{"Value":"GuardDuty","Comparison":"EQUALS"}],"Sample":[{"Value":true}],"RecordState":[{"Value":"ACTIVE","Comparison":"EQUALS"}],"WorkflowStatus":[{"Value":"NEW","Comparison":"EQUALS"},{"Value":"NOTIFIED","Comparison":"EQUALS"}]}' \
      --max-results $MAX_RESULTS \
      --region $REGION \
      --output json 2>&1)
  else
    RESPONSE=$(aws securityhub get-findings \
      --filters '{"ProductName":[{"Value":"GuardDuty","Comparison":"EQUALS"}],"Sample":[{"Value":true}],"RecordState":[{"Value":"ACTIVE","Comparison":"EQUALS"}],"WorkflowStatus":[{"Value":"NEW","Comparison":"EQUALS"},{"Value":"NOTIFIED","Comparison":"EQUALS"}]}' \
      --max-results $MAX_RESULTS \
      --next-token "$NEXT_TOKEN" \
      --region $REGION \
      --output json 2>&1)
  fi

  if [[ $RESPONSE == *"An error occurred"* ]]; then
    echo "Error in get-findings: $RESPONSE"
    exit 1
  fi

  FINDINGS=$(echo "$RESPONSE" | jq '.Findings')
  NUM_FINDINGS=$(echo "$FINDINGS" | jq 'length')

  if [ "$NUM_FINDINGS" -eq 0 ]; then
    echo "No more findings found."
    break
  fi

  IDENTIFIERS=$(echo "$FINDINGS" | jq '[.[] | {"Id": .Id, "ProductArn": .ProductArn}]')

  if [ "$DRY_RUN" = "dry-run" ]; then
    echo "[DRY RUN] Would update $NUM_FINDINGS findings to $STATUS."
  else
    UPDATE_RESPONSE=$(aws securityhub batch-update-findings \
      --finding-identifiers "$IDENTIFIERS" \
      --workflow '{"Status": "'"$STATUS"'"}' \
      --note '{"Text": "'"$NOTE_TEXT"'", "UpdatedBy": "'"$NOTE_UPDATED_BY"'"}' \
      --region $REGION \
      --output json 2>&1)

    if [[ $UPDATE_RESPONSE == *"An error occurred"* ]]; then
      echo "Error in batch-update-findings: $UPDATE_RESPONSE"
      sleep $SLEEP_INTERVAL
      continue
    fi

    PROCESSED=$(echo "$UPDATE_RESPONSE" | jq '.ProcessedFindings | length')
    UNPROCESSED=$(echo "$UPDATE_RESPONSE" | jq '.UnprocessedFindings | length')
    TOTAL_UPDATED=$((TOTAL_UPDATED + PROCESSED))
    TOTAL_UNPROCESSED=$((TOTAL_UNPROCESSED + UNPROCESSED))
    echo "Updated $PROCESSED findings (Unprocessed: $UNPROCESSED)."
  fi

  NEXT_TOKEN=$(echo "$RESPONSE" | jq -r '.NextToken // empty')
  if [ -z "$NEXT_TOKEN" ]; then
    break
  fi

  sleep $SLEEP_INTERVAL
done

echo "総更新件数: $TOTAL_UPDATED (未処理: $TOTAL_UNPROCESSED)"

スクリプトのポイント

  • ページネーション対応: get-findingsでNextTokenを使い、100件以上の検出結果した場合もループ処理しています
  • dry-runモード: dry-runを指定すると、更新せずに対象件数を確認。
  • エラーハンドリング: AWS CLIのエラーや未処理の検出結果をログに出力。レート制限に引っかかった場合は、SLEEP_INTERVALを調整してください
  • カスタマイズ性: STATUSRESOLVED/SUPPRESSED)やREGIONを変更できるようにしています。

注意:

  • 検出結果が数千件以上ある場合、APIレート制限に注意してください。必要ならSLEEP_INTERVALを増やしてください。

おまけ: SCPでサンプル検出結果の生成を防ぐ

操作ミスなどで、GuardDutyのサンプル検出結果を防ぎたい場合、AWS OrganizationsのService Control Policies (SCPs)を使ってguardduty:CreateSampleFindingsアクションを制限できます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyGuardDutySampleFindings",
      "Effect": "Deny",
      "Action": ["guardduty:CreateSampleFindings"],
      "Resource": ["*"]
    }
  ]
}

まとめ

Security HubのGuardDutyサンプル検出結果を一括処理(抑制、解決済み)に変更できますので、機会があればご利用ください。
また、SCPにより、サンプル検出結果の生成自体を防ぎ誤操作とうを防ぐこともできますので、ご参考ください

参考

https://dev.classmethod.jp/articles/create-a-single-sample-findings-in-guardduty/

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.