はじめに
こんにちは、MSPの田所です。
我々の住む情報社会では、もっと情報を絞りたい、もっと情報を見やすくしたい、というのは普遍的な悩みかと思います。
そんな時は、不要な情報を遮断し、必要な情報を最適化したいものです。
今回は Trend Micro Cloud One – Workload Security (C1WS) の情報をフィルター、加工した話です。
Amazon SNS と AWS Lambda を使用するよくあるユースケースになるかと思います。
どうぞお付き合いください。
C1WS メールインテグレーションの悩み
C1WS には、サーバーに対する攻撃や変更のイベントを検知し、メール通知する機能があります。
その際に、イベントの種類を限定してアラートを作成することはできるのですが、その条件を細かくフィルターすることはできません。
侵入防御や変更監視などの指定はできますが、ルール番号や重要度などによるフィルターはできません。

そのため以下のような悩みが生じます。
1. 情報過多
全てのアラートが上がってくるため、見る必要がないと分かっているものも含まれてしまいます。
10件のアラートが上がっているのに、本当に見るべきは1件、のような状況が起こりえます。
その1件だけにフィルターしたいですね。

2. 情報不足
通知されたアラートメールには基本的な情報が記載されますが、載っていない情報は C1WS コンソールに入って確認する必要があります。
イベントに対して設定した処理内容やインスタンスのOS情報など、その情報さえあれば対応判断ができるのに、、!
という状況が起こりえます。
必要な情報をカスタマイズして記載したいですね。

解決のステップ
フィルターと加工を実装して悩みを解決します。
1. フィルター
C1WS にはメールインテグレーションの他に、Amazon SNS に通知することができます。
その時に JSON 形式のフィルターを設定することができます。
そこではイベントの種類をはじめ、ルール番号や重要度など細かい条件のフィルターを作成可能です。
今回は重要度が「重大」に設定された侵入防御イベントのみを検知するよう設定します。

Amazon SNS と PagerDuty を連携する
PagerDuty サービスの Integration Email を控えます。

SNS トピックを作成し、プロトコル E メールで控えたメールエンドポイントを入力してサブスクリプションを作成します。

PagerDuty 側で “Subscription Confirmation” を検知するのでサブスクリプションの確認を行います。
Confirm subscription のアドレスをコピーして、AWS コンソール側で確認することをおすすめします。

C1WS と Amazon SNS を連携する
AWS の IAM からユーザー作成を行い、sns:Publish の権限を付与します。
アクセスキーを作成してダウンロードします。
ちなみに IAM ロールでの権限付与はできないようです。

C1WS にログイン > 管理 > システム設定 > イベントの転送
から「Amazon Simple Notification Serviceにイベントを公開」にチェックを入れ、アクセスキーと秘密鍵の情報を入力します。

JSON SNS設定の編集… から以下 JSON フィルターを入力します。
重要度が「重大」に設定された侵入防御イベントのみを、指定の SNS トピックに通知する設定です。
応用すれば、特定のルール番号、文字列、アラートの種類などを AND/OR 条件でフィルターする、柔軟な設定が可能です。
{
"Version": "2014-09-24",
"Statement": [
{
"Topic": "arn:aws:sns:ap-northeast-1:123456789012:tadokoro-c1ws-sns-pd-topic",
"Condition": {
"StringEquals": {
"EventType": "PayloadLog",
"SeverityString": "Critical"
}
}
}
]
}

メールインテグレーションから侵入防御アラートを除外する
管理 > システム設定 > アラート > [アラートの設定]
から「侵入防御ルールアラート」のアラートをオフにします。
これにより、侵入防御イベントの発生時にメール通知が飛ばなくなります。
結果、侵入防御イベントは SNS 経由でのみ通知されることになります。
そしてそこでは、重要度が「重大」のものが通知され、「重大」以外のものはフィルターされます。

