電通総研 テックブログ

電通総研が運営する技術ブログ

組織横断的なSecurity Hub導入に向けて

X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。

以前の記事では、AWS Security Hubを検証した際に気付いたことを山口さんよりご紹介しました。今回はその続編として、Security Hubの実用に向けた取り組みをお話しします。

社内プロジェクトへのSecurity Hub導入

普段ならつい見過ごしてしまいがちな、不十分なセキュリティ設定をきっちり警告してくれるため、ISIDではAWSを使用している全てのプロジェクトにSecurity Hubを導入することを目指しています。

Security Hubは導入するだけなら非常に簡単です。しかし本当に重要なのはその検出結果を確認し、確実に改善に繋げることです。そのためには少なくとも以下のことが必要になります。

  1. 定期的にSecurity Hubの検出結果を確認する運用手順や仕組み
  2. 検出結果の内容の理解
  3. 修正すべきポイントと、修正することによる影響の理解
  4. セキュリティリスクと修正コストを総合的に判断し、対応方針を決定すること

ISIDには数多くのプロジェクトがあり、担当メンバーも異なるため、1~4をプロジェクト内で個別に対応するのは望ましい姿とはいえません。そこで組織横断的な支援を行う体制により、1~4の実施を各プロジェクトに対してサポートしています。

  1. 定期的にSecurity Hubの検出結果を確認する運用手順や仕組み
    → 各プロジェクトの検出結果を集約し、定期的に通知する仕組みの導入(のちに詳述)
  2. 検出結果の内容の理解
    Security Hubのセキュリティ基準をさらに詳細化したノウハウ集の作成と展開
  3. 修正すべきポイントと、修正することによる影響の理解
    → 2.と同じ
  4. セキュリティリスクと修正コストを総合的に判断し、対応方針を決定すること
    → 対応方針の相談窓口の設置

2.と3.については検証用のAWSアカウントを利用して、各検出項目の発火の条件と改善方法を調査してきました。Security Hubのセキュリティ基準の項目は不定期に追加されますので、今後も継続的に調査を行っていくことになります。
4.についてはMicrosoft Teamsに専用のチームを作成し、プロジェクトからの相談を随時受け付ける体制にしています。検出結果に対応するにあたって、各プロジェクトに共通する課題なども見えてくるのではないかと期待しています。

ここからは 1. の「検出結果を集約し、通知する仕組み」について、詳細をお話しします。

検出結果の集約

※注:以下の記述はAWS Organizationsと統合されていない場合です。

Security Hubには複数のメンバーアカウントの検出結果を管理者アカウントに集約する機能がありますので、これを利用します。

アカウント集約

いわゆる管理者アカウントから各プロジェクトのメンバーアカウントに招待を送り、メンバーアカウントから承認することで、Security Hubの検出項目を管理者アカウントからも確認できるようになります。この際、メンバーアカウントにてSecurity HubとConfigの有効化が必要になります。

注意が必要なのは、Security Hubはリージョナルサービスであるため、メンバーアカウントの複数リージョンから結果を集約したい場合はリージョンごとに招待・承認しなければなりません。
複数のメンバーアカウントを取り扱うため、管理者アカウントの方は当然、全リージョンでSecurity Hubを有効にしておきます。

リージョンごとに招待

管理者アカウントに集約された結果を、リージョンを切り替えながら確認するのは手間がかかるので、結果をリージョン間で集約する機能を利用します。
これにより、複数のメンバーアカウントの複数リージョンの検出結果を、1つの管理者アカウントの1リージョンから取得できるようになります。

リージョン間集約

検出結果を通知する仕組みの構築

1箇所に集約された検出結果を、定期的に取得してプロジェクトに通知する仕組みを構築しました。
AWS SDK for JavaScriptを利用し、ECSタスクからSecurity Hub検出結果の取得、レポートの生成と保存、配信までを行います。
通知の仕組み

