Microsoft SentinelのIdentityInfoでAccountUPNとAccountUpnの列名違いに対応するKQL実装ガイド

Microsoft Sentinel や Defender で KQL を書いていると、「ドキュメントどおりに書いたのに無効なクエリになる」という相談がよくあります。とくに IdentityInfo テーブルの AccountUPN / AccountUpn の違いは、多くのテナントで実務上の落とし穴になっています。この記事では、この問題の背景と安全なクエリパターンを具体的に解説します。

目次

IdentityInfo で AccountUPN を書いたらクエリが落ちる理由

まず、今回のトラブルを整理します。

  • Microsoft Sentinel の分析ルールで KQL を実行したところ「無効なクエリ」エラーが発生。
  • ドキュメントやブログでは AccountUPN という列名で紹介されている。
  • ところが、実際のテナントでは IdentityInfo テーブルの列が AccountUpn になっていた。
  • 結果として、AccountUPN を参照するクエリが列未存在として失敗する。

ここで重要なのは、Kusto Query Language (KQL) は基本的に大文字・小文字を区別するという点です。

  • AccountUPN と AccountUpn は KQL にとって「別の列」です。
  • 存在しない列名を参照すると、KQL はその場で「無効なクエリ」と判断します。
  • 単なるタイプミスとまったく同じ扱いになるため、原因に気付きにくいのが難点です。

つまり、ドキュメントどおりに書いても、自分のテナントのスキーマと列名が少しでも違えばクエリは失敗します。問題の本質は「公式ドキュメントと現実のスキーマが完全には一致していない」点にあります。

IdentityInfo テーブルには「複数バージョン」が存在する

IdentityInfo テーブルがややこしいのは、1 つの名前に対して、実際には複数のスキーマが存在するためです。

代表的なバージョンは次の通りです。

バージョン主な利用場所特徴列名例
Log Analytics IdentityInfoAzure ポータルの Microsoft Sentinel (ログ画面)Sentinel UEBA 用の拡張スキーマ。UEBA 関連列が多い。AccountUPN, BlastRadius, EntityRiskScore など
Advanced Hunting IdentityInfo (旧)Defender ポータルの Advanced HuntingDefender XDR / Defender for Identity 由来。UEBA 列は限定的。AccountUpn ではないケースも存在
統合 IdentityInfo (Unified)Defender ポータル (Sentinel + UEBA 統合環境)Sentinel UEBA 由来の列と Defender 由来の列を統合した新スキーマ。AccountUpn、一部列名が統一&変更

Microsoft の公式情報によると、Defender ポータル側では 2025 年 5 月ごろから、Defender for Identity と Sentinel の IdentityInfo を統合した「統合 IdentityInfo テーブル」が段階的に展開されています。

この新しい統合スキーマでは、旧 Log Analytics 版の列名からいくつかが変更されており、その 1 つが AccountUPN → AccountUpn という変化です。

なぜテナントによって列名の大文字・小文字が違うのか

「同じ IdentityInfo なのに、別テナントでは列名が違う」という状況が起こる主な理由は次の通りです。

  • 利用ポータルの違い
    Azure ポータル (Sentinel/Log Analytics) なのか、Defender ポータル (Advanced Hunting) なのかで、読み出している IdentityInfo のバージョンが異なる。
  • UEBA の有効・無効
    UEBA を有効にしている Sentinel ワークスペースかどうかで、利用できる列・列名が変わる。
  • 統合スキーマの段階的ロールアウト
    統合 IdentityInfo への切り替えは一斉ではなく、テナント/リージョンごとの段階的な展開となっているため、同時期でもスキーマが揃っていない。
  • 新規テナントと既存テナントの差
    2025 年以降に作られたテナントでは最初から統合スキーマが前提、古いテナントでは旧スキーマが残っているケースがある。

この結果として、あるテナントでは AccountUPN が正解、別のテナントでは AccountUpn が正解、といった「ゆらぎ」が発生します。

Microsoft Q&A でも、AccountUPN と AccountUpn の違いに悩んだ事例に対して、「KQL は大文字・小文字を区別するので列名の違いが原因」「テナントごとにスキーマが異なるため、必ず自分の環境のスキーマを確認すること」といった回答が公式に示されています。

