0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CDK で Security Hub CSPM を2リージョンに構築する — 東京集約アーキテクチャと衝突しないゲートパターン【入門】

0
Posted at

第1回では「Security Hub は集約と採点の道具で、採点は AWS Config で動く」という概念を
整理しました。今回はそれを AWS CDK(TypeScript)で実際に形にします。

この記事でわかること

  • Security Hub CSPM を CDK で構築するときの全体の流れ(事前確認 → ゲート設定 → デプロイ → 検証)
  • なぜ東京だけでなく us-east-1 にも必要になるのか(CloudFront・WAFv2)と、finding aggregator による東京集約
  • 標準を FSBP 単独にする理由と enableDefaultStandards: false
  • 「リージョンに1個」しか作れないリソースと衝突しないゲートパターン
  • デプロイ後に「本当に動いているか」を確認する read-only コマンド一式

前提: 第1回の概念(SH の仕事は集約+採点 / 採点の実体は Config ルール / 有効化はリージョン単位)。
用語に迷ったら第1回の用語集へ。CDK v2 のプロジェクトを触ったことがある想定です。

シリーズ: ① 概念編 → ② CDK 構築編(本記事) → ③ トラブル編

1. 今回のゴールと進め方

先にゴールをはっきりさせておきます。この記事が終わったとき、手元にはこういう状態ができています:

ゴール(完成条件)

  • FSBP 標準の採点が実際に回っている(recorder が録画中で、コントロールが PASSED / FAILED を出す)
  • us-east-1 の findings も含めて、東京の Security Hub 一画面で見られる
  • 以上がすべてコード(CDK)で管理されていて、別アカウントにも同じ構成を再現できる

進め方は4ステップ。この順番自体が本記事の目次でもあります:

  1. 設計を理解する(§2) — 「東京だけ」で考え始めて、2つの壁にぶつかりながら最終形に辿り着く
  2. アカウントの現状を確認して「ゲート」を設定する(§3) — 既存リソースと衝突しないための下ごしらえ
  3. CDK で実装してデプロイする(§4) — 核心コードは3つだけ
  4. 本当に動いているか検証する(§5) — read-only コマンドで実体を確認

2. 設計 — 「東京だけ」から始めて、2つの壁にぶつかる

2.1 まず素朴に考える: 東京リージョンだけで良いのでは?

第1回の知識で素直に組むなら、こうなるはずです:
東京に AWS Config(recorder)を置き、東京で Security Hub CSPM + FSBP を有効化する
これで東京リージョンのリソースの採点は回ります。ワークロードが東京だけなら、実際これでほぼ足ります。

ところが、構築を進めると2つの壁にぶつかります。

2.2 壁① — CloudFront と WAF がいつまでも採点されない

FSBP には CloudFront.xWAF.x というコントロールがあるのに、CloudFront を使っていてもfindings が一向に出ない。

これはCloudFront と、CloudFront に紐づく WAFv2 Web ACL は、AWS 内部では us-east-1(バージニア北部)のリソースとして存在するからです。

第1回のとおり Security Hub CSPM の採点はリージョン単位 —東京の Hub は東京のリソースしか見えないので、us-east-1 にいる CloudFront は永遠に採点対象外になります。

結論①: CloudFront・WAF を採点したければ、us-east-1 にも Security Hub CSPM + Config(recorder)が必要。つまり最低でも「東京 + us-east-1」の2リージョン構成になる。

2.3 壁② — 今度は画面が2つに割れる

us-east-1 にも Hub を立てると、今度は findings の置き場が2か所になります。
「セキュリティの状況は Security Hub を見ればわかる」はずが、2つのリージョンのコンソールを巡回する運用に逆戻りしてしまう。

これを解決するのが第1回 §7 で予告した**クロスリージョン集約(finding aggregator)**です。集約リージョン(=東京)に aggregator を置いて us-east-1 をリンクすると、us-east-1 の findingsが 東京の Security Hub にも自動で現れます

運用者は東京だけ見ればよい。

結論②: 2リージョンに Hub を置きつつ、aggregator で東京に集約する。
通知(EventBridge)の配線も東京1か所で済むようになる。

2.4 たどり着いた完成形

qiita-img-p2-1-architecture.png
緑=CDK で新規作成、灰破線=既存活用(§3 のゲート)、青破線=次のステップ。us-east-1 → 東京の集約矢印が §2.3 の答え。*

壁①②の答えに加えて、あと3つの設計判断が入っています。いずれもそのままコスト設計でもあります:

