🌟

Security Hubの検出結果をBacklogに自動起票する

に公開

はじめに

業務でSecurity Hubの検出結果を自動でBacklogに起票するという実装をしたため、内容を紹介します。

やりたいこと

1.SecurityHubの検出結果に、重大度がCRITICALもしくはHIGHのものが追加された場合に処理を開始する。
2.1の結果がInspectorのものかつCRITICALでない場合は処理を行わない。それ以外は、検出結果から必要な情報を取得し、翻訳した上でBacklogAPIを実行し起票する。

以下の形式で起票することとします。

## 概要
### 原文
〜

### 機械翻訳
〜

## 重要度
〜

## リソース
### タイプ
〜

### ARN
〜

## 推奨事項
~

実現方法

1をEventBridgeで、2をStep Functionsで実装しました。
翻訳が必要でない場合は、EventBridgeのAPIの送信先を活用することでEventBridgeのみで完結できますが、今回は翻訳や後述する問題に対応するためにStep Functionsを採用しました。

イメージ図

EventBridgeで検出結果を受け取り、その結果をStep Functionsで加工し起票するというシンプルな構成です。

実装

EventBridgeの実装

EventBridgeのイベントパターンを以下のように設定しました。

{
  "detail-type": ["Security Hub Findings - Imported"],
  "source": ["aws.securityhub"],
  "detail": {
    "findings": {
      "Workflow": {
        "Status": ["NEW"]
      },
      "Severity": {
        "Label": ["HIGH", "CRITICAL"]
      }
    }
  }
}

Security Hubのすべての新しい結果と既存の結果のすべての更新のうち、ステータスがNewかつ重大度がCRITICALもしくはHIGHのものにのみ合致するものに絞っています。これで新規の検出結果をモニタリングして後続処理を行うことができます。

ただし、ここで問題となってくるのが既存の結果のすべての更新の部分です。
例えば、ある検出結果について起票が完了したとします。しかし、Security Hubでは既存の結果も定期的に更新される仕様となっており、起票済なのに更新のタイミングで再度処理が行われ同じ内容の課題が何度も起票されてしまう、という事象が発生します。
この点に関しては、次の実装で対策を行っています。

また、EventBridgeのイベント形式に関しても考慮が必要です。
Security Hubのイベント形式は以下となっています。

{
   "version":"0",
   "id":"CWE-event-id",
   "detail-type":"Security Hub Findings - Imported",
   "source":"aws.securityhub",
   "account":"111122223333",
   "time":"2019-04-11T21:52:17Z",
   "region":"us-west-2",
   "resources":[
      "arn:aws:securityhub:us-west-2::product/aws/macie/arn:aws:macie:us-west-2:111122223333:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841"
   ],
   "detail":{
      "findings": [{
         <finding content>
       }]
   }
}

実際のデータでは、findingsに大量のデータが存在しています。
この検出結果をそのままStep Functionsに渡し必要なデータだけ処理を行うことも可能ですが、なるべくこの時点で加工して必要なデータを渡す方がStep Functionsでのデータの取り回しが簡単だと思ったため、EventBridge側で加工する方針としました。

どのように加工するのか?という点に関しては、EventBridgeの機能である入力トランスフォーマーを活用しました。
入力トランスフォーマーについて簡単に説明すると、EventBridgeが受け取った入力を任意の形式に加工し、ターゲットへの入力として使用できる機能です。

Step Functionsで処理を行うにあたり、必要なデータのみを渡すために以下の形式で設定しました。

入力パス

{
  "detail-findings-0--Description": "$.detail.findings[0].Description",
  "detail-findings-0--Id": "$.detail.findings[0].Id",
  "detail-findings-0--ProductArn": "$.detail.findings[0].ProductArn",
  "detail-findings-0--Remediation-Recommendation-Url": "$.detail.findings[0].Remediation.Recommendation.Url",
  "detail-findings-0--Resources-0--Id": "$.detail.findings[0].Resources[0].Id",
  "detail-findings-0--Resources-0--Type": "$.detail.findings[0].Resources[0].Type",
  "detail-findings-0--Severity-Label": "$.detail.findings[0].Severity.Label",
  "detail-findings-0--Title": "$.detail.findings[0].Title"
}

