EventBridge API Destinationsを使ってGuardDuty検知をBacklogに自動起票してみた

2022.09.27

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

GuardDuty検知をBacklogの課題に自動起票する仕組みを作ってみました。

ちなみに本ブログは、以下ブログの「GuardDuty」版です。

EventBridgeのイベントパターン、入力トランスフォーマー設定が違うのみで、 他の部分(API Destinations設定など)は殆ど同じです。

なので共通部分やマネコンからの作成手順は割愛します。

イメージ図

アーキテクチャイメージは以下のとおり。 GuardDutyの検知イベントをSecurity Hub経由で取得します(注1)。 EventBridgeから、API Destinationを使ってBacklogへ課題を起票します。

img

(注1)GuardDutyとSecurity Hubはデフォルトで統合されています。 Security Hubにはリージョン集約機能があります。Security Hub経由にすることで、その恩恵を活用できます。

Backlog起票時のイメージはこんな感じ。

img

作ってみる

細かい手順は Security Hub版ブログ をご覧ください。 主にSecurity Hub版との差分を説明します。

また、構築に使ったTerraformコードを後ろの 『補足(構築時のコード)』 に記載しています。

EventBridge設定(イベントパターン)

イベントパターンは以下のとおりです。

{
  "detail-type": ["Security Hub Findings - Imported"],
  "source": ["aws.securityhub"],
  "detail": {
    "findings": {
      "ProductName": ["GuardDuty"],
      "RecordState": ["ACTIVE"],
      "Workflow": {
        "Status": ["NEW"]
      },
      "Severity": {
        "Label": ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
      }
    }
  }
}
  • Security Hub経由のGuardDutyイベント
  • レコードが Active
  • ワークフローのステータスが NEW
  • 全ての重要度

上記に一致するイベントが対象になります。

EventBridge設定(入力トランスフォーマー)

次にターゲット設定です。 「入力トランスフォーマー」以外は Security Hub版ブログ と同じです。

入力トランスフォーマーの「入力パス」、「テンプレート」設定は以下のとおりです。

入力パス

{
  "AwsAccountId": "$.detail.findings[0].AwsAccountId",
  "Description": "$.detail.findings[0].Description",
  "FindingType": "$.detail.findings[0].Types[0]",
  "Id": "$.detail.findings[0].Id",
  "Severity": "$.detail.findings[0].Severity.Label",
  "SourceUrl": "$.detail.findings[0].SourceUrl",
  "Title": "$.detail.findings[0].Title"
}

テンプレート

{
  "projectId": "1234567890",
  "summary": "<FindingType> found at AWS account <AwsAccountId>",
  "description": "<Title><br /><br />Description:<br /><Description><br /><br />Severity:<br /><Severity><br /><br />Links:<br />[GuardDuty](<SourceUrl>)",
  "issueTypeId": "1234567890",
  "priorityId": "3"
}

動作確認

GuardDutyにて サンプル検出結果 を生成しました。

しばらくするとBacklogに以下課題が自動起票されました。

img

---
- title: Unusual Behaviors/VM/Behavior:EC2-TrafficVolumeUnusual found at AWS account 123456789012
---

Unusually large amount of network traffic from EC2 instance i-99999999.

Description:
EC2 instance i-99999999 is generating unusually large amounts of network traffic to remote host 198.51.100.0.

Severity:
MEDIUM