自分のテナントの IdentityInfo スキーマを確認する方法

ドキュメントよりも優先すべき「真実」は、常に自分のワークスペースのスキーマです。まずは、IdentityInfo の実スキーマを確認しておきましょう。

Azure ポータル (Sentinel / Log Analytics) から確認

  1. Azure ポータルで対象の Microsoft Sentinel ワークスペース を開く。
  2. 左メニューから ログ(または「Log Analytics」)を開く。
  3. 画面左側のテーブル一覧から IdentityInfo を選択。
  4. 右ペインの「列」一覧で、AccountUPN か AccountUpn のどちらがあるか確認する。

KQL で確認する場合は、次のようなクエリが便利です。

IdentityInfo
| take 0

take 0 はデータを返さず列見出しだけを表示してくれるので、列名の確認に向いています。

より詳細なスキーマを見たい場合は、次のようにします。

IdentityInfo
| getschema

もしくは、権限があれば Kusto の管理コマンドを実行しても構いません。

.show table IdentityInfo schema

なお、管理コマンドは「ログ」画面では実行できず、専用の Kusto 環境が必要な場合があります。

Defender ポータル (Advanced Hunting) から確認

  1. Defender ポータルにアクセスし、左メニューから Advanced hunting を開く。
  2. スキーマペインで IdentityInfo テーブルを選択。
  3. 列一覧から AccountUpn などの列名を確認する。

Defender 側では、統合 IdentityInfo 導入後に列名が統一されているケースが多く、AccountUpn 表記が標準的です。

列名ゆらぎに強い KQL パターン:column_ifexists + coalesce

最も実務的な解決策は、どちらの列名でも動くクエリを書くことです。その中心になる関数が次の 2 つです。

  • column_ifexists("列名", 既定値) 指定した列が存在すればその列を返し、存在しなければ既定値を返します。
  • coalesce(expr1, expr2, ...) 左から順に評価し、最初に null / 空でない値を返します。

この 2 つを組み合わせると、AccountUPN と AccountUpn の両方に対応したクエリを簡潔に書けます。

IdentityInfo
| extend AccountUPN_ = coalesce(
    tostring(column_ifexists("AccountUPN", "")),
    tostring(column_ifexists("AccountUpn", ""))
  )
| where isnotempty(AccountUPN_)
| project
    AccountUPN = AccountUPN_,
    BlastRadius,
    EntityRiskScore,
    GroupMembership

ポイントは次の通りです。

  • AccountUPN_ という内部列に、どちらか存在する方の値をまとめる。
  • where isnotempty(AccountUPN_) で UPN が取れていない行を除外。
  • project で最終的な列名を AccountUPN に統一して出力。

こうしておけば、AccountUPN しかない環境でも、AccountUpn しかない環境でも、同じクエリが動作します。

project-rename を常用しない方がよい理由

project-rename を使えば列名をまとめることもできますが、存在しない列を指定するとその場でクエリが失敗するため、列名ゆらぎ対応には向きません。

// × 存在しない列を指定すると失敗する例
IdentityInfo
| project-rename AccountUPN = AccountUpn

この書き方は「列が存在することが 100% 保証されている場合」にだけ使うのがおすすめです。テナントごとにスキーマが揺れる IdentityInfo では、column_ifexists で安全に受けるほうが現実的です。

他の列でも起きている「名前変更」の例

統合 IdentityInfo スキーマでは、AccountUPN → AccountUpn 以外にも、いくつかの列名が変更されています。

旧 Log Analytics 列名統合 IdentityInfo 列名意味の概要
AccountCloudSIDCloudSidEntra ID のセキュリティ ID
AccountCreationTimeCreatedDateTimeアカウント作成日時
AccountSIDOnPremSidオンプレ AD の SID
AccountTenantIdTenantIdテナント ID
AccountUPNAccountUpnユーザーの UPN
MailAddressEmailAddressプライマリ メールアドレス
SourceSystemIdentityEnvironmentデータソース (Entra / on-prem など)

