Node.js AWS SDK V3でSecurityHub GuardDutyの全リージョン設定

2022.10.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX事業本部デリバリー部広島の吉川です。

皆さんはAWSアカウントのセキュリティ周りの初期セットアップはどうされてますでしょうか?

代表的なサービスにSecurityHubやGuardDutyがありますが、これらのためにマネコンを開いて手動で全リージョンに設定して回るのは少々大変と思います(できなくはない量だったりしますが)。

この点、本ブログにおいてCloudFormation+StackSetsで適用する、CDKで適用する、CLIで適用するなどなど、すでにやり方を色々紹介されているところですが、今回はNode.js + AWS SDK V3を使って全リージョン適用する方法を紹介したいと思います。

npm or yarnを使ったinstall操作、TypeScriptの設定などは割愛します。

環境

  • node 16.17.1
  • typescript 4.8.4
  • @aws-sdk/client-ec2 3.181.0
  • @aws-sdk/client-guardduty 3.181.0
  • @aws-sdk/client-securityhub 3.181.0
  • @aws-sdk/client-sts 3.181.0

SecurityHubとGuardDutyの全リージョン有効化

タイトルの通りで、SecurityHubとGuardDutyを全リージョン分有効化するスクリプトです。

import { DescribeRegionsCommand, EC2Client } from '@aws-sdk/client-ec2';
import {
  EnableSecurityHubCommand,
  SecurityHubClient,
} from '@aws-sdk/client-securityhub';
import {
  CreateDetectorCommand,
  GuardDutyClient,
} from '@aws-sdk/client-guardduty';

/**
 * 全リージョンのSecurityHubとGuardDutyを有効化する
 */
export const enableSecurityHubsAndGuardDuties = async () => {
  const ec2Client = new EC2Client({
    region: 'ap-northeast-1',
  });
  // リージョン一覧を取得
  const { Regions: regions = [] } = await ec2Client.send(
    new DescribeRegionsCommand({})
  );

  await Promise.all(
    regions.map(async ({ RegionName: regionName }) => {
      if (regionName == null) {
        throw new Error('regionNameがundefinedです');
      }

      // SecurityHub設定
      const securityHubClient = new SecurityHubClient({
        region: regionName,
      });
      try {
        // SecurityHubを有効化する
        await securityHubClient.send(new EnableSecurityHubCommand({}));
      } catch (e: unknown) {
        // 「すでに設定済み」エラーはスルーし、それ以外は予期せぬエラーとして落とす
        if (
          (e as Error).message !==
          'Account is already subscribed to Security Hub'
        ) {
          throw new Error('予期せぬエラーです', {
            cause: e,
          });
        }
      }

      // GuardDuty設定
      const guardDutyClient = new GuardDutyClient({ region: regionName });
      try {
        // GuardDutyを有効化する
        await guardDutyClient.send(new CreateDetectorCommand({ Enable: true }));
      } catch (e: unknown) {
        // 「すでに設定済み」エラーはスルーし、それ以外は予期せぬエラーとして落とす
        if (
          (e as Error).message !==
          'The request is rejected because a detector already exists for the current account.'
        ) {
          throw new Error('予期せぬエラーです', {
            cause: e,
          });
        }
      }
    })
  );
};

全リージョンに対し、直列ではなく並行にリクエストを投げているため実行時間は短縮されていると思います。

それから、「Aリージョンは有効化されているがBリージョンはされていない」といった中途半端な状態に対しても使えるよう、エラーハンドリングを工夫し、べき等な処理になるよう意識しています。

SecurityHubのスタンダードを「AWS 基礎セキュリティのベストプラクティス v1.0.0」のみにする

全リージョンでAWS Security Hubの不要なセキュリティ標準を無効化する | DevelopersIO

ただし2022年9月28日現在ではこの方法でセットアップした場合に有効となるセキュリティ標準は次の2つです。 AWS 基礎セキュリティのベストプラクティス v1.0.0 CIS AWS Foundations Benchmark v1.2.0 どちらも運用したい場合はこれで問題ないのですが、どちらか1つだけを運用したい場合も結構あるのでは?と思ってます。

現在、SecurityHubをONにするとデフォルトで2つの標準が有効となります。標準をより多く適用することによりチェックリストが増えてメリットもあるのですが、体制によっては負担が大きく感じられてしまうこともあるかと思います。

そのため、「CIS AWS Foundations Benchmark v1.2.0」をOFFにして「AWS 基礎セキュリティのベストプラクティス v1.0.0」だけを残すようにしてみます。

上のブログでAWS CLIとシェルスクリプトで実現されているのをNode.js + AWS SDK V3に翻訳してみた形となります。

import { DescribeRegionsCommand, EC2Client } from '@aws-sdk/client-ec2';
import {
  BatchDisableStandardsCommand,
  SecurityHubClient,
} from '@aws-sdk/client-securityhub';

/**
 * SecurityHubのスタンダードを「AWS 基礎セキュリティのベストプラクティス v1.0.0」のみにする
 */
export const updateSecurityHubStandards = async () => {
  const { Account: accountId } = await sts.send(
    new GetCallerIdentityCommand({})
  );
  if (accountId == null) {
    throw new Error('accountIdがundefinedです')
  }

  const ec2Client = new EC2Client({
    region: 'ap-northeast-1',
  });
  // リージョン一覧を取得
  const { Regions: regions = [] } = await ec2Client.send(
    new DescribeRegionsCommand({})
  );

  await Promise.all(
    regions.map(async ({ RegionName: regionName }) => {
      if (regionName == null) {
        throw new Error('regionNameがundefinedです');
      }

      // SecurityHub設定
      const securityHubClient = new SecurityHubClient({
        region: regionName,
      });
      // デフォルト有効のCIS AWS Foundations Benchmark v1.2.0を無効化し、AWS 基礎セキュリティのベストプラクティス v1.0.0を残す
      await securityHubClient.send(
        new BatchDisableStandardsCommand({
          StandardsSubscriptionArns: [
            `arn:aws:securityhub:${regionName}:${accountId}:subscription/cis-aws-foundations-benchmark/v/1.2.0`,
          ],
        })
      );
    })
  );
};

SecurityHubを特定のリージョンに集約する

最後に、各リージョンのSecurityHubを特定リージョンに集約するスクリプトです。これくらいならAWS CLIでも良さそうではありますが書いてみました。

今回は東京リージョンに集約するサンプルとします。

import {
  CreateFindingAggregatorCommand,
  SecurityHubClient,
} from '@aws-sdk/client-securityhub';

/**
 * SecurityHubを東京リージョンに集約する
 */
export const aggregateSecurityHubs = async () => {
  // 全リージョンのSecurityHubを東京リージョンに集約する
  const securityHubClient = new SecurityHubClient({
    region: 'ap-northeast-1',
  });
  try {
    await securityHubClient.send(
      new CreateFindingAggregatorCommand({ RegionLinkingMode: 'ALL_REGIONS' })
    );
  } catch (e: unknown) {
    // 「すでに設定済み」エラーはスルーし、それ以外は予期せぬエラーとして落とす
    if (
      !(e as Error).message.includes(
        'A finding aggregator already exists for the account ID'
      )
    ) {
      throw new Error('予期せぬエラーです', {
        cause: e,
      });
    }
  }
};

以上、参考になることがありましたら幸いです。

参考