🦄

実践:AWS Security Hub & Amazon GuardDuty ~私有マルチアカウント環境の統制を最適化する~

に公開

はじめに

前回はAWS Control Towerを用いて、マルチアカウント環境を構築しました。今回はその環境にAWSのセキュリティサービスを適用し、脆弱性や脅威を検出する体制をつくりたいと思います。

実践内容

  • AWS Security Hub, Amazon GuardDutyをマルチアカウント環境に適用する
  • この時、すべてのアカウント/リージョンに適用するのではなく、リソース数・コストを最低限に抑えるシンプルな構成を検討・実現する
  • 脆弱性・脅威を検出した際は管理者にメールで通知する仕組みを導入する

アーキテクチャ

設計思想

  • 利用可能なアカウント・リージョンすべての監視を行いつつコストを抑える
  • 無駄なリソースを作成しない
  • セキュリティ関連サービスはすべてAuditアカウントで管理する(委任する)

アーキ図

最終的な構成です。

Organizationsで有効化しているサービスは以下になります(煩雑になるためアーキ図では一部省いています)

  • Amazon GuardDuty
  • AWS Control Tower
  • AWS IAM Identity Center (AWS Single Sign-On)
  • CloudFormation StackSets
  • CloudTrail
  • Config
  • Security Hub

ランディングゾーン

管理対象リージョンは以下の4リージョンで、それ以外は拒否リージョンに設定しています。

  • 米国東部 (オハイオ) us-east-2
  • 米国東部 (バージニア北部) us-east-1
  • アジアパシフィック (東京) ap-northeast-1
  • 米国西部 (オレゴン) us-west-2

AWSアカウントではデフォルトで17リージョンが有効になっています。その内4つをホームリージョンを含む管理対象リージョンとしました。その他の13のリージョンが拒否リージョンです。
Control Towerでマルチアカウント環境を構築済の状態で以降の操作を進めます。

1. AWS Security Hub

用語解説

  • セキュリティ標準
    • コントロールの集合
    • AWSのセキュリティベストプラクティスをまとめたセキュリティ標準、CIS(Center for Internet)、NIST(National Institute of Standards and Technology,米国立標準技術研究所)、PCI SSC(Payment Card Industry Security Standards Council)が定めた規格に準拠するためのセキュリティ標準など、様々なセキュリティ標準がサポートされている
  • コントロール
    • セキュリティ標準に紐づく個々のセキュリティチェック項目
  • 検出結果(Findings)
    • あるコントロールに基づいてAWSリソースを評価した結果、問題がある、もしくは評価に失敗した場合に生成される検知情報

ステータス

ステータスが色々あってややこしいので整理します。

  • コントロールステータス
    • コントロールに紐づくステータス
    • あるコントロールで評価された全ての結果(コンプライアンスステータス)をまとめて評価した結果
    • PASSED/FAILED/UNKNOWN/NO DATA/DISABLEDのいずれかの値になる
    • e.g. コントロール[Config.1]はすべての検出結果のコンプライアンスステータスが「PASSED」の時、コントロールステータスが「PASSED」になる
  • コンプライアンスステータス
    • 検出結果に紐づくステータス
    • AWSリソースを評価した結果
    • PASSED/WARNING/FAILED/NOT/_AVAILABLEのいずれかの値になる
  • ワークフローステータス
    • 検出結果に紐づくステータス
    • 検出結果に対する調査状況を表す
    • NEW/NOTIFIED/SUPPRESSED/RESOLVEDのいずれかの値になる
    • ユーザによる変更が可能

Security Hub有効化・委任

まず、Control Tower管理アカウントのOrganizationsからSecurity Hubを有効化します。
委任された管理者アカウントにAuditアカウントを設定しましたが、AuditアカウントのマネコンではSecurity Hubが有効化されませんでした。一晩経っても変化がないので、中央設定から委任の設定を実施しました。すると、AuditアカウントのSecurity Hubが委任された管理者として有効化されました。

Security Hubのリージョン設定

当初のリージョン設定はこちらです。

  • Security Hubの「リンクされたリージョン」にすべての利用可能リージョン(31)を選択
  • Control Towerの管理対象リージョンは4リージョンのみ選択