IdentityInfo に依存した高度なクエリ(リスクベースの検出、ロールベースの検出など)を書いている場合、これらの名前変更も影響します。同じ考え方で、列名をマッピングする関数を 1 つ用意しておくと安全です。

UEBA 依存列への安全なアクセスパターン

UEBA を有効にしていないテナントでは、そもそも次のような列が存在しません。

  • EntityRiskScore
  • BlastRadius
  • AssignedRoles
  • GroupMembership(環境によって挙動が異なる)

これらの列は「便利だから」といって無条件に参照すると、UEBA 無効テナントでクエリが失敗します。ここでも column_ifexists が活躍します。

IdentityInfo
| extend
    AccountUPN_ = coalesce(
      tostring(column_ifexists("AccountUPN", "")),
      tostring(column_ifexists("AccountUpn", ""))
    ),
    EntityRiskScore_ = toint(column_ifexists("EntityRiskScore", int(null))),
    BlastRadius_     = toint(column_ifexists("BlastRadius", int(null)))
| where isnotempty(AccountUPN_)
| project
    AccountUPN = AccountUPN_,
    EntityRiskScore = EntityRiskScore_,
    BlastRadius = BlastRadius_

このように「UEBA があれば値が入る」「UEBA がなければ null になる」形にしておけば、テナントごとの差異でクエリが落ちることはありません。

既存の分析ルールを安全に書き換えるステップ

すでに AccountUPN 前提で大量の分析ルールやハンティングクエリを作ってしまっている場合、次のような手順で安全に書き換えることをおすすめします。

  1. スキーマを確認
    対象ワークスペースで IdentityInfo | take 0 を実行し、列名の実態を確認する。
  2. IdentityInfo 用の標準関数を作る
    後述のような「列名ゆらぎを吸収する関数」を 1 つ作成する。
  3. 新クエリは標準関数を前提に実装
    新規の分析ルールやワークブックでは、必ずその関数を経由して IdentityInfo を参照する。
  4. 既存ルールを段階的に置き換え
    影響が大きい重要ルールから順に、手動またはスクリプトでクエリを書き換える。
  5. テストワークスペースで先に検証
    可能であれば別ワークスペースにクローンし、新関数と新クエリで誤検知・検知漏れがないか確認する。

IdentityInfo 正規化関数の例

Sentinel のログ画面では、KQL 関数として共通処理を登録できます。IdentityInfo の列名ゆらぎと UEBA 有無を吸収する関数の例を示します。

let NormalizeIdentityInfo = () {
    IdentityInfo
    | extend
        AccountUPN_ = coalesce(
          tostring(column_ifexists("AccountUPN", "")),
          tostring(column_ifexists("AccountUpn", ""))
        ),
        TenantId_ = coalesce(
          tostring(column_ifexists("AccountTenantId", "")),
          tostring(column_ifexists("TenantId", ""))
        ),
        CloudSid_ = coalesce(
          tostring(column_ifexists("AccountCloudSID", "")),
          tostring(column_ifexists("CloudSid", ""))
        ),
        RiskScore_ = toint(column_ifexists("EntityRiskScore", int(null)))
    | where isnotempty(AccountUPN_)
    | project
        TimeGenerated,
        AccountUPN = AccountUPN_,
        TenantId   = TenantId_,
        CloudSid   = CloudSid_,
        EntityRiskScore = RiskScore_,
        AccountObjectId,
        AccountDisplayName,
        IdentityType = column_ifexists("IdentityType", "")
};
// 使用例:
NormalizeIdentityInfo()
| where EntityRiskScore >= 50

このような関数を 1 箇所に定義しておけば、クエリ側は「NormalizeIdentityInfo を使う」というルールさえ守れば、個別の列名変更を意識せずに済みます。

スキーマ差分を把握するための運用アイデア

IdentityInfo のようにテーブル側で積極的にスキーマ変更が行われる場合、「どのタイミングで何が変わったか」を可視化する運用を入れておくと安心です。

ワークブックでスキーマの「スナップショット」を取る

たとえば、次のような考え方でスキーマ監視用ワークブックを作成できます。

  • 定期的に IdentityInfo | getschema の結果を収集し、カスタム テーブルに格納。
  • 列名・型の変化を時系列チャートやテーブルで可視化。
  • 「列が増えた/減った/型が変わった」タイミングでアラートを出す。