①ECSタスクを定期実行します
②プロジェクトとAWSアカウントID、および結果配信先を関連付ける情報を取得します。今回はS3バケットCSVファイルとして配置することにしました
③プロジェクトごとに、Security Hubの検出結果を取得します
④⑤プロジェクトごとに検出結果を見やすい形でまとめたExcelレポートを作成し、S3バケットに保存します
⑥レポートにプロジェクトの担当者がアクセスできるように、署名付きURLを生成します
⑦レポートへの署名付きURLをプロジェクト担当者に通知します。今回はメールで配信することにしました
⑧将来的にプロジェクトの対応状況を分析できるように、検出履歴をDynamoDBに保存しておきます
⑨プロジェクト担当者は署名付きURLからレポートをダウンロードします。万が一署名付きURLが漏れても社外の第三者がレポートを取得できないように、バケットポリシーを設定しsource IPをISID社内からのアクセスに限定しておきます

この仕組みにより、以下のようなメリットが発生します

  • プロジェクトが個別にSecurity Hubを確認する負荷を軽減できる
  • 複数アカウントを利用しているプロジェクトの場合、1つのレポートに検出結果がまとまる
  • Excelのレポートを生成するため、検出結果をフィルターしやすい
  • 組織横断的に検出結果への対応状況を集計、分析できる(今後の期待)

構築上のTips

AWS SDK for JavaScript から Security Hub の検出結果を取得する際のTipsを2つ記します。

一度に取得できる検出結果は100件まで

@aws-sdk/client-securityhub モジュールの GetFindingsCommand から受け取る結果の最大数を MaxResults として指定できるのですが、最大100件までという制約があります。

const command = new GetFindingsCommand({
  MaxResults: 100, // 最大100まで
  NextToken: nextToken,
  Filters: filters,
});

100件を超える検出結果を取得したい場合は、結果に NextToken というプロパティに値が入った状態で返ってきます。これを GetFindingsCommand の入力に追加してもう一度送ることで、続きの検出結果を取得できます。

const data = await client.send(command);

if (data.NextToken === undefined) {
  // 結果が100件以内
} else {
  // 結果が100件を超えている
}

すなわち、再帰的に GetFindingsCommand を送信することで、全ての検出結果を取得できます。

import {
  SecurityHubClient,
  GetFindingsCommand,
  AwsSecurityFinding,
  AwsSecurityFindingFilters,
  StringFilter,
} from "@aws-sdk/client-securityhub";

const client = new SecurityHubClient({ region: "ap-northeast-1" });

export async function getNextFindings(awsAccountIds: string[], nextToken?: string): Promise<AwsSecurityFinding[]> {
  const accountsFilter: StringFilter[] = awsAccountIds.map((id) => {
    return { Value: id, Comparison: "EQUALS" };
  });

  // AWSアカウントID、レコードの状態、ワークフローステータスでフィルタリング
  const filters: AwsSecurityFindingFilters = {
    AwsAccountId: accountsFilter,
    RecordState: [{ Value: "ACTIVE", Comparison: "EQUALS" }],
    WorkflowStatus: [
      { Value: "NEW", Comparison: "EQUALS" },
      { Value: "NOTIFIED", Comparison: "EQUALS" },
    ],
  };

  const command = new GetFindingsCommand({
    MaxResults: 100,
    NextToken: nextToken,
    Filters: filters,
  });
  // コマンド送信
  const data = await client.send(command);
  const findings: AwsSecurityFinding[] = data.Findings ?? [];

  if (data.NextToken === undefined) {
    return findings;
  } else {
    // 100件を超える場合は再帰的に呼び出す
    return findings.concat(await getNextFindings(awsAccountIds, data.NextToken));
  }  
}

一度にフィルタリングできるアカウントIDは20件まで

プロジェクトごとに検出結果をSecurity Hubから取得したいので、AWSアカウントIDで検出結果をフィルタリングしてクエリしていますが、アカウントIDの数が多いと以下のようなエラーになりました。

UnhandledPromiseRejectionWarning: InvalidInputException: Invalid parameter 'Filters'. data.AwsAccountId should NOT have more than 20 items

アカウントIDは一度に20件までしかフィルタリングに利用できないとのことなので、20件を超えるアカウントIDの検出結果を取得したい場合は、20件以下に分割してから複数回クエリする必要があります。

今後に向けて

今回お話しした仕組みの運用はまだ始まったばかりで、今後新たな課題が発生するかもしれません。ISIDでは引き続き、組織横断的にAWS環境をセキュアにするための取り組みを実施していきます。

執筆:@kou.kinyo2、レビュー:@higaShodoで執筆されました