Security Hubのコントロール評価を行うConfigは管理対象リージョンでのみ有効化・ルールのデプロイが行われるため、拒否リージョンにこれらのリソースはデプロイされず、無駄な課金は発生しません。ただし、拒否リージョンでConfigが有効化されないため、すべてのメンバーアカウントでコントロール「Config.1 AWS Config should be enabled and use the service-linked role for resource recording」のコンプライアンスステータスがFAILEDになってしまいます。
よって、リンクされたリージョンを管理対象リージョンと同じ4リージョンに絞ることにしました。しかし、ここで別の問題が発生します。メンバーアカウントの管理対象リージョン以外のリージョンはSCPによって利用不可になっていますが、管理アカウントにはSCPが効きません。普段使わないリージョンなので何かしらの制約を設けるか、監視したいですよね。そこで、GuardDutyを使って管理アカウントの全リージョンを監視することにしました。詳しい設定内容は次節で解説します。

それでは、リンクされたリージョンを編集します。

  1. Security Hub>設定>リージョン>編集>利用可能なリージョンにおいて、管理対象リージョンにチェックを付け、拒否リージョンのチェックは外して保存します。

ここでControl Towerを見ると、いつの間にかコンプライアンスステータスが不明になっているアカウントを発見しました(今回の内容とは関係ありません)。

アカウントの詳細を確認します。

アカウントの登録に失敗しました。
次の理由により、AWS Control Tower はアカウントを登録できませんでした: AWS Control Tower could not baseline VPC in the enrolled account because of existing resource dependencies.

デフォルトVPCが存在しているため、Service Catalogがアカウントを更新できないようです。
Account Factoryから払いだされたアカウントにはデフォルトVPCは存在しませんが。。。そういえば、Bedrockのハンズオンをした時に作成したような。デフォルトVPCの他にも作成したリソースがあったので、全て削除した後、アカウントの更新を行うと登録に成功しました。

管理アカウントのSecurity Hub

Security HubとGuardDutyで監視するアカウントを分担する方針となったので、管理アカウントのSecurity Hubは無効化します(シンプルな構成にするために)。

  • メンバーアカウントで利用可能なリージョンはSecurity Hubで監視すること(リンクされたリージョンと管理対象リージョンが同じになったこと)
  • 管理アカウントの全リージョンをGuardDutyで監視すること

無効化の手順に記載します。

  1. Security Hub>設定>設定>組織から管理アカウントを選択し、管理タイプをセルフマネージドに変更します。
  2. ポリシーの再適用を行います。
  3. しばらくすると管理アカウントのポリシーが「セルフマネージド」になります。
  4. Security Hubを無効化するカスタムポリシーを作成します。管理アカウントのアカウントIDを入力し、ポリシーを保存して適用します。
  5. ポリシーの適用には20~30分程かかりました。

これで管理アカウントのすべてのリージョンでSecurity Hub,Configが無効化され、無駄な課金を防ぐことができます。無効化しても無料のトライアル期間は継続するのでご安心ください。

検出結果を抑制する

管理アカウントの全リージョンとメンバーアカウントの拒否リージョンではConfigが有効化されていないため、Config.1の検出結果においてコンプライアンスステータスがFAILEDになってます。

まずはこれらのワークフローステータスを「抑制済み」にします。当該検出結果にチェックを入れた状態で画面右上の「ワークフローステータス」から「抑制済み」を選択します。モーダルウィンドウが開くので、更新理由を記入します。抑制済みに変更した後は、Security Hubは同じ検出結果の新しい検出結果を生成しません。

将来、メンバーアカウントが作成されるたびにこの作業を行うのは面倒なので、オートメーションを使って自動化します。「カスタムルールを作成」よりも「テンプレートからルールを作成」からカスタマイズした方がラクです。プレビュー機能もあるので、条件が正しく設定できているか確認できます。抑制後はConfig.1のコントロールステータスが成功になり、セキュリティスコアにも反映されました。※オートメーションは既存の検出結果に適用されないため、手動で変更しました(CLIも可)。


オートメーション作成後、Account FactoryでAWSアカウントを作成し、Config.1が期待通り抑制されることを確認しました。

2. Amazon GuardDuty

GuardDutyもSecurity Hubと同じくリージョンサービスかつ、無料のトライアル期間(30日)があります。すべてのリージョンで有効化することが推奨されています。Organizations、Cfn Stack Setsを使えばすべてのアカウント/リージョンで一括有効化できます。
GuardDutyは分析対象である基礎データソース(CloudTrailイベントログ、VPCフローログ、DNSクエリログ)の量に応じて課金されます。管理アカウントは開発作業をするアカウントではないので、大した料金にはならない想定です。なお、基礎データソースはGuardDutyがユーザ環境と独立して取得するため、ログの有効化作業は不要です。

