Organizations 統合 GuardDuty 有効化前の既存環境影響一括調査
こんにちは!コンサルティング部のくろすけです!
AWS Organizations (以降 Organizations ) 統合の Amazon GuardDuty (以降 GuardDuty ) の有効化の際に、既存の GuardDuty の設定状況を確認したい場合があると思います。
そこで、AWS Config Aggregator (以降 Aggregator ) を活用した効率的な調査方法を検証してみました!
この記事では、Aggregator 機能を使って組織全体の GuardDuty 設定を一括で調査する方法をご紹介します。
目次
概要
調査の背景
Organizations 統合の GuardDuty 有効化において、以下のような課題があるかと思います
- 組織内の全アカウント・リージョンの GuardDuty 設定状況を把握したい
- 各保護機能(S3 Protection、Malware Protection等)の有効化状況を一覧化したい
- 手動での確認は非効率的で、設定状況を一覧で確認したい
料金
現在(2025/07/28)時点で Aggregator 自体には料金がかかりません。
ただし、必ず最新情報をご確認ください。
調査対象と条件
検出対象
- 組織内の全てのアカウントおよびリージョンの GuardDuty(管理アカウント含む)
検出条件
- Organizations 管理下のアカウントであること
- 管理アカウント、メンバーアカウント双方検出可能
- メンバーアカウントは、Control Tower 管理外であること
- 特にリージョン拒否設定の影響を受け、正しくリソースが取得できない可能性有
- Config が有効であること
- Config が GuardDuty Detector リソースを記録していること
出力項目
- AccountId:アカウントID
- AccountName:アカウント名
- Region:リージョン
- RegionStatus:リージョンの有効化状況
- ConfigStatus:Config の有効化状況
- ConfigRecorderGuardDutyStatus:Config の GuardDuty 記録状況
- GuardDutyDetectorId:GuardDuty Detector のリソースID
- GuardDutyStatus:GuardDuty の有効化状況
- GuardDutyCreatedAt:GuardDuty の作成日
- FindingPublishingFrequency:GuardDuty の結果更新頻度
- S3Protection:GuardDuty 保護プラン(S3Protection)の有効化状況
- EKSProtection:GuardDuty 保護プラン(EKSProtection)の有効化状況
- MalwareProtectionForEC2:GuardDuty 保護プラン(MalwareProtectionForEC2)の有効化状況
- RDSProtection:GuardDuty 保護プラン(RDSProtection)の有効化状況
- RuntimeMonitoring:GuardDuty 保護プラン(RuntimeMonitoring)の有効化状況
- LambdaProtection:GuardDuty 保護プラン(LambdaProtection)の有効化状況
- CheckTime:確認日時
やってみた
事前準備
1. AWS Account Management の信頼されたアクセスの有効化(実行アカウント:管理アカウント)
1-1. AWS Account Management の信頼されたアクセスの確認
下記のコマンドを実行し、 ServicePrincipal
にaccount.amazonaws.com
が存在するか確認
aws organizations list-aws-service-access-for-organization --output table
1-2. AWS Account Management の信頼されたアクセスの有効化
aws organizations enable-aws-service-access \
--service-principal account.amazonaws.com
1-3. AWS Account Management の信頼されたアクセスの確認
下記のコマンドを実行し、 ServicePrincipal
にaccount.amazonaws.com
が存在することを確認
aws organizations list-aws-service-access-for-organization --output table
2. AWS Config の委任管理アカウントの登録(実行アカウント:管理アカウント)
2-1. AWS Config の信頼されたアクセスの確認
下記のコマンドを実行し、 ServicePrincipal
にconfig.amazonaws.com
および config-multiaccountsetup.amazonaws.com
が存在するか確認
aws organizations list-aws-service-access-for-organization --output table
2-2. AWS Config の信頼されたアクセスの有効化
ServicePrincipal
にconfig.amazonaws.com
が存在しない場合は下記を実行
aws organizations enable-aws-service-access \
--service-principal=config.amazonaws.com
ServicePrincipal
にconfig-multiaccountsetup.amazonaws.com
が存在しない場合、下記を実行
aws organizations enable-aws-service-access \
--service-principal=config-multiaccountsetup.amazonaws.com
2-3. AWS Account Management の信頼されたアクセスの確認
下記のコマンドを実行し、 ServicePrincipal
にconfig.amazonaws.com
および config-multiaccountsetup.amazonaws.com
が存在することを確認
aws organizations list-aws-service-access-for-organization --output table
2-4. AWS Config の委任管理アカウントの登録
aws organizations register-delegated-administrator \
--service-principal=config.amazonaws.com \
--account-id "${DelegatedAdministratorAccountID}"
aws organizations register-delegated-administrator \
--service-principal=config-multiaccountsetup.amazonaws.com \
--account-id "${DelegatedAdministratorAccountID}"
2-5. AWS Config の委任管理アカウントの登録確認
aws organizations list-delegated-administrators --output table \
--service-principal=config.amazonaws.com
aws organizations list-delegated-administrators --output table \
--service-principal=config-multiaccountsetup.amazonaws.com
3. AWS Config Aggregator 用 IAMロール の作成(実行アカウント:委任管理アカウント)
3-1. IAMロール 作成
aws iam create-role --role-name OrgConfigAggregatorRole \
--assume-role-policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"config.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" \
--description "Role for organizational AWS Config aggregator"
3-2. IAMロール にポリシーをアタッチ
aws iam attach-role-policy \
--role-name OrgConfigAggregatorRole \
--policy-arn "arn:aws:iam::aws:policy/service-role/AWSConfigRoleForOrganizations"
3-3. IAMロール および ポリシー の状況確認
aws iam list-attached-role-policies \
--role-name OrgConfigAggregatorRole
4. AWS Config Aggregator の作成(実行アカウント:委任管理アカウント)
4-1. IAMロール ARN の取得および AWS Config Aggregator の作成
org_config_role_arn=$(aws iam get-role --output text \
--role-name OrgConfigAggregatorRole --query "Role.Arn")
aws configservice put-configuration-aggregator \
--configuration-aggregator-name OrgConfigAggregator \
--organization-aggregation-source "{\"RoleArn\": \"${org_config_role_arn}\",\"AllAwsRegions\": true}"
4-2. AWS Config Aggregator の作成確認
aws configservice describe-configuration-aggregators \
--configuration-aggregator-names OrgConfigAggregator
GuardDutyの設定状況の取得
GuardDuty の有効化および設定状況を取得するスクリプトを下記に記載します。
ただしこのスクリプトで検出可能な GuardDuty は下記になります。
検出スクリプト概要
検出対象
- 組織内の全てのアカウントおよびリージョンの GuardDuty (管理アカウント含む)
検出条件
以下は GuardDuty の設定状況が正確に取得できる場合の条件です。
ただし設定状況が取得できない場合は、出力結果より原因を絞り込むことが可能です。
- Organizations 管理下のアカウントであること
- 管理アカウント、メンバーアカウント双方検出可能
- メンバーアカウントは、Control Tower 管理外であること
- 特にリージョン拒否設定の影響を受け、正しくリソースが取得できない可能性有
- AWS Config が有効であること
- AWS Config が GuardDuty Detector リソースを記録していること
出力項目
- AccountId:アカウントID
- AccountName:アカウント名
- Region:リージョン
- RegionStatus:リージョンの有効化状況
- ConfigStatus:Config の有効化状況
- ConfigRecorderGuardDutyStatus:Config の GuardDuty 記録状況
- GuardDutyDetectorId:GuardDuty Detector のリソースID
- GuardDutyStatus:GuardDuty の有効化状況
- GuardDutyCreatedAt:GuardDuty の作成日
- FindingPublishingFrequency:GuardDuty の結果更新頻度
- S3Protection:GuardDuty 保護プラン(S3Protection)の有効化状況
- EKSProtection:GuardDuty 保護プラン(EKSProtection)の有効化状況
- MalwareProtectionForEC2:GuardDuty 保護プラン(MalwareProtectionForEC2)の有効化状況
- RDSProtection:GuardDuty 保護プラン(RDSProtection)の有効化状況
- RuntimeMonitoring:GuardDuty 保護プラン(RuntimeMonitoring)の有効化状況
- LambdaProtection:GuardDuty 保護プラン(LambdaProtection)の有効化状況
- CheckTime:確認日時
アカウントおよびリージョン情報取得(実行アカウント:管理アカウント)
下記のシェルスクリプトを実行することで、アカウントおよびリージョンの有効化状況を取得
情報はCSVで出力され、CSVを次のシェルスクリプトの入力として使用
#!/bin/bash
# =============================================================================
# 組織内アカウント・リージョン有効化状況確認スクリプト
# =============================================================================
# 説明: 組織内の全アカウント・リージョンの有効化状況を確認してCSVで出力する
# 実行環境: 管理アカウントまたは委任管理アカウントのCloudShell
# 必要権限: Organizations, Account の読み取り権限
# =============================================================================
set -euo pipefail
# 定数定義
readonly SCRIPT_NAME="$(basename "$0")"
readonly OUTPUT_FILE="account_region_status_$(date +%Y%m%d_%H%M%S).csv"
# ログ関数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
error_log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}
# 使用法表示
usage() {
cat << EOF
使用法: $SCRIPT_NAME [OPTIONS]
組織内アカウント・リージョン有効化状況確認スクリプト
OPTIONS:
-h, --help このヘルプを表示
-o, --output FILE 出力ファイル名を指定 (デフォルト: $OUTPUT_FILE)
-r, --regions LIST リージョンリストを指定 (コンマ区切り、デフォルト: 全リージョン)
--dry-run 実際のチェックを行わずに設定を表示
例:
$SCRIPT_NAME
$SCRIPT_NAME -o my_output.csv
$SCRIPT_NAME -r us-east-1,ap-northeast-1
$SCRIPT_NAME --dry-run
EOF
}
# 引数解析
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-r|--regions)
REGIONS="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
error_log "不明なオプション: $1"
usage
exit 1
;;
esac
done
}
# 前提条件チェック
check_prerequisites() {
log "前提条件をチェック中..."
# AWS CLIの確認
if ! command -v aws &> /dev/null; then
error_log "AWS CLI がインストールされていません"
exit 1
fi
# AWS認証情報の確認
if ! aws sts get-caller-identity &> /dev/null; then
error_log "AWS認証情報が設定されていません"
exit 1
fi
# 組織アカウントかどうかの確認
if ! aws organizations describe-organization &> /dev/null; then
error_log "組織の管理アカウントまたは委任管理アカウントで実行してください"
exit 1
fi
log "前提条件チェック完了"
}
# 組織アカウント一覧取得
get_organization_accounts() {
local accounts
accounts=$(aws organizations list-accounts \
--query 'Accounts[?Status==`ACTIVE`].[Id,Name]' \
--output text 2>/dev/null || echo "")
if [[ -z "$accounts" ]]; then
return 1
fi
# 空行を除去してから正確な行数をカウント
accounts=$(echo "$accounts" | sed '/^$/d')
echo "$accounts"
}
# リージョン一覧取得
get_regions() {
if [[ -n "${REGIONS:-}" ]]; then
echo "$REGIONS" | tr ',' '\n' | grep -v '^$'
else
# 全リージョン取得
aws ec2 describe-regions \
--all-regions \
--query 'Regions[].RegionName' \
--output text | tr '\t' '\n' | grep -v '^$' | sort
fi
}
# 管理アカウントID取得
get_management_account_id() {
aws organizations describe-organization \
--query 'Organization.MasterAccountId' \
--output text 2>/dev/null || echo ""
}
# アカウント・リージョンの有効化状況チェック
check_region_opt_status() {
local account_id="$1"
local region="$2"
local status
local management_account_id
management_account_id=$(get_management_account_id)
if [[ "$account_id" == "$management_account_id" ]]; then
# 管理アカウントの場合は--account-idパラメータを省略
status=$(aws account get-region-opt-status \
--region-name "$region" \
--output text 2>/dev/null || echo "ERROR")
else
# 通常のアカウントの場合
status=$(aws account get-region-opt-status \
--account-id "$account_id" \
--region-name "$region" \
--output text 2>/dev/null || echo "ERROR")
fi
if [[ "$status" == "ERROR" ]]; then
echo "CHECK_ERROR"
else
# リージョン名とタブ文字を削除して、ステータスのみを抽出
echo "$status" | awk '{print $NF}'
fi
}
# メイン処理
main() {
local dry_run="${DRY_RUN:-false}"
log "組織内アカウント・リージョン有効化状況確認を開始"
# 前提条件チェック
check_prerequisites
# 組織アカウント一覧取得
log "組織アカウント一覧を取得中..."
local accounts
accounts=$(get_organization_accounts)
if [[ -z "$accounts" ]]; then
error_log "組織アカウントの取得に失敗しました"
exit 1
fi
local account_count=$(echo "$accounts" | sed '/^$/d' | wc -l)
log "組織アカウント取得完了: $account_count アカウント"
# リージョン一覧取得
log "リージョン一覧を取得中..."
local regions
regions=$(get_regions)
if [[ -z "$regions" ]]; then
error_log "リージョン一覧の取得に失敗しました"
exit 1
fi
local region_count=$(echo "$regions" | sed '/^$/d' | wc -l)
log "リージョン一覧取得完了: $region_count リージョン"
# ドライランの場合は設定を表示して終了
if [[ "$dry_run" == "true" ]]; then
log "=== ドライラン実行 ==="
log "対象アカウント数: $account_count"
log "対象リージョン数: $region_count"
log "総チェック数: $((account_count * region_count))"
log "出力ファイル: $OUTPUT_FILE"
log ""
log "=== 対象アカウント ==="
while IFS= read -r account_line; do
account_id=$(echo "$account_line" | cut -f1)
account_name=$(echo "$account_line" | cut -f2-)
log " - $account_id ($account_name)"
done <<< "$accounts"
log ""
log "=== 対象リージョン ==="
while IFS=$'\n' read -r region; do
log " - $region"
done <<< "$regions"
log "=== ドライラン終了 ==="
exit 0
fi
# CSV ヘッダー作成
cat > "$OUTPUT_FILE" << EOF
AccountId,AccountName,Region,RegionStatus,CheckTime
EOF
# 各アカウント・リージョンの組み合わせでチェック実行
local total_checks=$((account_count * region_count))
local completed_checks=0
log "チェック開始: 総チェック数 $total_checks"
while IFS= read -r account_line; do
# タブで分割してアカウントIDと名前を取得
account_id=$(echo "$account_line" | cut -f1)
account_name=$(echo "$account_line" | cut -f2- | sed 's/,/_/g')
while IFS=$'\n' read -r region; do
log "チェック中: $account_id ($account_name) - $region"
# リージョン有効化状況チェック
local region_opt_status
region_opt_status=$(check_region_opt_status "$account_id" "$region")
echo "$account_id,$account_name,$region,$region_opt_status,$(date '+%Y-%m-%d %H:%M:%S')" \
>> "$OUTPUT_FILE"
completed_checks=$((completed_checks + 1))
log "進捗: $completed_checks/$total_checks ($(( completed_checks * 100 / total_checks ))%)"
done <<< "$regions"
done <<< "$accounts"
log "チェック完了: $OUTPUT_FILE"
# 結果サマリー
local total_accounts
local enabled_regions
local disabled_regions
local error_regions
total_accounts=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f1 | sort -u | \
sed '/^$/d' | wc -l)
enabled_regions=$(tail -n +2 "$OUTPUT_FILE" | grep -c "ENABLED")
disabled_regions=$(tail -n +2 "$OUTPUT_FILE" | grep -c "DISABLED")
error_regions=$(tail -n +2 "$OUTPUT_FILE" | grep -c "CHECK_ERROR")
log "=== 結果サマリー ==="
log "対象アカウント数: $total_accounts"
log "リージョン有効化済み: $enabled_regions"
log "リージョン無効化済み: $disabled_regions"
log "チェックエラー: $error_regions"
log "総チェック数: $total_checks"
log "=================="
}
# スクリプト実行
parse_args "$@"
main
GuardDutyの設定状況の取得(実行アカウント:委任管理アカウント)
下記のシェルスクリプトを実行することで、GuardDuty の有効化状況を CSV で出力
#!/bin/bash
# =============================================================================
# GuardDuty Config Aggregator レポート生成スクリプト (CSV入力版)
# =============================================================================
# 説明: リージョン有効化状況CSVを入力として、有効化されたリージョンのみで
# Config AggregatorからGuardDuty状況を確認してCSVで出力する
# 実行環境: 管理アカウントまたは委任管理アカウントのCloudShell
# 必要権限: Organizations, Config の読み取り権限
# =============================================================================
set -euo pipefail
# 定数定義
readonly SCRIPT_NAME="$(basename "$0")"
readonly DEFAULT_INPUT_FILE="account_region_status.csv"
readonly OUTPUT_FILE="guardduty_config_aggregator_merged_$(date +%Y%m%d_%H%M%S).csv"
readonly TEMP_DIR="/tmp/guardduty_report_$$"
# ログ関数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}
error_log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}
# CSV出力処理の統一
write_csv_record() {
local account_id="$1"
local account_name="$2"
local region="$3"
local region_status="$4"
local config_status="${5:--}"
local config_recorder_guardduty_status="${6:--}"
local guardduty_detector_id="${7:--}"
local guardduty_status="${8:--}"
local guardduty_created_at="${9:--}"
local finding_frequency="${10:--}"
local s3_protection="${11:--}"
local eks_protection="${12:--}"
local malware_protection="${13:--}"
local rds_protection="${14:--}"
local runtime_monitoring="${15:--}"
local lambda_protection="${16:--}"
local check_time="${17:-$(date '+%Y-%m-%d %H:%M:%S')}"
echo "$account_id,$account_name,$region,$region_status,$config_status,$config_recorder_guardduty_status,$guardduty_detector_id,$guardduty_status,$guardduty_created_at,$finding_frequency,$s3_protection,$eks_protection,$malware_protection,$rds_protection,$runtime_monitoring,$lambda_protection,$check_time" >> "$OUTPUT_FILE"
}
# JSON解析処理の統一
parse_configuration_json() {
local config_item="$1"
local field_path="$2" # 例: ".configuration" または ".RecordingGroup.AllSupported"
local config_json_raw
config_json_raw=$(echo "$config_item" | jq "$field_path" 2>/dev/null)
local config_type
config_type=$(echo "$config_json_raw" | jq -r 'type' 2>/dev/null)
case "$config_type" in
"object")
echo "$config_json_raw"
;;
"string")
local config_str
config_str=$(echo "$config_item" | jq -r "$field_path" 2>/dev/null)
if [[ -n "$config_str" ]] && [[ "$config_str" != "null" ]]; then
echo "$config_str" | jq . 2>/dev/null || echo "null"
else
echo "null"
fi
;;
*)
echo "null"
;;
esac
}
# 機能状態抽出のヘルパー関数
extract_feature_status() {
local config_json="$1"
local feature_name="$2"
echo "$config_json" | jq -r ".Features[]? | select(.Name==\"$feature_name\") | .Status // \"DISABLED\"" 2>/dev/null || echo "DISABLED"
}
# GuardDuty設定解析をより明確に
analyze_guardduty_configuration() {
local config_json="$1"
local detector_id="$2"
local created_at="$3"
# Enable状態の判定
local enable_status
enable_status=$(echo "$config_json" | jq -r '.Enable' 2>/dev/null)
local guardduty_status
case "$enable_status" in
"false") guardduty_status="SUSPENDED" ;;
"true") guardduty_status="ENABLED" ;;
"null"|"") guardduty_status="ENABLED" ;; # デフォルト
*) guardduty_status="UNKNOWN" ;; # フェールセーフ
esac
# 保護機能の解析
local s3_protection eks_protection malware_protection rds_protection
local runtime_monitoring lambda_protection finding_frequency
s3_protection=$(extract_feature_status "$config_json" "S3_DATA_EVENTS")
eks_protection=$(extract_feature_status "$config_json" "EKS_AUDIT_LOGS")
malware_protection=$(extract_feature_status "$config_json" "EBS_MALWARE_PROTECTION")
rds_protection=$(extract_feature_status "$config_json" "RDS_LOGIN_EVENTS")
lambda_protection=$(extract_feature_status "$config_json" "LAMBDA_NETWORK_LOGS")
finding_frequency=$(echo "$config_json" | jq -r '.FindingPublishingFrequency // "-"')
# EKS_RUNTIME_MONITORING または RUNTIME_MONITORING のどちらかが ENABLED の場合、RuntimeMonitoring を ENABLED とする
local eks_runtime_status runtime_monitoring_status
eks_runtime_status=$(extract_feature_status "$config_json" "EKS_RUNTIME_MONITORING")
runtime_monitoring_status=$(extract_feature_status "$config_json" "RUNTIME_MONITORING")
if [[ "$eks_runtime_status" == "ENABLED" || "$runtime_monitoring_status" == "ENABLED" ]]; then
runtime_monitoring="ENABLED"
else
runtime_monitoring="DISABLED"
fi
# 日付フォーマット変換
if [[ "$created_at" != "" && "$created_at" != "null" ]]; then
created_at=$(date -d "@$created_at" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$created_at")
else
created_at="-"
fi
echo "$detector_id,$guardduty_status,$created_at,$finding_frequency,$s3_protection,$eks_protection,$malware_protection,$rds_protection,$runtime_monitoring,$lambda_protection"
}
# 統一されたエラーハンドリング
handle_aws_api_error() {
local operation="$1"
local error_message="$2"
case "$operation" in
"config_aggregator")
error_log "Config Aggregatorからの情報取得に失敗: $error_message"
return 1
;;
"guardduty_config")
log "GuardDuty設定の取得に失敗、ERRORレコードを出力"
echo "ERROR,ERROR,ERROR,ERROR,ERROR,ERROR,ERROR,ERROR,ERROR,ERROR"
return 0
;;
*)
error_log "不明なエラー: $error_message"
return 1
;;
esac
}
# 使用法表示
usage() {
cat << EOF
使用法: $SCRIPT_NAME [OPTIONS]
GuardDuty Config Aggregator レポート生成スクリプト (CSV入力版)
OPTIONS:
-h, --help このヘルプを表示
-i, --input FILE 入力CSVファイルを指定 (デフォルト: $DEFAULT_INPUT_FILE)
-a, --aggregator NAME 使用するConfig Aggregator名を指定
-o, --output FILE 出力ファイル名を指定 (デフォルト: $OUTPUT_FILE)
--list-aggregators 利用可能なConfig Aggregatorを表示
--dry-run 実際のチェックを行わずに設定を表示
例:
$SCRIPT_NAME --list-aggregators
$SCRIPT_NAME -i my_input.csv -a my-config-aggregator
$SCRIPT_NAME -i my_input.csv -a my-config-aggregator -o my_output.csv
$SCRIPT_NAME -i my_input.csv -a my-config-aggregator --dry-run
注意:
Config Aggregatorの指定は必須です。
利用可能なAggregatorを確認するには --list-aggregators を使用してください。
入力CSVファイルのフォーマット:
AccountId,AccountName,Region,RegionStatus,CheckTime
RegionStatus が ENABLED, ENABLING, ENABLED_BY_DEFAULT の場合のみ
Config および GuardDuty の有効状況をチェックします。
EOF
}
# 引数解析
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-i|--input)
INPUT_FILE="$2"
shift 2
;;
-a|--aggregator)
AGGREGATOR_NAME="$2"
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
--list-aggregators)
LIST_AGGREGATORS=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
error_log "不明なオプション: $1"
usage
exit 1
;;
esac
done
# 入力ファイルのデフォルト設定
if [[ -z "${INPUT_FILE:-}" ]]; then
INPUT_FILE="$DEFAULT_INPUT_FILE"
fi
# アグリゲーターの指定が必須(--list-aggregators以外の場合)
if [[ "${LIST_AGGREGATORS:-false}" == "false" ]] && [[ -z "${AGGREGATOR_NAME:-}" ]]; then
error_log "Config Aggregatorの指定が必要です"
error_log ""
error_log "使用方法: $SCRIPT_NAME -i input.csv -a <AGGREGATOR_NAME>"
error_log "例: $SCRIPT_NAME -i $DEFAULT_INPUT_FILE -a my-config-aggregator"
error_log ""
error_log "利用可能なAggregatorを確認するには: $SCRIPT_NAME --list-aggregators"
exit 1
fi
}
# 前提条件チェック
check_prerequisites() {
log "前提条件をチェック中..."
# AWS CLIの確認
if ! command -v aws &> /dev/null; then
error_log "AWS CLI がインストールされていません"
exit 1
fi
# jqの確認
if ! command -v jq &> /dev/null; then
error_log "jq がインストールされていません"
exit 1
fi
# AWS認証情報の確認
if ! aws sts get-caller-identity &> /dev/null; then
error_log "AWS認証情報が設定されていません"
exit 1
fi
# 組織アカウントかどうかの確認
if ! aws organizations describe-organization &> /dev/null; then
error_log "組織の管理アカウントまたは委任管理アカウントで実行してください"
exit 1
fi
# 入力ファイルの存在確認
if [[ "${LIST_AGGREGATORS:-false}" == "false" ]] && [[ ! -f "$INPUT_FILE" ]]; then
error_log "入力ファイルが見つかりません: $INPUT_FILE"
exit 1
fi
# 一時ディレクトリ作成
mkdir -p "$TEMP_DIR"
log "前提条件チェック完了"
}
# 利用可能なConfig Aggregatorを表示
list_config_aggregators() {
local aggregators_detail
aggregators_detail=$(aws configservice describe-configuration-aggregators \
--query 'ConfigurationAggregators[].[ConfigurationAggregatorName,OrganizationAggregationSource.AllAwsRegions]' \
--output table 2>/dev/null || echo "")
if [[ -z "$aggregators_detail" ]]; then
error_log "Config Aggregatorが見つかりません"
return 1
fi
echo "=== 利用可能なConfig Aggregator ==="
echo "$aggregators_detail"
echo "=================================="
echo ""
echo "使用方法: $SCRIPT_NAME -i input.csv -a <AGGREGATOR_NAME>"
echo "例: $SCRIPT_NAME -i $DEFAULT_INPUT_FILE -a my-config-aggregator"
}
# 入力CSVファイルの読み込みと検証
load_input_data() {
# ヘッダー行の確認
local header
header=$(head -n 1 "$INPUT_FILE")
if [[ "$header" != "AccountId,AccountName,Region,RegionStatus,CheckTime" ]]; then
error_log "入力CSVファイルのヘッダーが不正です"
error_log "期待値: AccountId,AccountName,Region,RegionStatus,CheckTime"
error_log "実際値: $header"
exit 1
fi
# データ行数の確認
local data_lines
data_lines=$(tail -n +2 "$INPUT_FILE" | wc -l)
if [[ $data_lines -eq 0 ]]; then
error_log "入力CSVファイルにデータがありません"
exit 1
fi
}
# 有効化されているリージョンのみを抽出
get_enabled_regions_data() {
# ENABLED, ENABLING, ENABLED_BY_DEFAULT の行のみを抽出
local enabled_data
enabled_data=$(tail -n +2 "$INPUT_FILE" | \
grep -E "(ENABLED|ENABLING|ENABLED_BY_DEFAULT)" || echo "")
if [[ -z "$enabled_data" ]]; then
error_log "有効化されているリージョンが見つかりません"
exit 1
fi
echo "$enabled_data"
}
# [削除] 使用されなくなった関数を削除してコードを整理しました
# 統一版: 全アカウント・リージョンのConfig情報を一括取得
get_all_config_data_unified() {
local aggregator_name="$1"
# 全ConfigurationRecorderの情報を取得(ResourceIdと設定詳細も含めて)
local config_recorders
if ! config_recorders=$(aws configservice list-aggregate-discovered-resources \
--configuration-aggregator-name "$aggregator_name" \
--resource-type "AWS::Config::ConfigurationRecorder" \
--query 'ResourceIdentifiers[].[SourceAccountId,SourceRegion,ResourceId]' \
--output text 2>/dev/null); then
config_recorders=""
fi
# 全GuardDuty Detectorの情報を取得(ResourceIdも含めて)
local guardduty_resources
if ! guardduty_resources=$(aws configservice list-aggregate-discovered-resources \
--configuration-aggregator-name "$aggregator_name" \
--resource-type "AWS::GuardDuty::Detector" \
--query 'ResourceIdentifiers[].[SourceAccountId,SourceRegion,ResourceId]' \
--output text 2>/dev/null); then
guardduty_resources=""
fi
# 結果を連想配列に保存
# 既存の連想配列をクリア
unset guardduty_status_map 2>/dev/null || true
unset guardduty_resource_map 2>/dev/null || true
unset config_recorder_map 2>/dev/null || true
unset config_recorder_guardduty_map 2>/dev/null || true
# 新しい連想配列を宣言
declare -gA guardduty_status_map
declare -gA guardduty_resource_map
declare -gA config_recorder_map
declare -gA config_recorder_guardduty_map
# GuardDuty Detectorの情報を処理
local guardduty_count=0
if [[ -n "$guardduty_resources" ]]; then
while IFS=$'\t' read -r account_id region resource_id; do
if [[ -n "$account_id" && -n "$region" && -n "$resource_id" ]]; then
local key="${account_id}:${region}"
guardduty_status_map["$key"]="true"
guardduty_resource_map["$key"]="$resource_id"
guardduty_count=$((guardduty_count + 1))
fi
done <<< "$guardduty_resources"
fi
# ConfigurationRecorderの情報を処理
local config_recorder_count=0
if [[ -n "$config_recorders" ]]; then
while IFS=$'\t' read -r account_id region resource_id; do
if [[ -n "$account_id" && -n "$region" && -n "$resource_id" ]]; then
local key="${account_id}:${region}"
config_recorder_map["$key"]="$resource_id"
# ConfigurationRecorderの詳細設定を取得してGuardDuty記録対象かを判定
local guardduty_recording_status
guardduty_recording_status=$(get_config_recorder_guardduty_setting_details "$aggregator_name" "$account_id" "$region" "$resource_id")
config_recorder_guardduty_map["$key"]="$guardduty_recording_status"
config_recorder_count=$((config_recorder_count + 1))
fi
done <<< "$config_recorders"
fi
}
# ConfigurationRecorderの詳細設定を取得してGuardDuty記録対象かを判定(リファクタリング版)
get_config_recorder_guardduty_setting_details() {
local aggregator_name="$1"
local account_id="$2"
local region="$3"
local recorder_name="$4"
# ConfigurationRecorderの詳細設定を取得
local config_response
config_response=$(aws configservice get-aggregate-resource-config \
--configuration-aggregator-name "$aggregator_name" \
--resource-identifier ResourceType="AWS::Config::ConfigurationRecorder",ResourceId="$recorder_name",SourceAccountId="$account_id",SourceRegion="$region" \
2>/dev/null || echo "")
if [[ -z "$config_response" ]]; then
echo "UNKNOWN"
return
fi
# RecordingGroupの設定を解析
local config_item
config_item=$(echo "$config_response" | jq -r '.ConfigurationItem')
# JSON設定解析の共通処理を使用
local configuration
configuration=$(parse_configuration_json "$config_item" ".configuration")
if [[ "$configuration" == "null" ]]; then
echo "UNKNOWN"
return
fi
# AllSupportedの確認
local all_supported
all_supported=$(echo "$configuration" | jq -r '.RecordingGroup.AllSupported // false')
if [[ "$all_supported" == "true" ]]; then
echo "ENABLED"
return
fi
# ResourceTypesでGuardDutyが含まれているかを確認
local has_guardduty
has_guardduty=$(echo "$configuration" | jq -r '.RecordingGroup.ResourceTypes[]? // empty' | grep -c "AWS::GuardDuty::Detector" 2>/dev/null || echo "0")
# 改行を除去
has_guardduty=$(echo "$has_guardduty" | tr -d '\n')
if [[ "$has_guardduty" -gt 0 ]]; then
echo "ENABLED"
else
echo "DISABLED"
fi
}
# 連想配列からConfigurationRecorderのGuardDuty記録設定を取得
check_config_recorder_guardduty_setting() {
local account_id="$1"
local region="$2"
local key="${account_id}:${region}"
# 連想配列から事前に取得済みの情報を返す
echo "${config_recorder_guardduty_map[$key]:-DISABLED}"
}
# 統一版: 保存済みデータからGuardDuty ResourceIdを取得
get_guardduty_resource_id() {
local account_id="$1"
local region="$2"
local key="${account_id}:${region}"
echo "${guardduty_resource_map[$key]:-}"
}
# 統一版: 保存済みデータからConfig状況を確認(ConfigurationRecorderの存在で判定)
get_config_status_unified() {
local account_id="$1"
local region="$2"
local key="${account_id}:${region}"
# ConfigurationRecorderが存在する場合、Configが有効と判定
if [[ -n "${config_recorder_map[$key]:-}" ]]; then
echo "ENABLED"
else
echo "DISABLED"
fi
}
# 統一版: 保存済みデータからGuardDuty Detector状況を確認
get_guardduty_detector_status_unified() {
local account_id="$1"
local region="$2"
local key="${account_id}:${region}"
if [[ "${guardduty_status_map[$key]:-}" == "true" ]]; then
echo "true"
else
echo "false"
fi
}
# GuardDuty設定詳細情報を取得(リファクタリング版)
get_guardduty_config_details_refactored() {
local aggregator_name="$1"
local account_id="$2"
local region="$3"
local detector_id="$4"
# Config Aggregatorから詳細設定を取得
local config_response
config_response=$(aws configservice get-aggregate-resource-config \
--configuration-aggregator-name "$aggregator_name" \
--resource-identifier ResourceType="AWS::GuardDuty::Detector",ResourceId="$detector_id",SourceAccountId="$account_id",SourceRegion="$region" \
2>/dev/null || echo "")
if [[ -z "$config_response" ]]; then
handle_aws_api_error "guardduty_config" "Config Aggregatorから情報を取得できませんでした"
return
fi
# 設定情報を解析
local config_item
config_item=$(echo "$config_response" | jq -r '.ConfigurationItem')
# JSON設定解析の共通処理を使用
local config_json
config_json=$(parse_configuration_json "$config_item" ".configuration")
if [[ "$config_json" == "null" ]]; then
handle_aws_api_error "guardduty_config" "configuration情報の解析に失敗"
return
fi
# configurationItemStatusを使用してリソースの状態を判定
local config_item_status
config_item_status=$(echo "$config_item" | jq -r '.configurationItemStatus // ""')
# リソース状態に応じて処理を分岐
case "$config_item_status" in
"OK"|"ResourceDiscovered")
local created_at
created_at=$(echo "$config_item" | jq -r '.resourceCreationTime // ""' | tr -d '"')
analyze_guardduty_configuration "$config_json" "$detector_id" "$created_at"
;;
"ResourceDeleted")
echo "$detector_id,DELETED,-,-,-,-,-,-,-,-"
;;
"ResourceNotRecorded"|"ResourceDeletedNotRecorded")
echo "$detector_id,UNKNOWN,-,-,-,-,-,-,-,-"
;;
*)
echo "$detector_id,UNKNOWN,-,-,-,-,-,-,-,-"
;;
esac
}
# Config Aggregator検証を独立した関数に
validate_config_aggregator() {
local aggregator_name="$1"
if ! aws configservice describe-configuration-aggregators \
--configuration-aggregator-names "$aggregator_name" \
--query 'ConfigurationAggregators[0].ConfigurationAggregatorName' \
--output text &> /dev/null; then
error_log "指定されたConfig Aggregator '$aggregator_name' が見つかりません"
error_log "利用可能なAggregatorを確認するには: $SCRIPT_NAME --list-aggregators"
return 1
fi
return 0
}
# CSV ヘッダー作成
create_csv_header() {
cat > "$OUTPUT_FILE" << EOF
AccountId,AccountName,Region,RegionStatus,ConfigStatus,ConfigRecorderGuardDutyStatus,GuardDutyDetectorId,GuardDutyStatus,GuardDutyCreatedAt,FindingPublishingFrequency,S3Protection,EKSProtection,MalwareProtectionForEC2,RDSProtection,RuntimeMonitoring,LambdaProtection,CheckTime
EOF
}
# 各行処理を独立した関数に
process_account_region() {
local account_id="$1"
local account_name="$2"
local region="$3"
local region_status="$4"
local check_time="$5"
local aggregator_name="$6"
if [[ "$region_status" =~ ^(ENABLED|ENABLING|ENABLED_BY_DEFAULT)$ ]]; then
process_enabled_region "$account_id" "$account_name" "$region" "$region_status" "$aggregator_name"
else
write_csv_record "$account_id" "$account_name" "$region" "$region_status" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "$check_time"
fi
}
# 有効化リージョンの処理
process_enabled_region() {
local account_id="$1"
local account_name="$2"
local region="$3"
local region_status="$4"
local aggregator_name="$5"
# 正しい判定順序での評価:
# 1. Config が有効化されているかチェック
local config_status_simple
config_status_simple=$(get_config_status_unified "$account_id" "$region")
# 2. GuardDuty のリソース記録が有効であるかチェック
local config_recorder_guardduty_status
if [[ "$config_status_simple" == "ENABLED" ]]; then
config_recorder_guardduty_status=$(check_config_recorder_guardduty_setting "$account_id" "$region")
else
config_recorder_guardduty_status="DISABLED"
fi
# 3. GuardDuty が有効かチェック
local guardduty_detector_recorded
guardduty_detector_recorded=$(get_guardduty_detector_status_unified "$account_id" "$region")
# ConfigRecorderでGuardDutyが記録対象外の場合は、詳細情報を取得しない
if [[ "$config_recorder_guardduty_status" == "DISABLED" ]]; then
write_csv_record "$account_id" "$account_name" "$region" "$region_status" "$config_status_simple" "$config_recorder_guardduty_status"
elif [[ "$guardduty_detector_recorded" == "true" ]]; then
process_guardduty_detected "$account_id" "$account_name" "$region" "$region_status" "$config_status_simple" "$config_recorder_guardduty_status" "$aggregator_name"
else
write_csv_record "$account_id" "$account_name" "$region" "$region_status" "$config_status_simple" "$config_recorder_guardduty_status" "DISABLED"
fi
}
# GuardDuty検出時の処理
process_guardduty_detected() {
local account_id="$1"
local account_name="$2"
local region="$3"
local region_status="$4"
local config_status_simple="$5"
local config_recorder_guardduty_status="$6"
local aggregator_name="$7"
local resource_id
resource_id=$(get_guardduty_resource_id "$account_id" "$region")
if [[ -n "$resource_id" ]]; then
local guardduty_details
guardduty_details=$(get_guardduty_config_details_refactored "$aggregator_name" "$account_id" "$region" "$resource_id")
# guardduty_detailsは既にカンマ区切り形式なので、元の書き方を保持
echo "$account_id,$account_name,$region,$region_status,$config_status_simple,$config_recorder_guardduty_status,$guardduty_details,$(date '+%Y-%m-%d %H:%M:%S')" >> "$OUTPUT_FILE"
else
write_csv_record "$account_id" "$account_name" "$region" "$region_status" "$config_status_simple" "$config_recorder_guardduty_status" "-" "ENABLED"
fi
}
# データ処理のメイン関数
process_input_csv() {
local aggregator_name="$1"
local total_lines
total_lines=$(tail -n +2 "$INPUT_FILE" | wc -l)
local processed_lines=0
# 入力CSVの各行を処理
while IFS=',' read -r account_id account_name region region_opt_status check_time; do
processed_lines=$((processed_lines + 1))
# 空行をスキップ
if [[ -z "$account_id" ]]; then
continue
fi
process_account_region "$account_id" "$account_name" "$region" "$region_opt_status" "$check_time" "$aggregator_name"
done < <(tail -n +2 "$INPUT_FILE")
}
# レポートサマリー表示
show_report_summary() {
local total_accounts enabled_checked disabled_skipped config_configured
local guardduty_found guardduty_enabled s3_protection_enabled malware_protection_enabled
total_accounts=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f1 | sort -u | wc -l)
enabled_checked=$(tail -n +2 "$OUTPUT_FILE" | grep -c -E "(ENABLED|ENABLING|ENABLED_BY_DEFAULT)")
disabled_skipped=$(tail -n +2 "$OUTPUT_FILE" | grep -c -E "(DISABLED|DISABLING)")
config_configured=$(tail -n +2 "$OUTPUT_FILE" | grep -c "ENABLE")
guardduty_found=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f6 | grep -c -v -E "(DISABLED|-)")
guardduty_enabled=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f7 | grep -c "ENABLED")
s3_protection_enabled=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f11 | grep -c "ENABLED")
malware_protection_enabled=$(tail -n +2 "$OUTPUT_FILE" | cut -d',' -f13 | grep -c "ENABLED")
log "=== 結果サマリー ==="
log "対象アカウント数: $total_accounts"
log "有効化リージョン(チェック済み): $enabled_checked"
log "無効化リージョン(スキップ): $disabled_skipped"
log "Config設定済み: $config_configured"
log "GuardDuty Detector検出: $guardduty_found"
log "GuardDuty Detector有効化済み: $guardduty_enabled"
log "S3 Protection有効化済み: $s3_protection_enabled"
log "Malware Protection有効化済み: $malware_protection_enabled"
log "=================="
}
# レポート生成処理を独立した関数に
generate_guardduty_report() {
local aggregator_name="$1"
# CSV ヘッダー作成
create_csv_header
# データ処理
process_input_csv "$aggregator_name"
log "処理完了: $OUTPUT_FILE"
# サマリー表示
show_report_summary
}
# メイン処理
main() {
local dry_run="${DRY_RUN:-false}"
local list_aggregators="${LIST_AGGREGATORS:-false}"
log "GuardDuty Config Aggregator レポート生成を開始 (CSV入力版)"
# 前提条件チェック
check_prerequisites
# Aggregatorリスト表示の場合
if [[ "$list_aggregators" == "true" ]]; then
list_config_aggregators
exit 0
fi
# Config Aggregatorの確認
if ! validate_config_aggregator "$AGGREGATOR_NAME"; then
exit 1
fi
local aggregator_name="$AGGREGATOR_NAME"
# 入力CSVファイルの読み込み
load_input_data
# 有効化されているリージョンのデータを取得
local enabled_regions_data
enabled_regions_data=$(get_enabled_regions_data)
# Config AggregatorからConfigおよびGuardDutyリソースを取得
get_all_config_data_unified "$aggregator_name"
# 有効化されているリージョンの統計
local enabled_count
enabled_count=$(echo "$enabled_regions_data" | wc -l)
# 無効化されているリージョンの統計
local disabled_count
disabled_count=$(tail -n +2 "$INPUT_FILE" | \
grep -c -E "(DISABLED|DISABLING)" || echo "0")
# ドライランの場合は設定を表示して終了
if [[ "$dry_run" == "true" ]]; then
log "=== ドライラン実行 ==="
log "入力ファイル: $INPUT_FILE"
log "Config Aggregator: $aggregator_name"
log "有効化リージョン数: $enabled_count"
log "無効化リージョン数: $disabled_count"
log "出力ファイル: $OUTPUT_FILE"
log "=== ドライラン終了 ==="
exit 0
fi
# レポート生成処理
generate_guardduty_report "$aggregator_name"
# 一時ディレクトリクリーンアップ
rm -rf "$TEMP_DIR"
}
# スクリプト実行
parse_args "$@"
main
出力結果
下記はCSV出力をスプレッドシートにインポートした結果です。
数が多いため、リージョンを絞っています。
ただし、Config の設定状況により全ての情報が取得できるわけではありません。
特に下記のアカウント・リージョンについては、別途確認を行うことをおすすめします。
- ConfigStatus が DISABLED:Config が有効化されていないため、GuardDuty の情報が未取得
- ConfigRecorderGuardDutyStatus が DISABLED:Config にて GuardDuty を記録していないため、GuardDuty の情報が未取得
後片付け
1. AWS Config Aggregator の削除(実行アカウント:委任管理アカウント)
1-1. AWS Config Aggregator の削除
aws configservice delete-configuration-aggregator \
--configuration-aggregator-name OrgConfigAggregator
1-2. AWS Config Aggregator の削除確認
aws configservice describe-configuration-aggregators \
--configuration-aggregator-names OrgConfigAggregator
2. AWS Config Aggregator 用 IAMロール の削除(実行アカウント:委任管理アカウント)
2-1. IAMロール 削除
aws iam delete-role --role-name OrgConfigAggregatorRole
2-2. IAMロール の削除確認
aws iam get-role --role-name OrgConfigAggregatorRole
3. AWS Config の委任管理アカウントの解除(実行アカウント:管理アカウント)
3-1. AWS Config の委任管理アカウントの解除
aws organizations deregister-delegated-administrator \
--service-principal=config.amazonaws.com \
--account-id "${DelegatedAdministratorAccountID}"
aws organizations deregister-delegated-administrator \
--service-principal=config-multiaccountsetup.amazonaws.com \
--account-id "${DelegatedAdministratorAccountID}"
3-2. AWS Config の委任管理アカウントの解除確認
aws organizations list-delegated-administrators --output table \
--service-principal=config.amazonaws.com
aws organizations list-delegated-administrators --output table \
--service-principal=config-multiaccountsetup.amazonaws.com
3-3. AWS Config の信頼されたアクセスの無効化
# config-multiaccountsetup.amazonaws.com の無効化
aws organizations disable-aws-service-access \
--service-principal=config-multiaccountsetup.amazonaws.com
# config.amazonaws.com の無効化
aws organizations disable-aws-service-access \
--service-principal=config.amazonaws.com
3-4. AWS Config の信頼されたアクセスの確認
aws organizations list-aws-service-access-for-organization --output table
4. AWS Account Management の信頼されたアクセスの無効化(実行アカウント:管理アカウント)
4-1. AWS Account Management の信頼されたアクセスの無効化
aws organizations disable-aws-service-access \
--service-principal account.amazonaws.com
4-2. AWS Account Management の信頼されたアクセスの確認
aws organizations list-aws-service-access-for-organization --output table
あとがき
今回、Aggregator を活用した GuardDuty 設定状況の一括調査方法を検証しました。
今回作成したスクリプトで、従来の確認作業を大幅に効率化できたかと思います。
またCSV形式での出力により、Excel等での詳細分析や可視化も容易にできました。
以上、くろすけでした!