CloudTrailから怪しいイベントを見つけて通知するTerraform Moduleを作る
SRE Advent Calendar 2024の20日目の記事です。
概要
CloudTrailの情報、溜めてるだけになってませんか?
溜めてたとしても、以下のように何かあったときにクエリするといったことが大半だと思います。
何か活用できないかと調べていたところ、セキュリティハブのドキュメントに怪しいイベントを検知するのに良さげなメトリクスフィルタのサンプルがいくつもあったので、これをTerraformで書いて本番環境に入れました。
準備
AWSマネジメントコンソールからCloudTrail→証跡→対象の証跡→編集でオプションのCloudWatch Logsを有効化します。
このときログを出力するロググループと、CloudTrailが引き受けるIAMロールを指定します。権限は以下のような感じです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:ap-northeast-1:xxxxxxxxxx:log-group:/aws/trail/hoge-cloudtrail:log-stream:xxxxxxxxxx_CloudTrail_ap-northeast-1*"
]
},
]
}
また、Slack通知用のSNS Topicを用意します。特にカスタムしたいわけでなければAWS ChatBotを利用するのが楽です。
コード
- 再利用できるようにmoduleで作成します。
- variable.tfに各種閾値のデフォルト値を指定します。使用時に用途に合わせてオーバーライドできるようにするためです。
呼び出し元
module "cloudtrail_notify" {
# sourceに関して同一リポジトリなら相対パスで良いが、GitHubを使用しており、別のプライベートリポジトリを参照したい場合PATなしでできる以下の方法がセキュアで良い
# https://techblog.ap-com.co.jp/entry/2024/12/15/180000
source = "../modules"
sns_topic_arn = "arn:aws:sns:ap-northeast-1:xxxxxxxxxx:hoge-notify"
log_group_name = "/aws/trail/hoge-cloudtrail"
}
ディレクトリ構造
├── aws_cloudtrail_notify
│ ├── locals.tf
│ ├── main.tf
│ ├── providers.tf
│ └── variable.tf
locals.tf
locals {
patterns = [
{
# 不審なAPIコールを検知する
"name" : "Trail-Notify-UnauthorizedAPICalls",
"pattern" : "{($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")}",
"description" : "Unauthorized API calls",
"alert_threshold" : var.unauthorized_api_call_alert_threshold
},
{
# 〜〜〜略〜〜〜
# patternは上記の公式ドキュメントにあるのでそちらを見てください
#
# 以下はドキュメントに載ってないが、東京リージョンをメインに使う場合、他のリージョンで大量にリソースをcreateされた場合は悪い挙動だと思い作ったフィルタ
# alert_thresholdを1にするとグローバルリソースが作成した際に一々通知してやかましかったので環境に合わせて要調整である
{
# 東京リージョン以外のリージョンへのリソース作成を検知する
"name" : "Trail-Notify-ResourceCreationInOtherRegion",
"pattern" : "{($.awsRegion != \"ap-northeast-1\") && ($.eventName = \"Create*\")}",
"description" : "Resource creation in other regions",
"alert_threshold" : var.resource_creation_in_other_region_alert_threshold
}
]
}
main.tf
resource "aws_cloudwatch_log_metric_filter" "this" {
for_each = {
for i in local.patterns : i.name => i
}
name = each.value.name
pattern = each.value.pattern
log_group_name = var.log_group_name
metric_transformation {
name = each.value.name
namespace = each.value.name
value = "1"
default_value = "0"
}
}
resource "aws_cloudwatch_metric_alarm" "this" {
for_each = {
for i in local.patterns : i.name => i
}
alarm_name = each.value.name
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "1"
metric_name = each.value.name
namespace = each.value.name
period = "300"
statistic = "Sum"
threshold = each.value.alert_threshold
alarm_description = each.value.description
alarm_actions = [var.sns_topic_arn]
}
Slackでのアラート例
Tipsですが、Chatbotの通知をクリックし、マネジメントコンソールに入ります。
その後表示→関連ログで時間が絞られた状態でLogsに移動するので便利です。
数ヶ月運用した感想
得体の知れない何かがコアリソースを変更した際に気がつけるようになりました。幸いにそのようなことは起こっていません。
ただ、フォースポジティブ的に余計なアラートを鳴らしてしまうことがあるので、この辺りは閾値やフィルタを改造しがいがあると思いました。例えば以下の例です。
- スイッチロールでA環境に入っているときにコピーしておいたB環境のURLを開くと
"{($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")}"
が大量発生しアラートが鳴る - グローバルリソースが東京リージョン以外にリソースを作ったときに
"{($.awsRegion != \"ap-northeast-1\") && ($.eventName = \"Create*\")}"
が鳴る
余計なアラートに関しては閾値を実態と合わせました。
あと良い点として、運用コストが安いです。CloudWatch Logsに保存するイベントは保持期間を1日にすれば、マルチリージョンの証跡がONになっていても月10円程度と大した値段がかかりません。
最後に、これを作る過程でCloudTrailを眺めることで、なんとなくGUI操作をしている後ろでAWSがどういうAPIコールをを行っているのか勉強になりました。
参考
その他CloudTrailの活用方法についてOpsJawsでLTしました。併せて参考になれば幸いです。
Discussion