Links:
[GuardDuty](https://ap-northeast-1.console.aws.amazon.com/guardduty/home?region=ap-northeast-1#/findings?macros=current&fId=14examplef4a5)

おわりに

GuardDuty検知をBacklogの課題に自動起票する仕組みを作ってみました。

「驚異検知の対応〜記録」を効率化するためのパーツとして活用できると思います。

参考

補足(構築時のコード)

terraform.tfvars

こちらのGitHub Gistにも上げています。

backlog_issues_url    = "https://xx.backlog.jp/api/v2/issues?apiKey=abcdefghijklmn"
backlog_project_id    = "1234567890"
backlog_issue_type_id = "1234567890"
backlog_priority_id   = "3"

main.tf

こちらのGitHub Gistにも上げています。

### Provider
provider "aws" {
  region = "ap-northeast-1"
}

### Locals
locals {
  prefix = "test"
}

### Variables
variable backlog_issues_url {}
variable backlog_project_id {}
variable backlog_issue_type_id {}
variable backlog_priority_id {}

### Resources(IAMロール)
resource aws_iam_role backlog {
  name = "${local.prefix}-backlog-events-role"

  assume_role_policy = <<-EOF
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Effect": "Allow"
        }
      ]
    }
  EOF

  inline_policy {
    name   = "aws-actions"
    policy =  <<-EOF
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
              "events:InvokeApiDestination"
            ],
            "Resource": "${aws_cloudwatch_event_api_destination.backlog.arn}"
          },
          {
              "Effect": "Allow",
              "Action": [
                  "secretsmanager:CreateSecret",
                  "secretsmanager:UpdateSecret",
                  "secretsmanager:DescribeSecret",
                  "secretsmanager:DeleteSecret",
                  "secretsmanager:GetSecretValue",
                  "secretsmanager:PutSecretValue"
              ],
              "Resource": "arn:aws:secretsmanager:*:*:secret:events!connection/*"
          }
        ]
      }
    EOF
  }
}

### Resources(EventBridge)
# Connection
resource aws_cloudwatch_event_connection backlog {
  name               = "${local.prefix}-backlog"
  authorization_type = "API_KEY"

  auth_parameters {
    api_key {
      key   = "dummy-key"
      value = "dummy-value"
    }
  }
}

# API Destination
resource aws_cloudwatch_event_api_destination backlog {
  name                             = "${local.prefix}-backlog-issues"
  invocation_endpoint              = var.backlog_issues_url
  http_method                      = "POST"
  invocation_rate_limit_per_second = 2
  connection_arn                   = aws_cloudwatch_event_connection.backlog.arn
}

# EventBridgeルール
resource aws_cloudwatch_event_rule guardduty {
  name           = "${local.prefix}-security-events-guardduty"
  event_bus_name = "default"

  event_pattern = <<-EOF
    {
      "detail-type": ["Security Hub Findings - Imported"],
      "source": ["aws.securityhub"],
      "detail": {
        "findings": {
          "ProductName": ["GuardDuty"],
          "RecordState": ["ACTIVE"],
          "Workflow": {
            "Status": ["NEW"]
          },
          "Severity": {
            "Label": ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
          }
        }
      }
    }
  EOF
}

resource aws_cloudwatch_event_target guardduty {
  event_bus_name = "default"
  rule           = aws_cloudwatch_event_rule.guardduty.name
  target_id      = "backlog"
  arn            = aws_cloudwatch_event_api_destination.backlog.arn
  role_arn       = aws_iam_role.backlog.arn

  input_transformer {
    input_paths = {
      Description  = "$.detail.findings[0].Description",
      Id           = "$.detail.findings[0].Id",
      Title        = "$.detail.findings[0].Title",
      Severity     = "$.detail.findings[0].Severity.Label",
      SourceUrl    = "$.detail.findings[0].SourceUrl",
      FindingType  = "$.detail.findings[0].Types[0]",
      AwsAccountId = "$.detail.findings[0].AwsAccountId"
    }
    input_template = <<-EOF
      {
        "projectId": "${var.backlog_project_id}",
        "summary": "<FindingType> found at AWS account <AwsAccountId>",
        "description": "<Title><br /><br />Description:<br /><Description><br /><br />Severity:<br /><Severity><br /><br />Links:<br />[GuardDuty](<SourceUrl>)",
        "issueTypeId": "${var.backlog_issue_type_id}",
        "priorityId": "${var.backlog_priority_id}"
      }
    EOF
  }
}