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 をインストールします。インストール手順は公式ドキュメントをご参照ください。
マネージド 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 用のカスタムテーブルは、以下のツールを利用して作成します。
Set-ExecutionPolicy Unrestricted -Scope Process
Connect-AzAccount
.\tableCreator.ps1 -FullResourceID <Log Analytics ワークスペースのリソース ID>
JSON ファイル収集用のデータ収集ルールの作成
データ収集ルール(DCR)については、公式ドキュメントをご参照ください。
通常は、対象の JSON ファイルと同一スキーマを持つデータ収集ルールを作成します。
DeviceEvents などの診断設定や Defender のログの場合、各フィールドが properties に dynamic 型で格納されているため、まず properties を文字列として取得し、後続の変換ルールで各列へ展開します。
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 で定期的に実行することで、自動的にログを収集できます。
Discussion