CloudFormation StackSetsでSecurity Hubのコントロール無効化をOrganizations組織全体に適用する

2023.07.10

AWS事業本部のイシザワです。

先日のアップデートでSecurity Hubのコントロール無効化をCloudFormationで設定できるようになりました。

これを利用して、コントロール無効化をOrganizations組織全体に適用してみようと思います。

設定内容

以下の設定を適用します。

  • Organizations管理アカウントをSecurity Hubの管理アカウントとする
  • 全アカウントで、セキュリティ標準は「AWS 基礎セキュリティのベストプラクティス v1.0.0」と「CIS AWS Foundations Benchmark v1.4.0」のみを有効化
  • 全アカウントで、両セキュリティ標準のコントロール IAM.6 を無効化
  • 適用先リージョンはバージニア北部リージョンのみ

手順

全アカウント・全リージョンでSecurity Hubが無効化されている状態を初期状態とします。手順はバージニア北部リージョンで実施します。

Organizations管理アカウントでのSecurity Hubの有効化

管理アカウントでSecurity Hubを有効化します。このとき、セキュリティ基準の「AWS 基礎セキュリティのベストプラクティス v1.0.0 を有効にする」と「CIS AWS Foundations Benchmark v1.2.0 を有効にする」に入っているチェックを外します。

委任管理者の設定

Security Hubコンソールの左ペインの「設定」をクリックし、設定欄の「一般」タブを選択します。 委任された管理者の設定項目の「委任された管理者アカウントID」にOrganizations管理アカウントのアカウントIDを入力し、「委任」をクリックします。

これにより、Security HubのOrganizations統合が有効化されます。

メンバーアカウントでのSecurity Hubの有効化

設定で「アカウント」タブを選択し、「Auto-enable default standards」をオフにして、上部バーの「このリージョンで組織のSecurity Hubを有効にする」の「有効化」をクリックします。

これにより、他メンバーアカウントでSecurity Hubが有効化され、新規アカウントでSecurity Hubが自動で有効化されるようになります。 「Auto-enable default standards」をオフにしたことで、新規に有効化されたSecurity Hubではデフォルトのセキュリティ標準が有効化されません。 これはCloudFormationでデプロイするセキュリティ標準と設定が競合することを防ぐために実施しています。

メンバーアカウントのSecurity Hubの設定

以下のCloudFormationテンプレートをCloudFormation StackSetsでメンバーアカウントに適用します。

カスタムリソース SecurityHubEnablingWaiter はセキュリティ標準のデプロイをSecurity Hubの有効化後に行うことを確実にするためのリソースです。 特にStackSetsの自動デプロイの際は、このリソースが無いとSecurity Hubの自動有効化の前にスタックのデプロイが実行されて失敗することがあります。

enable-security-hub-standards.yml

AWSTemplateFormatVersion: 2010-09-09
Resources:
  FSBPStandard:
    Type: AWS::SecurityHub::Standard
    DependsOn: SecurityHubEnablingWaiter
    Properties:
      StandardsArn: !Sub "arn:aws:securityhub:${AWS::Region}::standards/aws-foundational-security-best-practices/v/1.0.0"
      DisabledStandardsControls:
        - StandardsControlArn: !Sub "arn:aws:securityhub:${AWS::Region}:${AWS::AccountId}:control/aws-foundational-security-best-practices/v/1.0.0/IAM.6"
          Reason: "No checked"

  CISv14Standard:
    Type: AWS::SecurityHub::Standard
    DependsOn: SecurityHubEnablingWaiter
    Properties:
      StandardsArn: !Sub "arn:aws:securityhub:${AWS::Region}::standards/cis-aws-foundations-benchmark/v/1.4.0"
      DisabledStandardsControls:
        - StandardsControlArn: !Sub "arn:aws:securityhub:${AWS::Region}:${AWS::AccountId}:control/cis-aws-foundations-benchmark/v/1.4.0/1.6"
          Reason: "No checked"

  SecurityHubEnablingWaiter:
    Type: Custom::SecurityHubEnablingWaiter
    Properties:
      ServiceToken: !GetAtt CustomResourceFunction.Arn

  CustomResourceFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub security-hub-enabling-waiter-function-${AWS::Region}
      Code:
        ZipFile: |
          import cfnresponse
          import boto3
          import time
          
          client = boto3.client('securityhub')
          response_data = {}
          
          def handler(event, context):
              print(event)
              if event['RequestType'] == 'Create':
                  while True:
                      try:
                          client.describe_hub()
                          print("## Security Hub is subscribed.")
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                          break
                      except client.exceptions.InvalidAccessException:
                          print ("## Security Hub is not subscribed. Sleep 10 seconds.")
                          time.sleep(10)
                      except Exception as e:
                          print(e)
                          cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
                          break
              else:
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
      Handler: index.handler
      MemorySize: 128
      Role: !GetAtt FunctionRole.Arn
      Runtime: python3.10
      Timeout: 300

  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub security-hub-enabling-waiter-role-${AWS::Region}
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: describe-security-hub-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: securityhub:DescribeHub
                Resource: "*"

以下のコマンドでメンバーアカウントにセキュリティ標準をデプロイします。

# スタックセットの作成
aws cloudformation create-stack-set \
  --stack-set-name enable-security-hub-standards \
  --template-body file://enable-security-hub-standards.yml \
  --permission-model SERVICE_MANAGED \
  --capabilities CAPABILITY_NAMED_IAM  \
  --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=true

# スタックインスタンスの作成
aws cloudformation create-stack-instances \
  --stack-set-name enable-security-hub-standards \
  --regions us-east-1 \
  --deployment-targets OrganizationalUnitIds=$(aws organizations list-roots --query 'Roots[].Id' --output text) \
  --operation-preferences MaxConcurrentPercentage=100,FailureTolerancePercentage=100

管理アカウントのSecurity Hubの設定

サービスマネージド型のStackSetsではOrganizations管理アカウントにデプロイできないので、以下のコマンドでセキュリティ標準をデプロイします。

# スタックの作成
aws cloudformation create-stack \
  --stack-name enable-security-hub-standards \
  --template-body file://enable-security-hub-standards.yml \
  --capabilities CAPABILITY_NAMED_IAM

複数リージョンにデプロイする場合は、セルフマネージド型のStackSetsでデプロイするのが効率的です。

設定内容の確認

各メンバーアカウントにて、セキュリティ標準で「AWS 基礎セキュリティのベストプラクティス v1.0.0」と「CIS AWS Foundations Benchmark v1.4.0」が有効化されていることが確認できます。

また、各セキュリティ標準でコントロールIAM.6が無効化されていることを確認できます。

終わりに

CloudFormationでセキュリティ標準の設定が可能になったので、CloudFormation StackSetsで組織全体に設定を反映させてみました。 今までは、組織全体でのコントロールの無効化には複雑な仕組みが必要でしたが、このアップデートによってシンプルに設定できるようになったと思います。

既にSecurity Hubが有効化済みの場合は、デプロイするセキュリティ標準を事前に無効化していないとデプロイ時にエラーがでるのでご注意ください。

このブログが誰かの助けになれば幸いです。