重大度スコア”中”以下のSecurity Hubコントロールを無効化するスクリプトをLambdaで実装する

記事タイトルとURLをコピーする

2022年新卒入社で現在CS課のOJTを受けていた末廣です。 Security Hubのセキュリティチェックで重大度スコア”中”以下のコントロールを無効化するスクリプトをLambdaで実装する検証を行いましたのでブログにまとめます。

Security Hub について

AWS Secyrity Hubは、AWS リソースに対して、自動化された継続的なセキュリティのベストプラクティスのチェックを行うクラウドセキュリティ体制管理サービスです。

aws.amazon.com

Security Hub にはサポートされている一連のセキュリティ基準に対し、 一連の自動セキュリティ制御が提供されており、関連付けられたリソースに変更があった場合は継続して実行、もしくは設定された定期的なスケジュールで実行されます。 特定のリソースに対するセキュリティチェック項目をコントロールと呼び、 修復作業の優先順位付けに役立つ特定の重大度スコアがあります。

セキュリティ基準を有効化すると、その基準のすべてのコントロールがデフォルトで有効になり、 有効化されたコントロールから無関係なコントロールを無効化すると、 環境に無関係なコントロールのセキュリティチェックの数を減らすことができます。

概要

今回の検証目的は、コントロールの重大度スコアが一定以下のものを定期的に無効化し、 重要性が高いセキュリティチェックのコントロールのみを残すことです。 マネジメントコンソールから手動で操作することもできますが、継続的にチェックされるされるデータをわざわざ確認する作業が必要な上、 コントロールを一つ一つ無効化するのはあまりにも手間がかかります。Security Hubのアップデートによるコントロールの追加にも対応しなければいけません。 また、Security Hub はリージョン別に有効化されるため、複数リージョンでの利用やさらに複数アカウントの利用の場合、手動で今回の操作を行うのは現実的ではありません。

そこで、一日一回定時にコントロールを無効化するスクリプトをLambdaで実行されるようにします。 Lambdaを含めたリソースの作成はCloudFormationで作成します。

構成図

また、今回の検証の実行環境として、SecurityHubのサービス自体、そしていずれかのセキュリティ基準は既に有効化されていることが前提であり、 どのセキュリティ基準のコントロールを無効化するかは判別せず、全てのセキュリティ基準のコントロールを確認し、無効化する作業となります。

作成物の内容

作成したpythonスクリプト

boto3でSecurity Hubを操作する方法を順に追いながらスクリプトの説明をしたいと思います。

boto3.amazonaws.com

大まかな手順は

  1. 有効化されているセキュリティ基準を取得する (15~18行)
  2. 1.で取得したセキュリティ基準内のコントロールをリスト化 (19~33行)
  3. 2.のコントロールの重大度スコアが中以下のもの且つ有効化されているものをリスト化 (34~42行)
  4. 3.のコントロールを無効化する (43~50行)

といったものになります。リストの操作が複雑になりそうだったので2~4のステップに分けて作成しました。

import boto3
from logging import getLogger, INFO
from botocore.config import Config

logger = getLogger()
logger.setLevel(INFO)

def lambda_handler(event, context):
  logger.info('[START] lambda_handler')
  config = Config(
      retries=dict(
          max_attempts=10
      )
  )
  securityhub = boto3.client('securityhub', config=config)

  
  # 有効化されているstandardsを取得
  response = securityhub.get_enabled_standards()
  # describe_standards_controls実行に必要なStandardsSubscription
  subscription_list = response['StandardsSubscriptions']


  # describe_standards_controlsで入手したControlsの部分
  controls_list = []

  # standardsarnごとに全てのcontrolを取得
  for arn in subscription_list:
      token = ""

      # 数が多いためNextToken発生
      while True:
          standardsctl = securityhub.describe_standards_controls(
              StandardsSubscriptionArn=arn['StandardsSubscriptionArn'],
              NextToken=token
          )
          controls_list += standardsctl['Controls']

          if 'NextToken' not in standardsctl:
              break
          token = standardsctl['NextToken']


  # 無効化したい重大度スコア
  rating = ['MEDIUM', 'LOW']
  # ratingに合致するarnのリスト
  controls_arn_list = []
  # controlsの重大度スコアが中以下のもの、かつ有効になっているもののarnをリストに
  for controls in controls_list:
      if controls['SeverityRating'] in rating and controls['ControlStatus'] == 'ENABLED':
          controls_arn_list.append(controls['StandardsControlArn'])
          logger.info(controls['StandardsControlArn'] + 'is now enabled')


  # リスト化したarnをすべて無効化する
  for controls_arn in controls_arn_list:
      securityhub.update_standards_control(
          StandardsControlArn=controls_arn,
          ControlStatus='DISABLED',
          DisabledReason='disable under medium'
      )
      logger.info(controls_arn + 'is disabled')
  logger.info('[END] lambda_handler')