実装には多少工夫が必要ですが、「ある日突然クエリが落ちて初めてスキーマ変更に気付く」といった事態を防げます。

テナント横断での比較

複数テナントを運用している場合は、各テナントの IdentityInfo スキーマを CSV などでエクスポートし、次のような観点で比較しておくとよいでしょう。

観点確認内容
列名の差AccountUPN / AccountUpn など、大文字・小文字を含む違いがないか。
列の有無EntityRiskScore, AssignedRoles など、UEBA 依存列の有無。
データ型RiskState のように、文字列 → 別の列へ移行していないか。
時間系列AccountCreationTime → CreatedDateTime など、名前変更された日時列。

よくある疑問とその答え

同じテーブル「バージョン」でもスキーマが違うことはある?

あります。

同じ「Log Analytics スキーマ版 IdentityInfo」であっても、テナントごとの有効機能やロールアウトのタイミングにより、列の追加・削除・名前変更がずれることがあります。特に統合 IdentityInfo の提供開始前後は、「一部テナントだけ先行して列名が変わっている」といった状態になりやすいです。

最終的にはどの表記に揃えればよい?

Defender ポータル側の統合 IdentityInfo では、公式ドキュメント上の列名が AccountUpn になっており、こちらが今後の標準表記と考えられます。

とはいえ、Log Analytics 版や古いワークスペースでは AccountUPN が残っている環境もあるため、クエリ内では column_ifexists + coalesce で両対応しつつ、出力列名はどちらかに統一する、という設計が現実的です。

ドキュメントと自分の環境、どちらを信じるべき?

最終的な正解は、常に「自分のワークスペースの実スキーマ」です。

ドキュメントはあくまで「代表的なスキーマ」を示しているに過ぎず、ロールアウト済み・未ロールアウト、機能有効・無効といった違いまでは表現しきれません。クエリが本番に載る前に、take 0 や getschema、ポータルの列一覧で必ず実スキーマを確認しましょう。

すぐにできるチェックリスト

最後に、今回のようなトラブルを避けるために、今すぐ実施できる項目をチェックリスト形式でまとめます。

チェック項目状態
IdentityInfo の列一覧(特に UPN 列)を、自テナントですでに確認した。□ 未 / □ 済
分析ルールやハンティングクエリで、AccountUPN / AccountUpn を直接参照している部分を洗い出した。□ 未 / □ 済
column_ifexists と coalesce を用いた「列名ゆらぎ」を吸収するパターンを、少なくとも IdentityInfo で導入した。□ 未 / □ 済
IdentityInfo をラップする共通関数を作成し、新規クエリでは必ずその関数を使うようにした。□ 未 / □ 済
UEBA 依存列(EntityRiskScore など)を、存在前提ではなく column_ifexists 経由で参照するよう変更した。□ 未 / □ 済
スキーマ変更に気付けるよう、getschema の結果を確認する運用(ワークブック/手動チェック)を検討または導入した。□ 未 / □ 済

まとめ

IdentityInfo テーブルの AccountUPN / AccountUpn 問題は、「KQL の大文字・小文字の区別」と「テナントごとに異なる IdentityInfo スキーマ」が組み合わさって発生する、典型的なハマりポイントです。

しかし、根本的な考え方はシンプルです。

  • ドキュメントだけを信じず、必ず自分のワークスペースのスキーマを確認する。
  • column_ifexists と coalesce で列名のゆらぎを吸収するクエリパターンを用意する。
  • IdentityInfo をラップする共通関数を作り、そこでだけスキーマ差分を意識する。
  • UEBA 依存列や統合 IdentityInfo による列名変更を前提に、クエリを柔軟にしておく。

この 4 点を押さえておけば、今後 IdentityInfo のスキーマが多少変化しても、突然分析ルールが大量にエラーになる、といったリスクを大きく減らせます。IdentityInfo はうまく使えば非常に強力なテーブルです。スキーマの揺らぎとうまく付き合いながら、Microsoft Sentinel / Defender XDR のハンティングと検出精度を一段引き上げていきましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次