入力テンプレート

{
  "arn":<detail-findings-0--ProductArn>,
  "description":<detail-findings-0--Description>,
  "id":<detail-findings-0--Id>,
  "label":<detail-findings-0--Severity-Label>,
  "recommendation":<detail-findings-0--Remediation-Recommendation-Url>,
  "resource":<detail-findings-0--Resources-0--Id>,
  "resourceType":<detail-findings-0--Resources-0--Type>,
  "summary":<detail-findings-0--Title>
}

取得内容は以下の通りです。これらのデータを後続のStep Functionsに渡します。

arn:検出を行った製品のarn
description:検出結果のタイトル
id:検出結果ID
label:重大度
recommendation:AWSが提供している推奨事項サイトへのURL
resource:検出されたリソースのarn
resourceType:検出されたリソースの種別
summary:検出結果の概要

Step Functionsの実装

Step Functionsで実現したいことは以下の4つです。

  • Inspectorの場合の条件分岐
  • 翻訳
  • Backlogへの起票
  • 既存の結果が再処理されてしまう事象への対応

順番に実装を説明します。

Inspectorの場合の条件分岐

EventBridge側でフィルタリングできると楽だったのですが現状では不可能なようだったので、Step Functions側で実装しました。
前述した入力トランスフォーマーの結果を参照し、arnがInspectorのものかつCRITICALでない場合は処理を終了させ、それ以外は後続の処理へ移るよう分岐させています。

    "JudgeProductionAndLabel": {
      "Type": "Choice",
      "Choices": [
        {
          "And": [
            {
              "Variable": "$.arn",
              "StringMatches": "*inspector*"
            },
            {
              "Not": {
                "Variable": "$.label",
                "StringEquals": "CRITICAL"
              }
            }
          ],
          "Next": "Success"
        }
      ],
      "Default": "TranslateSummary"
    },

翻訳

外部APIの使用をなるべく避けたかったため、Amazon Translateを使用しました。
Step Functionsにアクションが用意されており、Translate: TranslateTextを使用しました。

実装では、タイトルと概要を英語から日本語に翻訳しています。

    "TranslateSummary": {
      "Next": "TranslateDescription",
      "Type": "Task",
      "ResultPath": "$.translated.summary",
      "Resource": "arn:aws:states:::aws-sdk:translate:translateText",
      "Parameters": {
        "SourceLanguageCode": "en",
        "TargetLanguageCode": "ja",
        "Text.$": "$.summary"
      }
    },
    "TranslateDescription": {
      "Next": "Call Backlog API",
      "Type": "Task",
      "ResultPath": "$.translated.description",
      "Resource": "arn:aws:states:::aws-sdk:translate:translateText",
      "Parameters": {
        "SourceLanguageCode": "en",
        "TargetLanguageCode": "ja",
        "Text.$": "$.description"
      }
    },

Backlogへの起票

前提として、BacklogAPIについて簡単に説明します。
BacklogAPIAPI Key方式OAuth2.0方式での認証をサポートしています。
呼び出し方は、https://{baseurl}/api/v2/{resource}の形式です。
今回は課題の追加のAPIを使用します。

続いて、Step Functions上での呼び出し方です。
Step Functionsは*HTTP Endpointアクションを使用した外部API呼び出しに対応しています。
そのため、本アクションの呼び出し先をBacklogAPIとして設定します。

