MNTSQ Techブログ

「MNTSQ(モンテスキュー)」のTechブログです。

セキュリティ監査の運用をSlack Botで自動化した話

MNTSQ Tech Blog TOP > 記事一覧 > セキュリティ監査の運用をSlack Botで自動化した話

はじめに

SREの寺島です。

MNTSQでは、本番環境でのAWSの手動操作や顧客情報データへのアクセス等をCloudTrailログから検知し、操作者に目的や理由を確認するセキュリティ監査を運用しています(詳細はこちらの記事を参照)。

これまではログからの異常検知は自動化されていたものの、その後の操作内容の確認や目的や理由を確認する運用が人力で行われており、運用上のToilとなっていました。

この課題を解決するため、今回、セキュリティ監査運用を自動化するSlack Botを開発しました。本記事では、そのアーキテクチャから実装の詳細までを紹介します。

背景

監査の仕組み

MNTSQでは、本番環境に対する以下のような操作をセキュリティ監査の対象としています。

検知対象 データソース
本番系AWSアカウントでの手動変更操作 CloudTrail
顧客情報を収容するS3バケットへのアクセス CloudTrail
業務時間外のRedash操作・ログイン Redashサーバログ / CloudTrail(IAM Identity Center)

これらの検知からSREメンバーへの通知までは自動化されていました。EventBridgeをトリガーに週次でAthenaの名前付きクエリを実行し、結果をSREチャンネルに通知する仕組みです。

例えばAWSの手動変更操作の場合、以下のようなAthenaクエリが実行されます。

select
    ${環境名} as environment,
    eventtime,
    split_part(userIdentity.principalId, ':', 2) as email,
    eventName,
    resources[1].arn as resource,
    requestParameters
from
    ${環境名}
where
    timestamp between date_format(current_date - interval '1' day, '%Y/%m/%d') and date_format(current_date, '%Y/%m/%d')
    -- 個々人の直接操作を対象とする
    and userIdentity.principalId like '%@mntsq.com'
    -- 新設 / 変更 / 削除に該当する操作を対象とする
    and (
        eventName like 'Update%'
        or eventName like 'Put%'
        or eventName like 'Create%'
        or eventName like 'Delete%'
        or eventName like 'Modify%'
    )
    -- CloudShell そのものに関するイベントは対象から外す
    and eventsource != 'cloudshell.amazonaws.com'

個人の手動操作のうち、リソースの作成・変更・削除にあたるイベントを抽出しています。

不審な操作が検知されると、以下のようにSlackへ通知されます。

課題

検知までは自動化されていた一方で、その後の操作内容の確認・操作者へのヒアリングは手作業となっていました。

具体的な作業フロー

  1. 担当者がAthenaの結果を確認
  2. ログを読み解いて操作内容を把握、背景を確認
  3. 操作者に個別にヒアリング
  4. 回答がなければリマインド

この作業を担当していたSREメンバーは、週に約3時間をこの確認フローに費やしていました。年間に換算すると約1人月の工数です。これは弊社のSREチームの規模を考えると無視できないコストでした。

どのようなものを作ったか

上述の課題を解決するため、セキュリティ監査の運用を自動化するSlack Botを開発しました。

このSlack Botに「A2RM(Automatic Audit Reaction Mechanism)」と名付けました。

別名「監査回答強制マン」です。

Slack Botは、主に以下の4つの機能を備えています。

  • Amazon Bedrockによるログ要約: 操作ログを自然言語に変換して操作対象者に通知
  • Slack UIによる回答受付: ボタン選択による回答の簡略化
  • 自動リマインド: 未回答者への追跡通知
  • 証跡管理: 検知内容と回答結果のDynamoDB保存

動作フロー

通知

不審な操作が検知されると、Slackチャンネルへ操作者本人をメンションしたメッセージが投稿されます。

CloudTrailの生ログは可読性が低く、内容の把握に一定の知識を要します。

例えば、S3バケットポリシーの変更操作を行った場合、Athenaから出力されるCSVは以下のようになります。

