SecurityHubのGuardDutyサンプル検出結果をAWS CLI(シェルスクリプト)でまとめて解決/抑制してみた
クラウド事業本部の梶原@シン福岡オフィスです。
GuardDutyのサンプル検出結果をぽちっとすると大量の検出結果が生成されます。ぽちっとすると大量の検出結果が生成されます。(大事なry
2025年8月15日時点で363件(東京リージョン)生成されるます。
1件だけ生成したい場合は、以下をご参考ください
Amazon GuardDutyで1つのサンプルイベントのみ発生させる方法
生成してしまったものは正直しょうがないのですが、AWS Security Hub CSPMの検出結果にも大量に検出され、実際の検出結果と混在されてしまい。見通しがかなり悪くなってしまいます。
そこでAWS Security HubでGuardDutyのサンプル検出結果が、コンソールで1件ずつ(まとめても20件)「解決済み(RESOLVED)」や「抑制済み(SUPPRESSED)」にするのは大変です。ということで、AWS CLIを使ったスクリプトで、ページネーション対応でサンプル検出結果を一括処理する方法を紹介します。
ちなみに、AWSコンソールでサンプル検出結果のみを絞り込む場合は、検出結果の例=Trueで絞り込めます。
ぽちぽちする場合は、こちらでも対応可能です。
おまけとして、最後にService Control Policies (SCPs)を使ってサンプル検出結果の生成自体を防ぐ方法もご紹介します。
尚、スクリプトの生成には生成AIを使用しています。動作検証は十分に行っていますがすべての環境での動作を保証するものではありませんので、ご容赦ください。
前提条件
- AWS CLIがインストール済みで、適切な権限(
securityhub:GetFindings
,securityhub:BatchUpdateFindings
)が設定されていること。 jq
がインストール済み(例:apt install jq
やbrew 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
を調整してください - カスタマイズ性:
STATUS
(RESOLVED
/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により、サンプル検出結果の生成自体を防ぎ誤操作とうを防ぐこともできますので、ご参考ください
参考