Amazon GuardDutyをbacklogに連携する

Amazon Guard​Dutyの通知をみなさんはどうされていますか?色々な通知方法がある中から、Backlogに通知する方法をご紹介します。Backlogへの通知はAWS Lambdaを使い、LambdaのデプロイにはServerless Frameworkを使います。
2019.08.15

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

Amazon Guard​Dutyの通知をみなさんはどうされていますか?色々な通知方法がある中から、Backlogに連携する方法をご紹介します。Backlogへの連携はAWS Lambdaを使い、LambdaのデプロイにはServerless Frameworkを使います。

Backlogに通知し、担当者をアサインし対応内容を記載する

GuardDutyはセキュリティに関する脅威を検出するサービスです。GuardDutyで発生するイベントには、誤検知もありえます。私は普段東京オフィスからAWSコンソールを触っていますが、札幌オフィスに出張しAWSコンソールにログインすると、おそらくGuardDutyのイベントが発生します。普段とは異なるIPアドレスから接続することで検知されることは正常であるものの、実務的には誤検知になります。GuardDutyのイベントが誤検知か判断するために、なんらかの方法で通知を受け取り、担当者が判断する必要があります。backlogを利用すると、対応前/対応中/完了などのステータスを設定できたり、担当者をアサインできます。

backlog設定

backlogの設定と確認を行います。後ほど作成するLambdaでは、APIキーやスペースIDなどを使って課題を投稿します。

APIキーの発行

"個人設定">"API">"登録"から、APIキーを発行します。

スペースIDの確認

backlogのURLの一部(https://[スペースID].backlog.jp)がスペースIDになっています。

プロジェクトIDの確認

backlogのプロジェクト設定を開くとURLに、「project.id=12345」といった形で表示されます。

課題の作成とIDの確認

backlogのプロジェクト設定>種別>種別の追加から、「GuardDuty通知」のような種類を追加します。追加した種別を選択すると、「issueType.id=123456」といった形で表示されます。

プラグインによるパッケージ管理

LambdaはPython3.7で実行しました。 Lambda関数でrequestsパッケージを呼び出します。 パッケージ管理にserverless-python-requirementsプラグインを利用します。 serverlessのブログを参考にしました。プラグインとrequestモジュールをインストールします。

npm install --save serverless-python-requirements
pip install requests
pip freeze > requirements.txt

プラグインの実行にはDockerが必要です。 Dockerデーモンを起動しておきます。

providedテンプレートの作成

Serverless Frameworkの設定に移ります。以下のコマンドを実行すると、serverless.yml、handler.pyのテンプレートが作成されます。

serverless create -t aws-python3 -n guardduty2backlog

ファイルの記載

3つのファイルを作成します。

  • serverless.yml(serverless frameworkの設定ファイル)
  • handler.py(Lambda関数)
  • sample-guardduty-event.json(テスト用のCloudWatchイベントファイル)

serverless.yml

Serverless Frameworkの設定ファイルです。 Lambda関数の設定、CloudWatch Events、GuardDutyの有効化、serverless-python-requirementsプラグインの設定を記載しています。environmentには、backlog設定で確認した内容を記載します。

service: guardduty2backlog

provider:
  name: aws
  runtime: python3.7
  # you can overwrite defaults here
  stage: prod
  region: ap-northeast-1

# you can add packaging information here
package:
  exclude:
    - consolelogin.json

functions:
  main:
    handler: handler.main
    events:
      - cloudwatchEvent:
          event:
            source:
              - "aws.guardduty"
            detail-type:
              - "GuardDuty Finding"

#    Define function environment variables here
    environment:
      api_key: '' # backlog's API Key
      space_key: '' # classmethod backlog space key
      project_id: '' # backlog project id
      issue_type_id: '' # backlog issue type id.
      priority_id: '3' # backlog priority. normal = 3

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true

resources: # CloudFormation template syntax
  Resources:
    GDD:
      Type: "AWS::GuardDuty::Detector"
      Properties:
        Enable: true

handler.py

Lambda関数です。GuardDutyのイベントの内容を変数に格納してから、POSTします。BASE_URLは環境によって、backlog.combacklog.jpを選びます。

# -*- coding: utf-8 -*-
import requests
import os

BASE_URL = 'https://{space_key}.backlog.com/api/v2/{api}'
# BASE_URL = 'https://{space_key}.backlog.jp/api/v2/{api}'

api_key = os.environ['api_key']
space_key = os.environ['space_key']

def add_issue(_project_key, _issue_type_id, _priority_id, _summary, _description):
    api = 'issues'
    url = BASE_URL.format(space_key=space_key, api=api)
    payload={
        'projectId': _project_key,
        'issueTypeId': _issue_type_id,
        'priorityId': _priority_id,
        'summary': _summary,
        'description': _description
    }

    params = {
        'apiKey': api_key,
    }

    r = requests.post(url, params=params, data=payload)
    r.raise_for_status()
    return r

def main(event, context):
    project_id    = os.environ['project_id']
    issue_type_id = os.environ['issue_type_id']
    priority_id   = os.environ['priority_id']

    summary = str(event['detail-type'])
    if 'detail' in event:
        if 'title' in event['detail']:
            summary += "(" + str(event['detail']['title']) + ")"

    description = "version:" + str(event['version']) + '\n'

    messages_keys = ["account","time","region","detail-type"]
    for key in messages_keys:
        description += key + ":" + str(event[key]) + '\n'

    messages_detail_keys = ["accountId","type","title","desctiption","account","region","createdAt","updatedAt"]
    for key in messages_detail_keys:
        if 'detail' in event:
            if key in event['detail']:
                description += key + ":" + str(event['detail'][key]) + '\n'

    add_issue(project_id, issue_type_id, priority_id, summary, description)

sample-guardduty-event.json

CloudWatchイベントのルールの作成からAWS コンソールのサインインを選択すると、サンプルイベントを表示できます。jsonが表示されるのでコピーアンドペーストしsample-guardduty-event.jsonを作成します。

デプロイ

デプロイコマンドを実行します。

sls deploy

サンプルイベントを使って、テストします。 backlog課題が作成されれば、成功です。

sls invoke -f main -p consolelogin.json

GuardDutyは全てのリージョンで有効にすることが推奨されます。他のリージョンについても、デプロイします。オレゴンリージョンへのデプロイは以下のように行います。

sls deploy --region us-west-2

backlog運用イメージ

GuardDutyのイベントが発生すると以下のように課題が作成されます。

担当者をアサインし確認します。イベントの調査内容を記載したら、完了にします。

動作テストで発生した課題はまとめてクローズできます。

さいごに

Amazon Guard​Dutyのイベントをbacklogに連携する方法をご紹介しました。Guard​Dutyでイベントが発生したら誤検知か確認する必要があります。backlogに連携することでステータスや担当者、対応内容を管理できます。

参考

検証環境

Mac OSで以下のバージョンで検証しました。

$ serverless -v
Framework Core: 1.50.0
Plugin: 1.3.8
SDK: 2.1.0

$