🗳️

BLOB ストレージに保存した JSON 形式のログを Log Analytics ワークスペースに書き込みたい

に公開

はじめに

コスト的な観点やサービス制約により、ログ保管先として Log Analytics ワークスペースではなく BLOB ストレージを選択するケースも考えられます。一方で、検索や分析の目的で BLOB ストレージから Log Analytics ワークスペースへログを書き戻す必要が生じることもあります。
残念ながら、マネージドなサービスとしては提供していないため、このような要件に対して簡易的に実現できる方法について説明していきます。

方針

本構成の方針は以下の通りです。

  • ログは JSON 形式を想定しています。(診断設定や Defender からのログ エクスポートなど)
  • アプリケーションの開発は最小限とします。
  • 可能な限りマネージドサービスを活用し、運用負荷を軽減します。
  • ログの加工処理は極力行わず、元の形式のまま取り扱います。

実現方式

本記事で想定する構成は以下の通りです。

  • Linux の Azure VM を準備します。
  • azcopy をインストールし、BLOB ストレージからファイルを取得できるようにします。
  • AMA と DCR を利用し、JSON 形式のログを変換せずに Log Analytics ワークスペースのカスタムテーブルへ取り込みます。
  • (オプション) 取得用スクリプトを作成し、cron で定期的にファイルをダウンロードします。

Azure VM の設定

まず、Linux の Azure VM に azcopy をインストールします。インストール手順は公式ドキュメントをご参照ください。
https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azcopy-v10?tabs=apt#install-azcopy-on-linux-by-using-a-package-manager

マネージド ID を有効化し、ストレージ BLOB データ閲覧者ロールを割り当てます。

マネージド ID を用いて azcopy にログインします。

azcopy login --identity

ファイルが正常に取得できるか確認します。(例:MDE の DeviceEvents)

azcopy copy https://mystorage.blob.core.windows.net/insights-logs-advancedhunting-deviceevents/tenantId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/y=2025/m=09/d=24/h=09/m=00/PT1H.json /tmp/logfile

カスタム テーブルの作成

DeviceEvents 用のカスタムテーブルは、以下のツールを利用して作成します。
https://github.com/markolauren/sentinel/tree/main/tableCreator tool

Set-ExecutionPolicy Unrestricted -Scope Process
Connect-AzAccount
.\tableCreator.ps1 -FullResourceID <Log Analytics ワークスペースのリソース ID>

JSON ファイル収集用のデータ収集ルールの作成

データ収集ルール(DCR)については、公式ドキュメントをご参照ください。
https://learn.microsoft.com/ja-jp/azure/azure-monitor/vm/data-collection-log-json

通常は、対象の JSON ファイルと同一スキーマを持つデータ収集ルールを作成します。
DeviceEvents などの診断設定や Defender のログの場合、各フィールドが properties に dynamic 型で格納されているため、まず properties を文字列として取得し、後続の変換ルールで各列へ展開します。