判断 内容 理由
標準は FSBP 単独 enableDefaultStandards: false にして FSBP だけ明示的に有効化 既定のままだと FSBP + CIS が自動で両方有効になり、同じ問題が2重に findings 化され検査料金も増える。まず FSBP 1本で始めるのが定石
グローバルタイプの記録は東京のみ us-east-1 の Config recorder は WAFv2・CloudFront の7タイプ限定 IAM などのグローバルタイプを両リージョンで記録すると構成アイテム(CI)課金が二重になる。AWS も「グローバルリソースの記録は1リージョンのみ」を推奨
集約ホームは東京 aggregator を東京に置き us-east-1 をリンク 運用の主拠点が東京のため。通知の配線も東京1か所で済む

3. 実アカウントの現実 — 「リージョンに1個」問題とゲートパターン

設計が決まったら次はデプロイ…の前に、教科書どおりに行かないポイントがあります。GuardDuty の detector、Security Hub の Hub、Config の recorder はいずれも 1リージョンに1個しか存在できません
そして実アカウントには、過去に誰かが有効化したリソースが残っていることがよくあります(筆者のケースでは GuardDuty が既に有効でした)。

問題は、CloudFormation に「既にあればスキップ」という機能が無いこと。テンプレートに書いた以上、必ず作ろうとし、既存があれば already exists エラーでスタック全体がロールバックします。

qiita-img-p2-2-gate-flow.png

ゲートパターン。デプロイ前の read-only 確認 → .env のゲート値 → 「無いものだけ作る」。確認を怠ると already exists → ロールバック。*

3.1 デプロイ前の存在確認(read-only)

リソース 確認コマンド 「存在しない」ときの出力
Security Hub aws securityhub describe-hub --region ap-northeast-1 InvalidAccessException: ... not subscribed
GuardDuty aws guardduty list-detectors --region ap-northeast-1 "DetectorIds": []
Config recorder aws configservice describe-configuration-recorders "ConfigurationRecorders": []
CloudTrail aws cloudtrail describe-trails "trailList": []

3.2 ゲートの実装 — .env フラグ + if 文だけ

大げさな仕組みは要りません。確認結果を .env のフラグに落とし、スタック側は
フラグが true のときだけ construct を生成します:

// env-config.ts — "true" という文字列のときだけ true(既定 = 未設定 = false = 作らない)
function flag(value: string | undefined): boolean {
  return value?.trim().toLowerCase() === "true";
}
# .env — デプロイ前の確認結果を反映する
CREATE_SECURITYHUB=true        # 未サブスクライブを確認済み → 作る
CREATE_GUARDDUTY=false         # 既存 detector あり → 絶対に作らない
CREATE_CONFIG_RECORDER=true    # recorder 不在を確認済み → 作る
// スタック側 — ゲートが true のものだけテンプレートに載る
if (config.createSecurityHub) {
  cspm = new SecurityHubCspm(this, "SecurityHub");
}

ポイント: 既定値は必ず false(作らない) にする。「確認していないものは作らない」に倒しておけば、設定漏れがあっても事故は「作られなかった」で済む。逆(既定 true)だと設定漏れ = ロールバック事故になる。

4. CDK 実装 — 核心コード3つ

4.1 Security Hub CSPM + FSBP 単独有効化

import * as securityhub from "aws-cdk-lib/aws-securityhub";

export class SecurityHubCspm extends Construct {
  readonly hub: securityhub.CfnHub;

  constructor(scope: Construct, id: string) {
    super(scope, id);
    const stack = cdk.Stack.of(this);

    this.hub = new securityhub.CfnHub(this, "Hub", {
      enableDefaultStandards: false,  // 既定(FSBP+CIS 自動有効化)を止める
    });

    // FSBP だけを明示的に購読。ARN はリージョンごとに異なる点に注意
    const fsbp = new securityhub.CfnStandard(this, "FsbpStandard", {
      standardsArn: `arn:${stack.partition}:securityhub:${stack.region}::standards/aws-foundational-security-best-practices/v/1.0.0`,
    });
    fsbp.addDependency(this.hub);  // Hub 有効化前に購読しようとして失敗するのを防ぐ
  }
}

CfnStandardCfnHub は独立リソースなので、依存を張らないと CloudFormation が並列作成し「Hub がまだ無いのに標準を購読 → 失敗」が起き得ます。addDependency を忘れずに。

4.2 Finding Aggregator(東京がホーム)

// 東京スタック側。このスタックのリージョン(東京)が自動的に集約ホームになる
const aggregator = new securityhub.CfnFindingAggregator(this, "FindingAggregator", {
  regionLinkingMode: "SPECIFIED_REGIONS",
  regions: ["us-east-1"],          // リンクするリージョン
});
aggregator.addDependency(cspm.hub);   // これも Hub が先

4.3 us-east-1 の Config は7タイプ限定