アラートを発報する
サーバーへの攻撃を再現することになるためここでは省略しますが、SNS 経由のアラートを発報するとこのようになります。
“AWS Notification Message” というなかなかに質素なタイトル。
そして目視で確認するには無理がある雑多な本文です。

2. 加工
C1WS から Amazon SNS に連携すると、タイトルは “AWS Notification Message” となり、本文は JSON オブジェクトを [ ] で囲んだ配列形式のデータとなることが分かりました。
ここから必要な情報を抜き出して整えます。

SNS トピック2 と PagerDuty を連携する
2つ目の SNS トピックを作成し、PagerDuty の Integration Email をサブスクライブします。
先程作成した SNS トピック (tadokoro-c1ws-sns-pd-topic) と同じ状態です。

Lambda 関数を作成する
Lambda 関数を作成します。
Python コードの例は以下のようになります。
メッセージの情報からタイトルや本文を加工した上で、SNS トピック2に渡します。
関数の設定から環境変数 “DESTINATION_TOPIC_ARN” に SNS トピック2のARNを登録します。
また実行ロールには SNS トピック2 への sns:Publish 権限をつけておきます。
import json
import boto3
import os
def lambda_handler(event, context):
sns_client = boto3.client('sns')
destination_topic_arn = os.environ['DESTINATION_TOPIC_ARN'] # 環境変数からARNを取得
# SNSイベントからメッセージを取得
message = event['Records'][0]['Sns']['Message']
data_list = json.loads(message) # 配列全体を取得
# 件名を生成(配列内の最初のJSONオブジェクトを使って件名を作成)
subject = f"侵入防御ルール {data_list[0]['Reason']} のアラートが発生しました"
# 件名が100文字を超えている場合、100文字以内に切り詰める
if len(subject) > 100:
subject = subject[:100]
formatted_messages = []
for data in data_list:
reason = data.get('Reason', '不明')
log_date = data.get('LogDate', '不明')
severity = data.get('SeverityString', '不明')
host_id = data.get('HostID', '不明')
hostname = data.get('Hostname', '不明')
host_os = data.get('HostOS', '不明')
action = data.get('ActionString', '不明')
formatted_message = (
f"アラート:{reason}\n"
f"時刻:{log_date}\n"
f"重要度:{severity}\n"
f"インスタンスID:{host_id}\n"
f"インスタンス名:{hostname}\n"
f"ホストOS:{host_os}\n"
f"処理:{action}"
)
formatted_messages.append(formatted_message)
# 各メッセージ間に改行を挿入して結合
final_message = "\n\n".join(formatted_messages)
# SNSメッセージを送信
sns_client.publish(
TopicArn=destination_topic_arn,
Message=final_message,
Subject=subject
)
return {
'statusCode': 200,
'body': json.dumps('Message processed and sent to SNS')
}

SNS トピック1 と Lambda を連携する
Lambda を SNS トピック1にサブスクライブします。
先程作成した SNS トピック1をそのまま使います。
PagerDuty に直接通知するサブスクリプションは削除しておきます。

C1WS と SNS トピック1 を連携する
C1WS 側で SNS トピック1 への通知やフィルターの設定を行います。
今回は先程の設定を使い回すので省略します。

アラートを発報する
この設定でアラートを発報すると PagerDuty では以下のように検知します。

Lambda なしの時と比べるとずいぶん見やすくなりましたね。

これで必要なアラートを必要な情報と共に検知することができるようになりました。
これなら対応不要のアラートに埋もれることも、情報が足りなくてもどかしい思いをすることもありません。

おわりに
SNS と Lambda を使って、C1WS のアラートをフィルター、加工した上で PagerDuty に検出する方法を見てきました。
ここに Terraform や CloudFormation で構築を自動化したり、Lambda のエラー監視を入れることで、より盤石な構築、運用体制を築いていけそうです。
情報であれ、物であれ、何にせよ整理整頓してすっきりとした生活を送りたいものですね。
おしまい