DeviceEvents の変換ルール
source
| extend prop_obj = parse_json(properties)
| extend
    TimeGenerated = todatetime(prop_obj.Timestamp),
    Timestamp = todatetime(prop_obj.Timestamp),
    DeviceId = tostring(prop_obj.DeviceId),
    DeviceName = tostring(prop_obj.DeviceName),
    ReportId = tolong(prop_obj.ReportId),
    ActionType = tostring(prop_obj.ActionType),
    AccountDomain = tostring(prop_obj.AccountDomain),
    AccountName = tostring(prop_obj.AccountName),
    AccountSid = tostring(prop_obj.AccountSid),
    AdditionalFields = parse_json(prop_obj.AdditionalFields),
    AppGuardContainerId = tostring(prop_obj.AppGuardContainerId),
    CreatedProcessSessionId = tolong(prop_obj.CreatedProcessSessionId),
    FileName = tostring(prop_obj.FileName),
    FileOriginIP = tostring(prop_obj.FileOriginIP),
    FileOriginUrl = tostring(prop_obj.FileOriginUrl),
    FileSize = tolong(prop_obj.FileSize),
    FolderPath = tostring(prop_obj.FolderPath),
    InitiatingProcessAccountDomain = tostring(prop_obj.InitiatingProcessAccountDomain),
    InitiatingProcessAccountName = tostring(prop_obj.InitiatingProcessAccountName),
    InitiatingProcessAccountObjectId = tostring(prop_obj.InitiatingProcessAccountObjectId),
    InitiatingProcessAccountSid = tostring(prop_obj.InitiatingProcessAccountSid),
    InitiatingProcessAccountUpn = tostring(prop_obj.InitiatingProcessAccountUpn),
    InitiatingProcessCommandLine = tostring(prop_obj.InitiatingProcessCommandLine),
    InitiatingProcessCreationTime = todatetime(prop_obj.InitiatingProcessCreationTime),
    InitiatingProcessFileName = tostring(prop_obj.InitiatingProcessFileName),
    InitiatingProcessFileSize = tolong(prop_obj.InitiatingProcessFileSize),
    InitiatingProcessFolderPath = tostring(prop_obj.InitiatingProcessFolderPath),
    InitiatingProcessId = tolong(prop_obj.InitiatingProcessId),
    InitiatingProcessLogonId = tolong(prop_obj.InitiatingProcessLogonId),
    InitiatingProcessMD5 = tostring(prop_obj.InitiatingProcessMD5),
    InitiatingProcessParentCreationTime = todatetime(prop_obj.InitiatingProcessParentCreationTime),
    InitiatingProcessParentFileName = tostring(prop_obj.InitiatingProcessParentFileName),
    InitiatingProcessParentId = tolong(prop_obj.InitiatingProcessParentId),
    InitiatingProcessRemoteSessionDeviceName = tostring(prop_obj.InitiatingProcessRemoteSessionDeviceName),
    InitiatingProcessRemoteSessionIP = tostring(prop_obj.InitiatingProcessRemoteSessionIP),
    InitiatingProcessSessionId = tolong(prop_obj.InitiatingProcessSessionId),
    InitiatingProcessSHA1 = tostring(prop_obj.InitiatingProcessSHA1),
    InitiatingProcessSHA256 = tostring(prop_obj.InitiatingProcessSHA256),
    InitiatingProcessUniqueId = tostring(prop_obj.InitiatingProcessUniqueId),
    InitiatingProcessVersionInfoCompanyName = tostring(prop_obj.InitiatingProcessVersionInfoCompanyName),
    InitiatingProcessVersionInfoFileDescription = tostring(prop_obj.InitiatingProcessVersionInfoFileDescription),
    InitiatingProcessVersionInfoInternalFileName = tostring(prop_obj.InitiatingProcessVersionInfoInternalFileName),
    InitiatingProcessVersionInfoOriginalFileName = tostring(prop_obj.InitiatingProcessVersionInfoOriginalFileName),
    InitiatingProcessVersionInfoProductName = tostring(prop_obj.InitiatingProcessVersionInfoProductName),
    InitiatingProcessVersionInfoProductVersion = tostring(prop_obj.InitiatingProcessVersionInfoProductVersion),
    IsInitiatingProcessRemoteSession = tobool(prop_obj.IsInitiatingProcessRemoteSession),
    IsProcessRemoteSession = tobool(prop_obj.IsProcessRemoteSession),
    LocalIP = tostring(prop_obj.LocalIP),
    LocalPort = toint(prop_obj.LocalPort),
    LogonId = tolong(prop_obj.LogonId),
    MachineGroup = tostring(prop_obj.MachineGroup),
    MD5 = tostring(prop_obj.MD5),
    ProcessCommandLine = tostring(prop_obj.ProcessCommandLine),
    ProcessCreationTime = todatetime(prop_obj.ProcessCreationTime),
    ProcessId = tolong(prop_obj.ProcessId),
    ProcessRemoteSessionDeviceName = tostring(prop_obj.ProcessRemoteSessionDeviceName),
    ProcessRemoteSessionIP = tostring(prop_obj.ProcessRemoteSessionIP),
    ProcessTokenElevation = tostring(prop_obj.ProcessTokenElevation),
    RegistryKey = tostring(prop_obj.RegistryKey),
    RegistryValueData = tostring(prop_obj.RegistryValueData),
    RegistryValueName = tostring(prop_obj.RegistryValueName),
    RemoteDeviceName = tostring(prop_obj.RemoteDeviceName),
    RemoteIP = tostring(prop_obj.RemoteIP),
    RemotePort = toint(prop_obj.RemotePort),
    RemoteUrl = tostring(prop_obj.RemoteUrl),
    SHA1 = tostring(prop_obj.SHA1),
    SHA256 = tostring(prop_obj.SHA256),
    SourceSystem = tostring(prop_obj.SourceSystem)

azcopy で取得したファイルを DeviceEvents.json に追記し、正しく取り込めることを確認します。

(オプション) 自動的にログを取得

以下のスクリプトを作成し、cron で 1 時間に 1 回実行する運用を想定しています。

  • スクリプト内で azcopy login --identity によるログインを行います。
  • 直前の yyyy, mm, dd, hh を計算して指定します。
  • azcopy copy でファイルをダウンロードします。
  • ダウンロードしたファイルの内容を DeviceEvents.json に追記します。
  • azcopy でダウンロードした PT1H.json ファイルは削除します。

※ 上記とは別に、DeviceEvents.json のローテーション(例:1日1回)もご検討ください。

#!/bin/bash

# マネージド ID で azcopy にログイン
azcopy login --identity

# 1時間前の UTC 時刻を取得(分は常に 00 に固定)
TARGET_TIME=$(date -u -d "1 hour ago" +"%Y/%m/%d/%H")
IFS='/' read -r yyyy mm dd hh <<< "$TARGET_TIME"
min="00"

# BLOB の URL
BLOB_URL="https://mystorage.blob.core.windows.net/insights-logs-advancedhunting-deviceevents/tenantId=48306a5f-8980-42c2-9168-1f65b9c68047/y=$yyyy/m=$mm/d=$dd/h=$hh/m=$min/PT1H.json"

# 一時ファイルの保存先
TMP_FILE="/tmp/PT1H.json"

# ファイルをダウンロード
azcopy copy "$BLOB_URL" "$TMP_FILE"

# ダウンロード成功時のみ処理を続行
if [ -f "$TMP_FILE" ]; then
    # DeviceEvents.json に追記(存在しない場合は作成)
    cat "$TMP_FILE" >> DeviceEvents.json

    # 一時ファイルを削除
    rm -f "$TMP_FILE"
else
    echo "ファイルのダウンロードに失敗しました: $BLOB_URL"
    exit 1
fi

まとめ

本記事では、BLOB ストレージ上の JSON 形式のログを Log Analytics ワークスペースへ取り込む方法について説明しました。AMA と DCR を利用することで、JSON 形式のログを変換せずにカスタムテーブルとして取り込むことが可能です。また、azcopy を利用して BLOB ストレージからログを取得し、cron で定期的に実行することで、自動的にログを収集できます。

Microsoft (有志)

Discussion