ステップ 1 (15~18行)

コントロールを取得するために引数として必要となるセキュリティ基準のARNを含むレスポンスを取得するget_enable_standardsを実行します。

# 有効化されているstandardsを取得
response = securityhub.get_enabled_standards()
# describe_standards_controls実行に必要なStandardsSubscription
subscription_list = response['StandardsSubscriptions']
ステップ 2 (19~33行)

ステップ 1で取得した全てのセキュリティ基準のARNを引数としてコントロールを取得するdescribe_standards_controlsを実行します。 コントロールの数が多いためページネータの処理が必要なので注意です。

docs.aws.amazon.com

# describe_standards_controlsで入手したControlsの部分
controls_list = []

# standardsarnごとに全てのcontrolを取得
for arn in subscription_list:
    token = ""

    # 数が多いためNextToken発生
    while True:
        standardsctl = securityhub.describe_standards_controls(
            StandardsSubscriptionArn=arn['StandardsSubscriptionArn'],
            NextToken=token
        )
        controls_list += standardsctl['Controls']

        if 'NextToken' not in standardsctl:
            break
        token = standardsctl['NextToken']
ステップ 3 (34~42行)

ステップ 3で有効化されているセキュリティ基準のコントロールのリストができたので、全ての重大度スコアを検査します。 ratingに無効化したいスコアを指定し且つ有効化されているコントロールのみを新しいリストに追加します。

# 無効化したい重大度スコア
rating = ['MEDIUM', 'LOW']
# ratingに合致するarnのリスト
controls_arn_list = []
# controlsの重大度スコアが中以下のもの、かつ有効になっているもののarnをリストに
for controls in controls_list:
    if controls['SeverityRating'] in rating and controls['ControlStatus'] == 'ENABLED':
        controls_arn_list.append(controls['StandardsControlArn'])
        logger.info(controls['StandardsControlArn'] + 'is now enabled')
ステップ 4 (43~50行)

ステップ 3で無効化したいコントロールのリストができたので、状態を更新するメソッドupdate_standards_controlに 引数DISABLEDを与えて実行します。

# リスト化したarnをすべて無効化する
for controls_arn in controls_arn_list:
    securityhub.update_standards_control(
        StandardsControlArn=controls_arn,
        ControlStatus='DISABLED',
        DisabledReason='disable under medium'
    )
    logger.info(controls_arn + 'is disabled')

CloudFormationのYAMLファイル

作成リソースはCloudFormationで作成します。

  • 上記のLambda関数
  • Lambda関数へのIAMロール
  • EventBridgeルール
  • EventBridgeへのLambda実行権限

トリガーの設定として、毎日午前10時に起動するようにしています。 Cron形式で指定していますが、時間はUTCなので9時間前の時刻にする必要があります。

docs.aws.amazon.com

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  ResourceName:
    Type: String
    Default: securityhub-from-lambda

Resources:
  DisableSecurityHubLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Role: !GetAtt "LambdaExecutionRole.Arn"
      FunctionName: !Sub ${ResourceName}-disable-function
      Runtime: "python3.9"
      Handler: index.lambda_handler
      Timeout: "300"
      Code:
        ZipFile: |
          略
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: lambda-disable-securityhub-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - securityhub:*
                  # - securityhub:GetEnabledStandards
                  # - securityhub:DescribeStandardsControls
                  # - securityhub:UpdateStandardsControl
                Resource: "*"
  EventBridgeRule:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      Name: !Sub ${ResourceName}-eventbridge-rule
      ScheduleExpression: cron(00 1 * * ? *) # 毎日10時に起動
      State: ENABLED
      Targets:
        - Arn: !GetAtt DisableSecurityHubLambdaFunction.Arn
          Id: DisableSecurityHubLambdaFunction

  PermissionForEventsToInvokeLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref DisableSecurityHubLambdaFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EventBridgeRule.Arn

動作の確認

CloudWatchでLambdaが毎日10時に動作しているログが残っています。 また、重大度スコアがLowMediumのコントロールが無効化されいることも確認できました。

Lambda実行ログ

コントロール無効化の確認

まとめ

今回はSecurity Hubについて、重大度スコアが一定以下のコントロールを無効化することで、 より重要なものが残るような自動化スクリプトの紹介をしました。 APIを少々操作するだけのスクリプトですが、工夫を凝らすことで マルチアカウントやマルチリージョンでSecurity Hubを利用する際により活かせるのではと考えます。

末廣 満希(執筆記事の一覧)

2022年新卒入社です。ここに何かかっこいい一言を書くことができるエンジニアになれるように頑張ります。