AWSコンソールに入った記録を残したい!!
というわけで、Discord WebHookでログイン通知を飛ぶ仕組みを作成してみました。
はじめに
ログイン通知を実装する方法は、少なくとも2パターンあるようです。
今回は、コストが下げられる & 手順が簡単なので1.の方を設定します。
1. EventBridge経由
フロー: CloudTrail(標準機能) → EventBridge → Lambda → Discord
メリット
- ほぼ即時でアラートが飛ぶ
- 比較的低コストで済む
デメリット
- 各リージョンでEventBridge設定が必要
※ イベントバスやCloudFormationを使えば、ある程度集約できるらしい
2. CloudWatch Logs経由
フロー: CloudTrail(証跡追加) → CloudWatch Logs → サブスクリプションフィルタ → Lambda → Discord
メリット
- 1テナントの設定で、全リージョンのアラートが取れる
(Virginiaを選択すれば、全リージョンの証跡が取れる)
デメリット
手順
今回はこの手順で設定します。
- CloudTrailの証跡を確認する
- DiscordのWebHook URLを取得する
- Discordに送信するLambdaを作成する
- EventBridgeの作成
1. CloudTrailの証跡を確認する
CloudTrailのログイン証跡があることを確認する
そもそもログが出ていないとかなりハマります。
念のためログイン証跡が記録されていることを確認しましょう。
CloudTrail > イベント履歴 > 「ConsoleLogin」を探す
※ 表示されない場合は別のリージョンを選択するなどして「ConsoleLogin」を探しましょう。
絞り込みはルックアップ属性に「イベント名」を選択→「ConsoleLogin」で検索。
2. DiscordのWebHook URLを取得する
※ ご存じの方・Slack等を使う方は3.に進んでください。
AWSの設定を行う前に、送信先の設定を行います。
Botは難しそうだったので、今回はWebHookを設定していきます。
2022/5時点では、この手順でURLを取得できます。
Discordの通知先サーバー > サーバー設定 > 連携サービス > WebHook > 「新しいウェブフック」
この「ウェブフックURLをコピー」でWebHook URLが取得できます。どこかに保存しておきましょう。
3. Discordに送信するLambdaを作成する
ここからが本題。
まずはEventBridgeで発行される値を元に、Pythonで処理を作ります。
Lamdaが受けるデータ形式はこんな感じです。
データ形式
{ "version": "0", "id": "76f2f25f-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "detail-type": "AWS API Call via CloudTrail", "source": "aws.cloudtrail", "account": "123456789012", "time": "2022-05-31T14:00:00Z", "region": "ap-northeast-1", "resources": [], "detail": { "eventVersion": "1.08", "userIdentity": { "type": "IAMUser", "principalId": "PRINCIPALID1234567890", "arn": "arn:aws:iam::123456789012:user/testuser", "accountId": "123456789012", "accessKeyId": "PRINCIPALID1234567890", "userName": "testuser", "sessionContext": { "sessionIssuer": {}, "webIdFederationData": {}, "attributes": { "creationDate": "2022-05-31T14:00:00Z", "mfaAuthenticated": "true" } } }, "eventTime": "2022-05-31T14:00:00Z", "eventSource": "cloudtrail.amazonaws.com", "eventName": "LookupEvents", "awsRegion": "ap-northeast-1", "sourceIPAddress": "AWS Internal", "userAgent": "AWS Internal", "requestParameters": { "lookupAttributes": [ { "attributeKey": "ReadOnly", "attributeValue": "false" } ], "maxResults": 50 }, "responseElements": null, "requestID": "b1a9fc9c-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "eventID": "15e67291-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "readOnly": true, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management", "sessionCredentialFromConsole": "true" } }
コードのテスト用に、これをLambdaのTestにコピペして入れておくと良いです。
Lambdaには、こんな感じのコードをデプロイしました。(雑)
※ rootユーザの表記がバグりますが、知らないログイン通知が来た時点で事件なので保留
コード
lambda_function.py
from dateutil import parser from dateutil.tz import gettz import urllib.request import logging import json logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): url = "取得したDiscordのWebHook URL" headers = { "Content-type" : "application/json", "User-Agent": "AWS/Lambda" } payload = { "username":"discordbot", "content": "ログインがありました。", "embeds": [ { "fields":[ { "name": "ユーザ名", "value": event['detail']['userIdentity']['userName'], }, { "name": "リージョン", "value": event['detail']['awsRegion'], "inline": True }, { "name": "ログイン日時", "value": parser.parse(event['detail']['eventTime']).astimezone(gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S"), "inline": True }, ]} ] } req = urllib.request.Request( url=url, headers=headers, data=json.dumps(payload).encode('utf-8') ) with urllib.request.urlopen(req) as res: logger.info(res.read().decode("utf-8"))
コードのデプロイ後、上に記載したデータでテストで通知が届くことを確認しておくと確実です。
4. EventBridgeの作成
最後に、EventBridgeでCloudTrailとLambdaを繋げましょう。
EventBridgeはこのように設定します。
EventBridge > ルール > ルールを作成
サンプルイベント: AWS Console Sign In via CloudTrail
※ 設定しなくても動きますが、内容テストが使えるため設定を推奨。
イベントパターン:
{ "detail-type": ["AWS Console Sign In via CloudTrail"], "detail": { "eventSource": ["signin.amazonaws.com"], "eventName": ["ConsoleLogin"] } }
ターゲット指定
先ほど作成したLambdaを指定しましょう。
動作確認
設定が完了したらログアウトして、設定を行ったリージョンでログインし直しましょう。
Discordにログが出力されるはずです。
おわりに
勉強のため手動で設定しましたが、全リージョンの設定であればCloudFormationなどにした方が良さそうです。
送信コードの共通化はEventBridge イベントバスを設定した方がよさそう。
改良の余地があるので、そのあたりが今後の課題。