用語解説

  • 検出結果(Findings)
    • ワークロードやデータ内で検出された潜在的なセキュリティの問題
    • 疑わしい/悪意のあるアクティビティの兆候を検出したときに生成される
    • 検出結果自体は90日間保持され、その後破棄される
  • 重要度(Severity Level)
    • 検出結果ごとに重要度が設定される
    • 重要度はCritical(9.0-10.0),High(7.0-8.9),Medium(4.0-6.9),Low(0.1-3.9)のいずれかの値になる
    • 値が大きいほどセキュリティリスクが大きいことを示す
  • 検出結果タイプ(Finding type)
    • 検出結果の一項目で、検出結果を簡潔に記述したもの
    • 検出結果タイプの形式は、ThreatPurpose:ResourceTypeAffected/ThreatFamilyName.DetectionMechanism!Artifact
  • 拡張脅威検出(Extended Threat Detection)
    • 2024年12月1日にGAされた機能
    • デフォルトで有効化される
    • 多段階攻撃を検出する機能
  • 攻撃シーケンス
    • 特定のパターン/順序で発生した一連の疑わしいアクテビティ
    • 攻撃シーケンスの検出結果の重要度はCriticalが設定される
  • 保護プラン
    • GuardDutyのオプション機能
    • 拡張脅威検出以外の保護プランはデフォルトで無効化されている
    • サポートされているのは以下の通り
      • S3 Protection
      • EKS Protection
      • Extended Threat Detection
      • Runtime Monitoring
      • Malware Protection for EC2
      • Malware Protection for S3
      • RDS Protection
      • Lambda Protection

GuardDuty有効化・委任

Security Hubと同様に管理アカウントにてGuardDutyの有効化とAuditアカウントへの委任を行います。

メンバー追加

Auditアカウントにて、GuardDutyの監視対象に管理アカウントを追加します。この時、エラーが発生しました。どうやら、メンバー追加の前に管理アカウントのGuardDutyを有効化する必要があるようです。
管理アカウントのホームリージョンでGuardDutyを有効化してから実施したところ成功しました。

エラー
以下のアカウントを organizationEnableGuardDuty できませんでした:
${AccountId}: Operation failed because your organization master must first enable GuardDuty to be added as a member

GuardDuty全リージョン有効化

管理アカウントの全リージョンでGuardDutyを有効化します。マネコンでポチポチするのは面倒なので、ChatGPTにシェルスクリプトを作成してもらい、CloudShellから実行しました。

#!/bin/bash

# 有効なリージョンを取得
ALL_REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text)

for REGION in $ALL_REGIONS; do
  echo "Checking GuardDuty in region: $REGION"

  # すでにDetectorが存在するかチェック
  DETECTOR_ID=$(aws guardduty list-detectors --region "$REGION" --query 'DetectorIds[0]' --output text)

  if [ "$DETECTOR_ID" = "None" ] || [ -z "$DETECTOR_ID" ]; then
    echo "→ Creating GuardDuty detector in $REGION"
    aws guardduty create-detector --enable --region "$REGION"
  else
    echo "→ GuardDuty already enabled in $REGION (DetectorId: $DETECTOR_ID)"
  fi
done

次に全リージョンのGuardDutyの管理をAuditアカウントに委任します。既に委任されている場合はエラーになります。for文は継続されるので問題ありません。

#!/bin/bash

# AuditアカウントのアカウントID
AUDIT_ACCOUNT_ID="111111111111"

ALL_REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text)
for REGION in $ALL_REGIONS; do
  echo "Setting delegated admin for GuardDuty in region: $REGION"

  # すでに委任されていないか確認(明示的に確認しない場合は何度でも実行してOK)
  aws guardduty enable-organization-admin-account \
    --admin-account-id "$AUDIT_ACCOUNT_ID" \
    --region "$REGION"

  echo "Delegated admin set for $REGION"
done

全リージョンで委任が成功しているか確認します。

#!/bin/bash

# AuditアカウントIDを指定
AUDIT_ACCOUNT_ID="111111111111"
ALL_REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text)

for REGION in $ALL_REGIONS; do
  echo -n "$REGION: "
  OUTPUT=$(aws guardduty list-organization-admin-accounts --region $REGION --query "AdminAccounts[?AdminAccountId=='$AUDIT_ACCOUNT_ID'].AdminStatus" --output text 2>/dev/null)
  if [ "$OUTPUT" == "ENABLED" ]; then
    echo "ENABLED"
  else
    echo "NOT DELEGATED"
  fi
done

3. CloudFormation StackSets

