こんにちは、Orca Security 担当の尾谷です。
Orca Security と Backlog の自動連携方法ということで、Orca Cloud Security Platform でアラートを検知したら、自動で Backlog に課題を起票する仕組みと、Backlog で課題を完了ステータスに変更したら、関連する Orca アラートを自動でクローズする仕組みを作りましたので、ご紹介させていただきます。
構成図
本ブログでご紹介するのは、以下のような構成イメージです。
項番の 1 と 2 が、Orca Cloud Security Platform をトリガーに Backlog チケットを起票するまでのフローで、3 と 4 が、Backlog をトリガーに Orca のチケットを Closed にするまでのフローです。
- Orca Automations
Orca で High 以上のアラートを検知したら、Automations の AWS SQS でキューを送信します。 - Backlog API
Amazon Simple Queue Service のキューをトリガーに、AWS Lambda 関数が呼び出され、Backlog API を用いて課題を起票します。
この際、アラート ID を Backlog 課題のカスタム属性フィールドに登録します。 - Backlog Webhook
Backlog の課題を完了したら、Webhook で API Gateway のエンドポイントをコールします。
この際、課題のカスタム属性フィールドに登録された アラート ID を API コール時のパラメーターに指定します。 - Orca API
API Gateway に紐付けた統合 Lambda が、Orca アラート ID をキーにチケットを Orca のアラートのステータスを Closed にします。
Orca Automations
Orca で High アラート以上を検知したら、AWS SQS を実行する Automations を作成しました。
Orca Automations - AWS SQS の使い方は以下ブログ記事でご紹介しています。
Webhook での連携も検討しましたが、万が一、大量にアラートを検知した際に、通知対象を取りこぼさないよう、Amazon SQS のキューに入れることにしました。
Amazon Simple Queue Service
SQS キューに Lambda トリガーを設定しました。
Lambda 関数には以下の SQS を操作する IAM ポリシーを追加しました。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ReceiveMessage", "Effect": "Allow", "Action": [ "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes" ], "Resource": "arn:aws:sqs:ap-northeast-1:123456789012:amazon-sqs-queue" } ] }
Lambda 関数には、SQS から以下のようなイベントが届きます。body の中に Orca のアラート情報が含まれます。
Value を削除しているので、シンプルに見えますが、body は非常に多くのデータが含まれます。
{ "Records": [ { "messageId": "", "receiptHandle": "", "body": "", "attributes": { "ApproximateReceiveCount": "", "SentTimestamp": "", "SenderId": "", "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "", "awsRegion": "" } ] }
※ Value は削除して Key のみ記載しています。今後、Orca Security の改修作業によって仕様が変更される可能性があります。
body の中に含まれる Orca アラートの情報は、以下のような形で取り出せます。
import os import json import requests def lambda_handler(event, context): # event から Orca のデータを取得 arr_records = event.get('Records') str_body = arr_records[0].get('body') json_body = json.loads(str_body) json_data = json_body.get('data') # アラートタイトル str_title = json_data.get('title') # アラートの概要 str_details = json_data.get('details') # アラート ID str_alert_id = json_body.get('state').get('alert_id')
※ 見やすいようにコメントを入れて、エラー分岐は省略しています。
Lambda レイヤー
Requests は、Lambda レイヤーで Klayers-p312-arm64-requests を指定しました。
以下、ARN です。
arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p312-arm64-requests:7
Backlog API
1 にて、SQS イベントからデータが抽出できました。
コードで用意した変数とその内容をご紹介します。
- str_title:
Orca アラートのタイトルです。Backlog のタイトルにします。 - str_details:
Orca アラートの詳細です。Backlog の本文に入力します。 - str_alert_id:
Orca アラート ID です。後ほど Backlog の課題を完了にした際にパラメーターとして利用します。
データが揃ったので、Backlog の課題を起票する部分をご紹介します。
Backlog 課題を追加する Lambda 関数
Backlog API は、以下のサイト で公開されています。
このリファレンスの中で利用したのは、課題一覧の取得 と 課題の追加、課題コメントの追加、課題情報の更新 の 4 つです。
最初は、課題を追加するだけの Lambda 関数をコーディングしていましたが、アラートが重複して起票される不具合があったので改修しました。
既に起票していないかチェック
Orca は、アラートが Closed ステータスになると、そのリスクが本当に解消されたか検証をします。
リスクが解消されていない場合は、アラートを再度 Open ステータスや In Progress ステータスに変更します。
Lambda 関数をただ、課題を起票するだけの処理でコーディングしてしまうと、Orca アラートのステータスが Open に戻ったタイミングで Backlog 課題が二つ起票されてしまいます。
そこで Lambda 関数に、Backlog 課題の起票前にアラート ID を含む Backlog 課題が既に起票されていないか確認する処理を入れました。
api_key = os.environ['BACKLOG_API'] project_id = os.environ['PROJECT_ID'] workgroup_name = os.environ['WORKGROUP_NAME'] headers = { 'Content-Type': 'application/x-www-form-urlencoded' } # アラート ID を含むアラートが存在するか確認する backlog_url = f"https://{workgroup_name}.backlog.jp/api/v2/issues?apiKey={api_key}&projectId[]={project_id}&customField_110282={alert_id}" r = requests.get(backlog_url, headers = headers)
以下、用意した変数と、環境変数などをご紹介します。
Backlog API Key
コード内の変数名: api_key
Backlog の API キーは個人設定から発行できます。
Project ID
コード内の変数名: project_id
Project ID は、Backlog プロジェクトを開き、プロジェクト設定をクリックすると見つかります。
URL に、?project.id=000000
と表記されます。
もし、課題が見つからない場合は、新規で課題を追加し、課題が既に登録されていた場合は、コメントを記載します。
Workgroup 名
コード内の変数名: workgroup_name
Workgroup 名は、Backlog のワークグループ名です。
これも、URL を参照します。
https://XXX.backlog.jp
となっているかと思います。この XXX がワークグループ名です。
Backlog 課題の起票
data = { 'projectId': project_id, 'summary': str_title, 'description': str_details, 'issueTypeId': 000000, 'priorityId': 1, 'customField_110282': alert_id } # 課題を起票する backlog_url = f"https://{workgroup_name}.backlog.jp/api/v2/issues?apiKey={api_key}" r = requests.post(backlog_url, data = data, headers = headers)
Backlog 課題コメントを追記
data = { 'content': f"アラート: {alert_id} がリオープンしました。' } # コメント追記 backlog_url = f"https://{workgroup_name}.backlog.jp/api/v2/issues/{str_keyId}/comments?apiKey={api_key}" r = requests.post(backlog_url, data = data, headers = headers)
Backlog 課題のステータス変更 (完了から処理中に変更)
# 課題のステータス変更 data = { 'statusId': '2' } # 課題の更新 backlog_url = f"https://{workgroup_name}.backlog.jp/api/v2/issues/{str_keyId}?apiKey={api_key}" r = requests.patch(backlog_url, data = data, headers = headers)
※ ステータスの変更メソッドは、POST ではなく、PATCH でした。
カスタム属性フィールドの ID
コード内の変数名: custom_fid
Orca のアラートは、Close するとそのアラートのリスクが本当に解消しているか検証を行います。
以前はクローズしたアラートがゾンビのように戻ってくると、問い合わせをよくいただきましたが、現在は、Closed オプションを選択するとメッセージが表示されるように UI が改修されました。
一度 Backlog 課題として起票したアラートが、Closed 処理後に検証され、リオープンした際に再度 Backlog 課題が起票されてしまうと課題が重複してしまいます。
そこで、Backlog にカスタム属性フィールドを追加しアラート ID をパラメーターとして渡せるようにしました。
カスタムフィールドの ID はプロジェクト設定のカスタム属性を開き、URL を見ると確認できます。
今回検証で作成したカスタムフィールド ID は、110282 でした。
そのため、API で指定するパラメーター名は、customField_110282 になります。
優先度
コード内の変数名: priorityId
優先度は、1: 高, 2: 中, 3: 低 から選択します。
種別の ID
コード内の変数名: issueTypeId
種別は Backlog 課題を起票する際に必須となるパラメーターです。
プロジェクト設定の種別を開き、URL を見ると確認できます。
チケットがリオープンしたときは、こんな感じで更新されます。
Backlog Webhook
Backlog 課題をクローズした際の処理です。
API Gateway をデプロイして、エンドポイントを払い出し、Backlog Webhook の WebHook URL に設定しました。
統合 Lambda のコードを紹介します。
event 処理
コードの冒頭です。
Backlog Webhook を用いて送信されたデータは、event で届きます。
import json import requests def lambda_handler(event, context): # event から body (文字列) を取得 alert_id = '' str_body = event.get('body') if not str_body: print('This event is no body.') return json_body = json.loads(str_body) if json_body.get('content').get('status').get('name') != '完了': print('This event is not a closed ticket.') return # カスタム属性フィールドチェック arr_custom_field = json_body.get('content').get('customFields') for f in arr_custom_field: if f.get('field') == 'Alert-Id': alert_id = f.get('value') if not alert_id: print('This event do not has a Alert-id.') return
※ 見やすいようにコメントを入れて、エラー分岐は省略しています。
課題が完了ステータスに変更された場合のみ処理するようにしました。
Alert-id が含まれない課題がクローズされたときは、対象外にしました。
Backlog Webhook から届く課題の情報
Backlog Webhook で取得した event には、body がありまます。body には、文字列型で課題の情報が入っています。
以下、body に含まれるステータス部分です。
name に完了が記述されている場合は、後続の処理を行います。
"status": { "id": 4, "projectId": 126588, "name": "完了", "color": "#b0be3c", "displayOrder": 4000 },
Orca API
最後は、Orca API を使ってアラートを Close する処理について解説します。
Orca API でアラートを Closed にする
以下のような形でアラートのステータスを変更しました。
Orca API は Swagger が用意されていて、GUI で試せます。
以下、ブログ記事で藤原さんが詳しく紹介しています。
API Token の払い出し方法は、こちらのブログ記事 でご紹介しています。
Orca API でアラートをクローズする方法
以下、コーディングしました。
orca_api_url = f"https://{orca-tenant-url}/api/alerts/{alert_id}/status/close" data = {} headers = { 'Content-Type': 'application/json', 'accept': 'application/json', 'Authorization': f"Token {api_token}, 'x-orca-ui-origin': 'true' } response = requests.put(orca_api_url, headers=headers, json=data) response.raise_for_status()
まとめ
如何でしたでしょうか。
かなり長くなりましたが、Orca で検知したアラートを自動で Backlog に課題を起票する仕組みと、Backlog で課題を完了ステータスに変更したら、関連する Orca アラートを自動でクローズする仕組みのご紹介でした。
Orca Security にはアラート管理機能も付与されており、PagerDuty や ServiceNow、Jira との連携機能は標準で用意されています。
一方で、日本のお客様ですと、Backlog で管理したいといったニーズも一定数あるかと思います。
エラー分岐など、かなり省略して記載しましたので、お分かりにならない点も多々あると思いますし、弊社にて組み込むことも可能ですので、是非、お声がけください。