// us-east-1 スタックの Config recorder — グローバルタイプは記録しない(東京の担当)
recordingGroup: {
  allSupported: false,
  includeGlobalResourceTypes: false,   // ← 二重課金防止の要
  resourceTypes: [
    "AWS::WAFv2::WebACL", "AWS::WAFv2::RuleGroup", "AWS::WAFv2::IPSet",
    "AWS::WAFv2::RegexPatternSet", "AWS::CloudFront::Distribution",
  ],
},

上記5タイプが「実際に自分で作る可能性がある」もの。AWS::WAFv2::ManagedRuleSet は AWS/マーケットプレイスのルールベンダー専用リソースなので通常は不要、AWS::CloudFront::StreamingDistribution(RTMP配信)は2020年に非推奨化され新規作成できないため、両方とも一覧から外して問題ありません。

Config recorder 自体の CDK 実装には大きな落とし穴があります。
recorder と delivery channel を素直に L1 リソースで書くと、どちらを先に作っても失敗する「循環デッドロック」に必ずハマります。筆者は実際に CREATE_FAILED とハングを1回ずつ経験しました。この顛末と解決策(AwsCustomResource ×3)は第3回で詳しく扱います。

5. デプロイと「本当に動いているか」の確認

スタックは「東京」「us-east-1」の2つ。デプロイ後、次の read-only コマンドで実体を確認します(コンソールを眺めるより確実で、そのまま手順書にもなります):

# ① Config recorder が実際に記録中か(最重要 — 第1回 §5 参照)
aws configservice describe-configuration-recorder-status --region ap-northeast-1
aws configservice describe-configuration-recorder-status --region us-east-1
# → 期待値: "recording": true, "lastStatus": "SUCCESS"

# ② Security Hub が FSBP 単独で有効か
aws securityhub get-enabled-standards --region ap-northeast-1
# → aws-foundational-security-best-practices/v/1.0.0 が1件だけ

# ③ 集約の確認 — ホームとリンク先
aws securityhub list-finding-aggregators --region ap-northeast-1
aws securityhub get-finding-aggregator --region ap-northeast-1 --finding-aggregator-arn <ARN>
# → 期待値: "FindingAggregationRegion": "ap-northeast-1", "Regions": ["us-east-1"]

# ④ しばらくして: us-east-1 発の findings が東京に見えるか
aws securityhub get-findings --region ap-northeast-1 \
  --filters '{"Region":[{"Value":"us-east-1","Comparison":"EQUALS"}]}' --max-items 10

焦らないポイント2つ

① 標準のステータスが READY になるまで時間がかかります。デプロイ直後に
INCOMPLETE / NO_AVAILABLE_CONFIGURATION_RECORDER と表示されても、①の確認で recorder がrecording: true` なら AWS 側の状態更新待ちであることが多い(第1回 §5 の「recorder がそもそも無い」状況とは、recorder の実在で見分けられます)。

② 集約直後は us-east-1 の findings が東京に現れるまでラグがあります。day-0 で空でも正常。

6. コストの話 — この設計自体がコストガードレール

  • 30日無料トライアル: Security Hub CSPM はリージョン×アカウント単位で初回30日無料。GuardDuty も同様。まず検証用アカウントで findings の量と料金メーターを観察してから本番へ、が安全です。
  • AWS Config が主なコスト源: 記録開始直後は既存全リソース分の構成アイテム(CI)が一括記録される「初期バースト」があります(一時的なもの)。以後は変更量に比例。
  • 設計判断がそのまま節約: FSBP 単独(検査数を抑える)+ グローバルタイプは1リージョンのみ記録 (CI の二重課金防止)。§2.4 の表の「理由」列は全部コストの話でもあります。

7. まとめと次回予告

  • 進め方は4ステップ — 設計 → 事前確認+ゲート → 実装+デプロイ → 検証
  • 「東京だけ」で始めると2つの壁(CloudFront が採点されない / 画面が割れる)にぶつかる → 答えは us-east-1 追加 + finding aggregator で東京集約(図C)
  • 標準は enableDefaultStandards: false + FSBP 単独から始める
  • 「リージョンに1個」リソースはゲートパターンで守る — 事前確認 → .env フラグ →
    無いものだけ作る。既定は必ず false(図D)
  • デプロイ後は read-only コマンドで「実体」を確認。recording: true が試金石

次回(第3回・最終回): この構築、実は一度で成功していません。AWS Config の recorder とdelivery channel が互いを前提とする循環デッドロックで、CREATE_FAILED → ハング → と2連敗しました。CloudFormation のイベントログを追いながら原因を特定し、AwsCustomResource ×3 で解決するまでの一部始終を、エラーメッセージの原文つきで振り返ります。


シリーズ: 第1回 概念編 / 第2回(本記事)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?