GuardDutyはデフォルトでイベントが発行されるのでEventBridgeルールを作成することで対象の検出結果が生成/更新された時、通知を受け取ることが可能です。ここでは、管理アカウントの全リージョンを対象に、GuardDutyで検出結果が生成された時、メール通知を受け取る仕組みを実装します。各リージョンへのデプロイはCloudFormation StackSetsで行います。作成するリソースは下記です。

  • EventBridge Rule①
    • 全リージョンに作成
    • デフォルトイベントバスに配置する
    • ターゲットにホームリージョンのカスタムイベントバスを指定
  • EventBridge Rule②
    • カスタムイベントバスに配置する
    • ターゲットはSNSトピックを指定
  • EventBridge Rule①の実行ロール
    • アカウントに一つ作成
  • EventBridge カスタムイベントバス
    • ホームリージョンに作成
  • SNSトピック
    • カスタムイベントバスからイベントを受信してサブスクリプションに配信する
  • SNSサブスクリプション
    • エンドポイントに通知先のメールアドレスを設定

CloudFormation StackSets有効化

Organizationsから有効化を行います。委任はしません。

テンプレートの準備

こちらからテンプレートを拝借しました。サブスクリプションが実装されていなかったのでParameters, Resourcesに追加しました。

Parameters:
  SNSEndpoint:
    Description: "Email address to receive notifications"
    Type: String

Resources:
  SNSSubscription:
    Type: "AWS::SNS::Subscription"
    Properties:
      Endpoint: !Ref SNSEndpoint
      Protocol: "email"
      TopicArn: !Ref SNSTopic1

StackSets作成

拝借したテンプレートの1つ目(1-eventbridge-receiver-with-sns.yml)はCloudFormationスタックとしてホームリージョンにデプロイします。スタック作成後は削除保護を有効化しました。出力された2つの値を2つ目のテンプレート(2-eventbridge-sender.yml)のパラメータに入力します。

2つ目のテンプレートはStackSetsでデプロイします。StackSetsのアクセス許可は2種類あります。

  • サービスマネージドアクセス許可
    • OUに属するアカウントの特定のリージョンにスタックをデプロイします(必ずOU IDの指定が必要)。
    • StackSetsがIAMロールを作成するため、ユーザが準備する必要はありません。
    • 自動デプロイ(組織/OUに追加されたアカウントにStackSetsが自動デプロイを行う)の機能が備わっています。
  • セルフサービスのアクセス許可

管理アカウントはOUに属していないため、セルフサービスのアクセス許可を採用します。管理アカウントにAWSCloudFormationStackSetAdministrationRole、ターゲットアカウント(管理アカウント)にAWSCloudFormationStackSetExecutionRoleを作成します。ロールはテンプレートが提供されているため、スタックとしてデプロイしました。削除保護は有効化しておきます。

いよいよStackSetsを作成します。デプロイオプションの設定>リージョンの設定にて、「すべてのリージョンを追加」を選択すると無効化されているリージョンも含まれててしまうためデプロイエラーになります。注意です。

当初、ロールを自分で作成する必要があると知らず、StackSetsの作成を進めたらエラーになりました。

Account 222222222222 should have 'AWSCloudFormationStackSetExecutionRole' role with trust relationship to Role 'AWSCloudFormationStackSetAdministrationRole'

動作確認

最後に、検出結果が生成された時、メール通知が飛んでくるか確認します。CloudShellで下記コマンドを実行し、重要:高の検出結果を1件生成します。管理アカウントとAuditアカウントの両方のコンソールで生成されていることを確認できました。メールもしっかり飛んできました。
なぜ検出結果が362件もあるかというと、GuardDuty>設定から検出結果のサンプルを作成したからです。検出結果は削除できないため邪魔でしょうがないです。1件だけ作成する手段があるとは,,,時すでに遅し。

aws guardduty create-sample-findings \
  --detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
  --finding-types "Backdoor:EC2/DenialOfService.Dns"


1つ目のテンプレートで作成したイベントルールのイベントパターンは重要度が0以上、つまり、あらゆる検出結果が生成された時、通知を受け取る設定にしています。

{
  "source": [
    "aws.guardduty"
  ],
  "detail-type": [
    "GuardDuty Finding"
  ],
  "detail": {
    "severity": [{
      "numeric": [">=", 0]
    }]
  }
}

さいごに

長かった。理想的なSecurity Hubの設定をするのに苦戦して、トータルで約一週間費やしました💦💦💦。認定試験の時、文字だけで理解していた知識を、構築を通して深めることができました。セキュリティ標準については、コントロールの内容を把握するには至っていないため、これから運用していく中で重要度が高いもから対応していこうと思います。
本当に長くなりましたが、最後までご一読いただき、ありがとうございました。

Discussion