🔧

SecurityHubの自動修復アクションをカスタムして導入してみた話

2024/08/13に公開

はじめに

Septeni Japan株式会社でプロダクト開発&セキュリティエンジニアの市原と申します。前回はSecurityHubの運用フローをアップデートした話をしました。

https://zenn.dev/septeni_japan/articles/c9c3aee46f4e29

今回は更にもう一歩踏み込んで、SecurityHub で検出されたセキュリティリスクの修復を自動化することで、SecurityHub での検出から数分以内という極めて短いリードタイムで修復を行い、開発チームのタスク自体を発生させない状態を実現した話となります。

前提

  • AWS Organizations 経由でAuditアカウントにSecurityHub の管理権限を委任することで、マルチアカウント運用を実現できている状態
  • Audit アカウント・メンバーアカウントに「AWS提供の自動修復ソリューション」を導入するための、Cloud Formation の実行権限やロールの作成権限などを持っている状態

AWS 提供の自動修復ソリューションの概要


出処: Automated Security Response on AWS Implementation Guideより引用

  1. SecurityHubによる脆弱性の検出
  2. 検出結果に対応した EventBridge が起動
  3. Step Functionsが自動修復を実行する Lambda関数を呼び出す
  4. メンバーアカウントのSSM Document を参照して自動修復を実行
  5. SNSに結果を送信すると共に、SecurityHubのワークフローステータスを「RESOLVED」に変更

発生した課題

「AWS 提供の自動修復ソリューション」を適用すればそれで自動修復ソリューションの対応は終わりかと思いきや、我々が想定していた挙動と異なる部分があり、デフォルトの仕様では導入できないことが分かりました。その課題とは、例外として「抑制済み」にしたリソースに対しても自動修復アクションが動作してしまうため、例外ケースの運用が一切できなくなるという点でした。

解決策の概要

そこで、「AWS 提供の自動修復ソリューション」をカスタマイズすることで、「抑制済み」の場合は自動修復の実行をスキップする仕様に変更を行いました。

通常の状態遷移パターン

  1. ユーザーによるリソースの作成・変更
  2. AWS SecurityHubによる検出
  3. 自動修復アクションによるリソースの修復

解決後の状態遷移パターン

  1. ユーザーがリソースの作成・変更
  2. AWS SecurityHubによる検出
  3. 自動修復アクションによるリソースの修復
  4. ユーザーが検出結果のワークフローステータスを抑制済みに変更
  5. 自動修復されたリソースの設定を元に戻す
  6. 状態としては非準拠となるが自動修復アクションは動作しない
  7. 再度自動修復の対象としたい場合はワークフローステータスを解決済みに変更

解決策の詳細

スキップ処理を適用したコードは Github に公開していますので、カスタマイズされる際の参考なれば幸いです。なお、ソリューションのデプロイ方法は幾つかありますが、当社では CloudFormation StackSetsを利用しています。詳細は こちらのページを参照してください。
https://github.com/septeni/infra-automated-security-response-on-aws/issues
※デプロイ方法はAutomated Security Response on AWSのGitHubのREADMEを参照してください

それでは、主な改修箇所を紹介していきます。

Orchestrator の Step Functions にステップを追加



デフォルトの状態では、Finding WorkFlow State NEW? の後に、Get Remediation Approval Requirement に処理が流れますが、その間に以下2つのステップを追加しました。

  1. Get Latest Finding Workflow SUPPRESSED: リソースが「抑制済み」かどうかの判定
  2. Remediation skip or execute?: 「抑制済み」の場合に処理をスキップ

1. Get Latest Finding Workflow SUPPRESSEDの処理

対象のFindingのうち、同じアカウントID、かつ、同じリソースで直近のFindingのワークフローステータスがSUPPRESSEDであれば、SKIPPEDをメッセージと共にレスポンスの中に含める。それ以外の場合はEXECUTEをメッセージと共にレスポンスに含める。

source/Orchestrator/get_last_workflow_state_suppressed.py 46行目
if latest_workflow_status is None:
    return {"status": "EXECUTE", "message": "Past findings not found."}
elif latest_workflow_status == "SUPPRESSED":
    return {"status": "SKIPPED", "message": "Latest finding is suppressed."}
else:
    return {"status": "EXECUTE", "message": f'Latest finding is {latest_workflow_status}.'}
source/lib/common-orchestrator-construct.ts 376行目
checkWorkflowNew.when(
  sfn.Condition.or(
    sfn.Condition.stringEquals('$.EventType', 'Security Hub Findings - Custom Action'),
    sfn.Condition.and(
      sfn.Condition.stringEquals('$.Finding.Workflow.Status', 'NEW'),
      sfn.Condition.stringEquals('$.EventType', 'Security Hub Findings - Imported'),
    ),
  ),
  getLastWorkflowStateSuppressed,
);
checkWorkflowNew.otherwise(docNotNew);

getLastWorkflowStateSuppressed.next(checkExecSkip);

2. Remediation skip or execute?の処理

Get Latest Finding Workflow SUPPRESSEDでレスポンスに追加したStateがEXECUTEであれば、getApprovalRequirementへ、SKIPPEDであれば、notifyへ分岐させる。

source/lib/common-orchestrator-construct.ts 226行目
const checkExecSkip = new sfn.Choice(this, 'Remediation skip or execute?');
source/lib/common-orchestrator-construct.ts 390行目
checkExecSkip.when(sfn.Condition.stringEquals('$.Notification.State', 'EXECUTE'), getApprovalRequirement);
checkExecSkip.when(sfn.Condition.stringEquals('$.Notification.State', 'SKIPPED'), notify);

CloudFormation StackSetsで複数リージョンにデプロイできるように修正

CloudFormation StackSets を使用することで、複数のメンバーアカウントに対して一括でソリューションをデプロイすることが可能となります。

しかし、複数のリージョンにデプロイしようとすると Orchestrator RoleCloudWatch Dashboardを作成する工程で、「作成しようとしているリソースと、同一のリソースが既に存在する」というエラーが発生しました。これは通常、RoleやCloudWatch Dashboardはグローバルで管理されており、リージョン毎にリソースを作成する際に重複したリソースとしてエラーになるためです。

こちらの回避策として、リソース名の末尾にリージョン名を記載する処理を追加しました。

Orchestrator Role

source/lib/solution_deploy-stack.ts 214行目
const orchestratorRole = new Role(this, 'orchestratorRole', {
  assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
  description: 'Lambda role to allow cross account read-only SHARR orchestrator functions',
  roleName: `${RESOURCE_PREFIX}-SHARR-Orchestrator-Admin-${this.region}`,
});

CloudWatch Dashboard

source/lib/cloudwatch_metrics.ts 228行目
const remediationDashboard = new Dashboard(scope, 'RemediationDashboard', {
  dashboardName: `ASR-Remediation-Metrics-Dashboard-${region}`,
  defaultInterval: Duration.days(7),
});

まとめ

「AWS 提供の自動修復ソリューション」をカスタマイズすることにより、自動修復アクションによる迅速なセキュリティリスクへの対処と、自動修復を適用外としたい例外ケースをうまく融合させることができました。

今後の課題

新規で作成したアカウントを自動修復ソリューションの適用対象とするには、以下のような手作業が発生するため、自動化を検討したいと考えています。

  1. 対象アカウントにCloudFormation StackSetsの実行ロールを付与
  2. CloudFormation StackSets のパラメータに新規作成したアカウントIDを登録して実行

Discussion