以下のように実装しました。

    "Call Backlog API": {
      "Next": "UpdateFindingState",
      "Type": "Task",
      "ResultPath": "$.response",
      "Resource": "arn:aws:states:::http:invoke",
      "Parameters": {
        "ApiEndpoint": "https://〇〇〇.backlog.jp/api/v2/issues",
        "Authentication": {
          "ConnectionArn": "[EventBridgeの接続のARN]"
        },
        "Method": "POST",
        "Headers": {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        "RequestBody": {
          "summary.$": "States.Format('{} ({})', $.summary, $.translated.summary.TranslatedText)",
          "description.$": "States.Format('##概要\n###原文\n{}\n###機械翻訳\n{}\n##重要度\n{}\n##リソース\n###タイプ\n{}\n###ARN\n{}\n##推奨事項\n{}', $.description, $.translated.description.TranslatedText, $.label, $.resourceType, $.resource, $.recommendation)"
        },
        "Transform": {
          "RequestBodyEncoding": "URL_ENCODED",
          "RequestEncodingOptions": {
            "ArrayFormat": "INDICES"
          }
        }
      }
    },

BacklogAPIの課題追加のエンドポイントに対し、markdown形式で課題の内容を渡して課題を追加しています。
ConnectionArnの部分が注目ポイントです。
このアクションでは外部APIとの接続にEventBridgeの接続を使用しています。そのため、別途接続設定を行う必要があります。

設定は使いたいAPIによって変わると思いますが、今回は画像のように設定しました。
値はSecrets Managerから取得しています。APIキーの他に、どのプロジェクトに起票するか(projectId)、カテゴリー(issueTypeId)、優先度(priorityId)、担当者(assigneeId)を指定しています。指定可能なパラメータはAPIのページをご確認ください。

注意が必要な点として、認証タイプで設定しているAPIキー情報はダミーのものとなっています。
というのも、BacklogAPIでは認証情報をHTTPパラメータに付加する形の認証となっているため、呼び出しHttpパラメータでAPIキーをセットすれば良いです。
ただし、APIキーを空にして作成することができないため、ダミーの値を入れて対応しています。

既存の結果が再処理されてしまう事象への対応

Security Hubのワークフローステータスを活用して対応しました。
Security Hubでは検出結果の対応状況を把握できるようワークフローステータスというものがあり、以下のステータスがあります。

NEW:レビューする前の結果の初期の状態
NOTIFIED:セキュリティ問題についてリソース所有者に通知した状態
SUPPRESSED:結果をレビューし、アクションが必要だとは判断しなかった状態
RESOLVED:結果はレビューおよび修正され、現在は解決済みの状態

お気づきの方もいらっしゃるかもしれませんが、

NOTIFIED:セキュリティ問題についてリソース所有者に通知した状態

を活用することで本事象に対応しました。

問題の根本原因は、Security Hubの検出結果が定期的に更新される点でしたが、これを更新しないという設定は現状だと見つけることができませんでした。
そのため、通知済のものはステータスをNOTIFIEDに変更し、イベントパターンでステータスがNEWのものに限定することで、通知済のものを除外し完全に新規の検出結果のみ受け取ることができます。

実現方法はとても簡単で、Security Hub:BatchUpdateFindingsフローを使用するだけで実現可能です。
APIのパラメータに以下を渡し、処理対象の検出結果のワークフローをNOTIFIEDに更新しています。

{
  "FindingIdentifiers": [
    {
      "Id.$": "$.id",
      "ProductArn.$": "$.arn"
    }
  ],
  "Workflow": {
    "Status": "NOTIFIED"
  }
}

動作確認

実際に起票された結果です。問題無く起票されていることが確認できます。

まとめ

Security Hubの検出結果をBacklogに起票する実装について説明しました。
Security Hub上だと対応担当者や期日を管理するのは難しいため、Backlogで管理するという選択肢は大いにアリだと思っています。
シンプルなアーキテクチャですが、その分カスタマイズの幅も残されており柔軟に変更できるものになっているかと思います。
なお、業務では本アーキテクチャをCDK化するところまで実装しましたので、その内容は次回の記事に記載予定です。公開されましたら是非ご覧ください。

Fusic 技術ブログ

Discussion