"environment","eventtime","email","eventName","resource","requestParameters"
"example-env","2026-03-06T07:57:17Z","user@example.com","PutBucketPolicy","arn:aws:s3:::example-tfstate","{""bucketPolicy"":{""Version"":""2012-10-17"",""Statement"":[{""Sid"":""AllowDatadogReadAccess"",""Effect"":""Allow"",""Principal"":{""AWS"":""arn:aws:iam::123456789012:root""},""Action"":[""s3:GetObject"",""s3:ListBucket""],""Resource"":[""arn:aws:s3:::example-tfstate/*"",""arn:aws:s3:::example-tfstate""]}]},""bucketName"":""example-tfstate"",""Host"":""example-tfstate.s3.ap-northeast-1.amazonaws.com"",""policy"":""""}"

このログはDatadogへの参照権限付与という内容ですが、パッと見て判断するのは困難です。 そこで、Amazon Bedrockを用いて以下のように自然言語で要約しています。

💡 変更概要:

S3バケットに対するポリシー更新操作が検知されました。example-tfstateバケットのポリシーが変更され、外部AWSアカウントに対してs3:GetObjects3:ListBucketの権限が付与されました。これは、Datadogからの読み取りアクセスを許可するための変更と推測されます。

📋 検知された操作:

・S3バケット example-tfstate のポリシー更新(Datadogからの読み取りアクセス許可) (16:57)

このように要約された内容を通知することで、操作者が「自分のどの作業か」を瞬時に判断できるようになります。

回答

この監査の仕組みによって検知される操作の多くは「デプロイ」や「障害対応」などの定型作業です。そのため、ボタン選択式で回答できるようにし、操作者の記述負荷を最小限に抑えています。

  • 定型作業の場合: ボタンをクリックするだけで回答が完了します。

  • イレギュラーな操作の場合: 「その他」を選択した場合のみモーダルが開き、自由記述と関連URL(チケット等)を入力します。

回答が完了すると、元のSlackメッセージは「回答済み」のステータスに更新されます。

回答内容はSREチームのチャンネルへ転送・DBに記録されます。

リマインド

回答がないまま翌営業日を迎えた場合、該当メッセージのスレッドに対して自動でリマインドを投稿します。3回リマインドを行っても回答が得られない場合は、SREチームへのエスカレーション通知に切り替わります。

アーキテクチャ

3つのLambda関数がそれぞれ独立した責務を持ち、DynamoDBを介して状態を共有するイベント駆動のアーキテクチャです。

コンポーネント一覧

コンポーネント 種別 説明
notify-audit-report Lambda Athena結果をBedrockで要約しSlack通知
audit-response-handler Lambda Function URL Slackインタラクティビティの処理
send-audit-reminder Lambda 未回答者への自動リマインド
audit_attestations DynamoDB 回答状態の管理,証跡の蓄積
Amazon Bedrock (Nova Pro) LLM 操作ログの自然言語要約

各コンポーネントの実装の詳細は後述します。

Slack Appの設定

本システムのSlack Appに必要なOAuth Scopesは最小限です。

Scope 用途
chat:write メッセージ投稿・更新
reactions:write リアクション追加
users:read.email メールアドレスからユーザーID検索
users:read users:read.emailの前提として必要

認証情報(Bot Token / Signing Secret)はSSM Parameter Store(SecureString)に保管し、Lambda実行時に取得します。

Slack AppのInteractivityのRequest URLには、Lambda Function URLを指定します。

各機能の実装

通知(notify-audit-report)

操作ログの要約生成からSlack通知までの処理について解説します。

処理フロー

  1. EventBridgeでAthenaクエリの完了を検知し、Lambdaを起動
  2. S3からAthenaの結果CSVを取得
  3. ユーザー(メールアドレス)ごとにグルーピング
  4. Bedrockで操作ログを自然言語に要約
  5. 操作者にメンション付きでSlack通知を投稿
  6. DynamoDBにレコードを保存(リマインド・回答追跡用)

EventBridgeのイベントパターンでAthenaのワークグループごとにクエリ完了を検知し、input_transformerを用いて必要なメタデータ(S3パスや監査種別)をLambdaへ渡しています。

resource "aws_cloudwatch_event_rule" "a2rm_notify_audit_report" {
  event_pattern = jsonencode({
    "source" : ["aws.athena"],
    "detail-type" : ["Athena Query State Change"],
    "detail" : {
      "workgroupName" : ["manual-modification-report"],
      "currentState" : ["SUCCEEDED"]
    }
  })
}

resource "aws_cloudwatch_event_target" "a2rm_notify_audit_report" {
  input_transformer {
    input_paths = {
      queryExecutionId = "$.detail.queryExecutionId"
    }
    input_template = <<-EOT
      {
        "s3_bucket": "athena-results-bucket",
        "s3_key": "cloudtrail/manual_modification/<queryExecutionId>.csv",
        "audit_type": "manual_operation"
      }
    EOT
  }
}

CloudTrailログからSlackメンションへの変換

MNTSQではAWSへのログインに IAM Identity Center を利用しています。これにより、CloudTrailの userIdentity 内にユーザーのメールアドレスが記録されるようになっています。

{
  "type": "AssumedRole",
  "principalid": "AROA...:user@example.com",
  "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_Administrator_.../user@example.com",
  "sessioncontext": {
    "sessionissuer": {
      "type": "Role",
      "arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/.../AWSReservedSSO_Administrator_..."
    }
  }
}

principalidarn にメールアドレスが含まれるため、Athenaクエリでこれを抽出します。Slack APIの users.lookupByEmail を用いてメールアドレスをSlack User IDに変換し、メンション文字列を生成しています。

def lookup_user_by_email(slack: WebClient, email: str) -> str | None:
    try:
        response = slack.users_lookupByEmail(email=email)
        return response["user"]["id"]
    except SlackApiError as e:
        logger.warning("Slackユーザーの検索に失敗しました: %s: %s", email, e.response["error"])
        return None

Bedrock周り

操作ログの要約にはAmazon Bedrockの Amazon Nova Pro を使用しています。

このモデルを選定した理由は以下のとおりです。

  • Bedrockで使える他のモデルと比較して安価
  • 検証段階で出力品質に問題がなかった
  • 完璧な要約は求めておらず、操作者が内容を思い出せる程度の内容で十分だった
  • Slack mrkdwn記法での構造化出力が安定していた

Bedrockクライアントの実装はシンプルです。

bedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")

def generate_summary(prompt: str, model_id: str) -> str:
    response = bedrock_client.invoke_model(
        modelId=model_id,
        body=json.dumps({
            "messages": [{"role": "user", "content": [{"text": prompt}]}],
            "inferenceConfig": {
                "maxTokens": 2048,
                "temperature": 0.3,  # 一貫性のある出力のため低めに設定
            },
        }),
    )
    response_body = json.loads(response["body"].read())
    return response_body["output"]["message"]["content"][0]["text"]

temperature はLLMの出力のランダム性を制御するパラメータで、値が低いほど決定的な出力になります。監査レポートという性質上、同じ入力に対してできるだけ一貫した出力を得たいため、0.3 と低めに設定しています。

実際に使用しているプロンプトの全文を以下に掲載します。 {csv_data} にAthenaの結果CSVが埋め込まれます。

あなたはAWS操作の監査レポートを生成するアシスタントです。
以下のCSVデータはAWS環境で検知された操作ログです。このデータを分析し、指定されたフォーマットでレポートを生成してください。

## CSVデータ
{csv_data}

## 出力フォーマット
以下のフォーマットで出力してください。Slackのmrkdwn記法を使用してください。

🗓 *最終検知日時:* YYYY-MM-DD HH:MM (JST)

💡 *変更概要:*
(リソースのカテゴリごとに、何が行われ、どのような状態になったかを目的の推測を含めて2〜3行ずつ簡潔に要約してください。複数が混在する場合は箇条書きを使用してください)

📋 *検知された操作:*
・操作の説明 (HH:MM)
・操作の説明 (HH:MM)
...

## ルール
- 変更概要は「複数のリソースが更新されました」といった抽象的な表現を避け、サービス単位(例:ECS関連、RDS関連、ACM関連など)でそれぞれの具体的な変更内容と目的を記述してください。
- eventtimeはUTCなのでJST(+9時間)に変換してください
- 最終検知日時は最も新しいeventtimeをJSTで表示してください
- 検知された操作は各行のeventNameとrequestParametersから人間にわかりやすい日本語の操作説明を生成してください
- 操作説明にはリソース名やサービス名を含めてください
- 同一のeventNameが多数ある場合は代表的なものをまとめて表示してください(例:「ECSサービスの更新 x5件」)
- 最大20件まで表示し、それ以上は「…他N件」としてください

## 出力例(入力データとは無関係のサンプル)

入力:
"environment","eventtime","email","eventName","resource","requestParameters"
"production","2026-01-20T08:18:00Z","user@example.com","UpdateService",,"{{""cluster"":""prod-cluster"",""service"":""web-api"",""desiredCount"":3}}"
"production","2026-01-20T08:15:00Z","user@example.com","UpdateService",,"{{""cluster"":""prod-cluster"",""service"":""worker"",""desiredCount"":0}}"

出力:
🗓 *最終検知日時:* 2026-01-20 17:18 (JST)

💡 *変更概要:*
production環境のECSサービスに対する更新操作が検知されました。web-apiサービスのインスタンス数を3台に増加し、workerサービスを停止(0台)にする変更が行われており、デプロイまたはメンテナンス作業の一環と推測されます。

📋 *検知された操作:*
・ECSサービス web-api の更新(台数: 3) (17:18)
・ECSサービス worker の更新(台数: 0) (17:15)

---
上記のフォーマットに従い、レポートを出力してください。出力のみを返し、余計な説明や前置きは不要です。

いくつか工夫したポイントを記載します。

  • 出力フォーマットをSlack mrkdwn記法で指定: LLMの出力をそのままSlack Block Kitの mrkdwn テキストとして使えるようにしています。
  • 抽象的な表現を禁止するルール: 指示しないと「複数のリソースが更新されました」のような無意味な要約を返すことがあったため、明示的に禁止しています
  • 入出力例の提示: Few-shot的に具体例を与えることで出力フォーマットの安定性が向上しました
  • レコード数の上限: 最大50件のCSV行をプロンプトに含め(LLM出力は20件まで表示)、超過分はその旨を注記します。レコード数が多すぎるとトークン制限に達する恐れがあり、要約の精度が低下するためです

LLMの呼び出しに失敗した場合や正しくSlack mrkdwn記法で出力されなかった場合でも、通知自体は止めず「詳細ログを確認してください」というFallbackテキストを送信するように実装していますが、現時点では発生していません。

回答ボタンの生成

上述した通り、定型的な理由はボタン1クリックで回答できるようにし、説明が必要な例外的な操作だけモーダルで自由記述してもらう設計にしています。

以下のように監査種別ごとに回答ボタンを出し分けています。

  • AWS手動操作: デプロイ、定型作業、障害対応。
  • S3/Redash操作: 上記に加え「顧客問い合わせ対応」「顧客データ分析」などを追加。
BUTTONS_MANUAL_OPERATION = [
    {"action_id": "deploy", "label": "デプロイ・リリース", "emoji": "🚀"},
    {"action_id": "routine_work", "label": "定型作業", "emoji": "💫"},
    {"action_id": "incident_response", "label": "障害対応", "emoji": "🚨"},
    {"action_id": "other", "label": "その他(記述する)", "emoji": "📝"},
]

BUTTONS_S3_EVENT = [*_BASE_BUTTONS, _BUTTON_CUSTOMER_INQUIRY, _BUTTON_OTHER]

BUTTONS_REDASH = [
    _BUTTON_DEPLOY, _BUTTON_CUSTOMER_INQUIRY,
    _BUTTON_CUSTOMER_ANALYSIS, _BUTTON_INCIDENT_RESPONSE, _BUTTON_OTHER,
]

ボタンは Slack Block Kit の actions ブロックとして構築します。

button_elements = [
    {
        "type": "button",
        "text": {"type": "plain_text", "text": f"{btn['emoji']} {btn['label']}"},
        "action_id": btn["action_id"],
        "value": btn["action_id"],
    }
    for btn in button_defs
]

action_id が後続の回答処理でどのボタンが押されたかを判定するキーになります。

DynamoDBへの保存

通知投稿後、リマインドと回答追跡のためにDynamoDBにレコードを保存します。将来的な監査証跡としての活用も見据え、検知内容の要約も含めて保存しています。

属性 書き込みタイミング 説明
message_ts (PK) 通知時 Slackメッセージのタイムスタンプ
channel_id 通知時 投稿先チャンネル
slack_user_id 通知時 操作者のSlack User ID
email 通知時 操作者のメールアドレス
status 通知時 → 回答時に更新 pendingcompleted
created_at 通知時 レコード作成日時
audit_type 通知時 監査種別(manual_operation 等)
detection_summary 通知時 LLMの要約 or テンプレートテキスト(JSON)
s3_result_url 通知時 Athena結果CSVのS3パス
responded_at 回答時 回答日時
response_reason 回答時 選択された理由(deploy 等)
response_content 回答時 回答内容の詳細(JSON)
remind_count リマインド時に更新 リマインド回数
table.put_item(
    Item={
        "message_ts": message_ts,      # PK: Slackメッセージのタイムスタンプ
        "channel_id": channel_id,
        "slack_user_id": slack_user_id,
        "email": email,
        "status": "pending",
        "created_at": now.isoformat(),
        "remind_count": 0,
        "s3_result_url": s3_log_url,
        "audit_type": audit_type,
        "detection_summary": detection_summary,  # LLMの要約 or テンプレートテキスト
    }
)

パーティションキーにSlackのmessage_tsを使っています。Slackのメッセージタイムスタンプはチャンネル内で一意であり、スレッド返信やメッセージ更新の際にもこの値で元メッセージを特定できるためです。

※ message_ts単体のPKなのは、本Botは単一の監査チャンネルに投稿する設計のためです。

resource "aws_dynamodb_table" "a2rm_audit_attestations" {
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "message_ts"

  global_secondary_index {
    name            = "status-created_at-index"
    hash_key        = "status"
    range_key       = "created_at"
    projection_type = "ALL"
  }

  global_secondary_index {
    name            = "email-created_at-index"
    hash_key        = "email"
    range_key       = "created_at"
    projection_type = "ALL"
  }
}

statuscreated_at にGSI(グローバルセカンダリインデックス)を張ることで、リマインド対象となる「未回答(pending)」かつ「一定期間経過」したレコードを効率的に抽出できるようにしています。 また、将来的にユーザーごとの監査履歴を参照できるようにemailcreated_atにもGSIを張っています。

回答受け付け(audit-response-handler)

操作者がSlack上でボタンを押下した際のインタラクション処理について解説します。

Slack Boltとlazy listener

Slackのインタラクティブイベント(ボタン押下やモーダル送信)を処理するには、SlackからのHTTPリクエストを 3秒以内にACK する必要があります。しかし、DynamoDB更新やSlack APIの複数呼び出しを含む一連の処理は、3秒を超過する可能性があります。

この問題を Slack Bolt for Pythonlazy listener パターンで解決しています。

app.action("deploy")(ack=ack_action, lazy=[process_standard_button])
app.action("other")(ack=ack_action, lazy=[process_other_button])
app.view("audit_reason_other")(ack=ack_view, lazy=[process_modal_submission])

ack 関数が即座に200レスポンスを返し、lazy に指定した関数は 自身のLambdaを非同期で再invoke して別のLambda実行コンテキストで処理されます。この仕組みを実現するため、LambdaのIAMポリシーには自身へのInvoke権限を付与しています。

# lazy listener が自身を非同期呼び出しするために必要
statement {
  effect    = "Allow"
  actions   = ["lambda:InvokeFunction", "lambda:GetFunction"]
  resources = ["arn:aws:lambda:<略>:function:audit-response-handler"]
}

Lambda Function URL の採用

回答処理のエンドポイントには Lambda Function URL を採用しました。

Interactivity Webhookの認証は、Slackがリクエストヘッダーに付与する署名の検証(Signing Secretによる検証)によって行われるため、API Gateway等の前段を置かずにFunction URLのみで十分であると判断しました。

回答処理の流れ

ボタンが押された後の処理は以下の順で行います。

  1. 二重処理防止: Slackのリトライ仕様を考慮し、処理の冒頭で、DynamoDBのstatusを確認し、すでに completed なら処理をスキップします。
  2. 元メッセージを更新: 操作者が連続してボタンを押せないよう、chat.update で元メッセージのボタンを「回答済み」のテキスト表示に差し替えます。
  3. SREチャンネルに回答内容を転送: 監査結果の透明性を確保するため、回答は公開チャンネルに流します
  4. ステータスの更新: すべての処理が成功した段階で、DynamoDBの statuscompleted に更新します。
def complete_attestation(client, message_ts, channel_id, user_id, action_id, label, original_blocks, detail_text=None):
    # 1. 二重処理防止
    item = table.get_item(Key={"message_ts": message_ts}).get("Item")
    if item and item.get("status") == "completed":
        return

    # 2. 元メッセージを更新(ボタン → 回答済み表示)
    completed_blocks = build_completed_blocks(original_blocks, user_id, label, detail_text)
    client.chat_update(channel=channel_id, ts=message_ts, blocks=completed_blocks)

    # 3. SREチャンネルに回答内容を転送
    forward_blocks = build_forward_blocks(user_id, label, original_blocks, channel_id, message_ts, detail_text)
    client.chat_postMessage(channel=FORWARD_CHANNEL_ID, blocks=forward_blocks)

    # 4. DynamoDB更新(回答内容も証跡として保存)
    response_content = json.dumps(
        {"reason": action_id, "detail_text": detail_text},
        ensure_ascii=False,
    )
    table.update_item(
        Key={"message_ts": message_ts},
        UpdateExpression="SET #status = :s, responded_at = :r, response_reason = :reason, response_content = :rc",
        ExpressionAttributeNames={"#status": "status"},
        ExpressionAttributeValues={
            ":s": "completed",
            ":r": datetime.now(timezone.utc).isoformat(),
            ":reason": action_id,
            ":rc": response_content,
        },
    )

リマインド(send-audit-reminder)

未回答の操作者に対し、自動で再通知およびエスカレーションを行う仕組みについて解説します。

処理フロー

  1. トリガー: EventBridgeにより、平日の午前10:00(JST)にLambdaが起動。
  2. 対象の抽出: DynamoDBのGSIを用いて、前日以前に作成された未回答レコード(status=pending)をクエリ。
  3. リマインド投稿: 該当するSlackメッセージのスレッドに対してリマインドを投稿。
  4. カウンタ更新: DynamoDBの remind_count をインクリメント。
  5. エスカレーション: リマインド回数が規定値(3回)を超えた場合、SREチームへ通知。

リマインド対象の特定

リマインド対象のレコードは status-created_at-index GSIで取得します。

paginator = dynamodb_client.get_paginator("query")
pages = paginator.paginate(
    TableName=dynamodb_table_name,
    IndexName="status-created_at-index",
    KeyConditionExpression="#s = :status AND created_at < :cutoff",
    ExpressionAttributeNames={"#s": "status"},
    ExpressionAttributeValues={
        ":status": {"S": "pending"},
        ":cutoff": {"S": cutoff},
    },
)

ここで cutoff は「当日の0:00(JST)」を基準としています。これにより、当日検知されたばかりのレコード(まだ回答の猶予があるもの)をリマインド対象から除外しています。

エスカレーション

3回リマインドしても回答がない場合は、SREチームへのエスカレーションに切り替えます。

def _process_item(item, slack, channel_id, sre_group_id, table):
    remind_count = int(item.get("remind_count", 0))

    if remind_count >= 3:
        text = _build_escalation_text(slack_user_id, sre_group_id)
    else:
        text = _build_reminder_text(slack_user_id)

    slack.chat_postMessage(channel=channel_id, thread_ts=message_ts, text=text)

導入効果

コスト面

全体をサーバーレスアーキテクチャで構成しているため、月額の運用コストは1,000円以下に抑えられています。

Lambda、DynamoDB、Amazon Bedrockはいずれも従量課金であり、週数回という実行頻度ではほとんどコストが発生しません。LLMに安価な Amazon Nova Pro を選定したことも低コスト化に寄与しています。

月額1,000円以下の運用コストで年間約1人月分に相当する工数を削減することができました。

機能・運用面

導入前後で、監査確認フローは以下のように改善されました。

項目 導入前(Before) 導入後(After)
確認頻度 週次(手動) 日次(自動)
回答方法 Slackでの自由記述 ボタン1クリック / モーダル入力
リマインド 手動でフォローアップ 自動リマインド+エスカレーション
証跡管理 なし DynamoDBに保存
属人化 担当者の手作業に依存 Botによる標準化

セキュリティ面・ガバナンス面

検知内容、回答内容がDynamoDBに構造化された状態で蓄積されるため、そのまま監査証跡として活用可能な状態になりました。

ISO27001(ISMS)などのセキュリティ認証では、イベントログの定期的なレビューや、重要な変更の特定・記録・通知といった管理策が求められます。

今回のシステム化により、「検知 → 確認 → 記録」の一連のプロセスが自動化・標準化されました。仕組みとして漏れのない運用が行われていることを客観的に示せるようになったことは、今後のセキュリティ認証の維持・取得に向けても良い影響があるのではないかと考えています。

最後に

本記事では、Slack Botを活用したセキュリティ監査運用の自動化事例について紹介しました。

セキュリティ監査運用は非常に重要ですが、検知後の確認フローをいかに効率化し、形骸化させずに継続するかという点についてはまだ多くの組織で模索が続いている部分だと感じています。

今回、サーバーレスアーキテクチャとLLMを組み合わせることで、低コストかつ運用負荷の低い形でこの課題を解決することができました。

本記事の内容が、同様の課題を抱えている方や、セキュリティ運用の自動化を検討されている方々にとって、参考になれば幸いです。