ここが変だよ、AADサインインログ - part2(誤ったUserPrincipalNameの使い方編)
AADサインインログとは
Azure Active Directory(以下、AAD)の認証時に発生するログです。
AADの[サインインログ]から確認することや、
Azure Active Directoryの[診断設定]からログを出力させることもできます。
本稿では[診断設定]で出力できる以下のログの中でもSigninLogsについて、
”ログとしてここが扱いづらい!”
という点をまとめていきます。
UserPrincipalName(UPN)とは
UPNはActive Directoryのユーザー属性の一つで、ユーザーを一意に特定できます。(フォレスト内重複不可)
Azure ADにおいても同じく、ユーザーを一意に特定できるものとして実装されており、サインインログを分析するクエリなどでも度々登場します。
例えばログ分析のチュートリアルに記載の以下KQLを見てみましょう。
AuditLogs
| where OperationName contains "Add User"
| extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| project TimeGenerated, UserPrincipalName
| join kind = inner (SigninLogs) on UserPrincipalName
| summarize arg_min(TimeGenerated, *) by UserPrincipalName
| extend SigninDate = TimeGenerated
このKQLはAzure AD監査ログ中に"Add User"、つまり追加されたユーザーのUPN(TargetResources[0].userPrincipalName)と一致するサインインログを探し、その一番古いログ(つまり追加されたユーザーの時間スコープ内における初回ログイン)を表示するものです。
UserPrincipalNameにUserIdが紛れ込む問題
ここからが本題ですが、Azure ADサインインログを見ていると以下のようなログが出力されることがあります。
UserPrincipalNameの値は...
188dcfb4-b30f-43a3-a6e2-905587fa0423
!?!?
そうです、UserPrincipalNameの値がUserIdとなることがあるのです。(何故)
ちなみにユーザーIDが188dcfb4-b30f-43a3-a6e2-905587fa0423であるユーザーは実際に存在し、当然ながらUserPrincipalNameは定義されています。
(更にいうとユーザーIDが188dcfb4-b30f-43a3-a6e2-905587fa0423で正常なUserPrincipalNameが出力されているログは同時間帯に存在する)
何が問題?
例えばAzure ADサインインログに含まれるユーザーごとに、過去90日にAzure Portalに対してサインイン試行した数を数えようとします。
※サインイン試行後にサインインが成功したか失敗したかはこの例では検討しません
特に意識せずにUserPrincipalNameを使用してKQLを書くと以下のようになりがちかと思います。
SigninLogs
| where TimeGenerated > ago(90d)
| where AppDisplayName == "Azure Portal"
| summarize count() by UserPrincipalName
しかし結果を見てみると...
ご覧の通りUserPrincipalNameにUserIdが出力されていることで、本来表示されるべきUserPrincipalNameとは別々に計上されてしまい、同じアカウントからのサインイン試行件数を正確にカウントすることができません。
チュートリアルのKQLに関しても
| join kind = inner (SigninLogs) on UserPrincipalName
および
| summarize arg_min(TimeGenerated, *) by UserPrincipalName
でUserPrincipalNameを主キーとして結合、集計しているため、
問題となっているログに関しては正しく結合されず、集計もおかしな結果を導いてしまうことになります。
どうすればいいの?
Azure ADサインインログ中のUserPrincipalNameとUserIdが一致しないテーブルを用意して、UserPrincipalNameとUserIdの対応表をlet句を使って作成します。
let UPNResolver = SigninLogs
| where UserId <> UserPrincipalName
| distinct UserId, UserPrincipalName;
次に例と同じように過去90日にAzure ADに対してサインイン試行した数を集計しますが、ここでUserPrincipalNameごとにではなく、UserIdごとに集計します。
SigninLogs
| where TimeGenerated > ago(90d)
| where AppDisplayName == "Azure Portal"
| summarize count() by UserId
集計した値はUserIdごとに出力されるため、先に定義した対応表を使って結合(leftouter join)を行い、UserIdに対応するUserPrincipalNameを結合します。最後に重複するカラムとなるUserId1を出力から除外(project-away)します。
let UPNResolver = SigninLogs
| where UserId <> UserPrincipalName
| distinct UserId, UserPrincipalName;
SigninLogs
| where TimeGenerated > ago(90d)
| where AppDisplayName == "Azure Portal"
| summarize count() by UserId
| join kind=leftouter UPNResolver on UserId
| project-away UserId1
このようにすることで正しいUserPrincipalNameとユーザーごとに集計した値を出力させることができます。
ちなみに上図の一番下のログはUserPrincipalNameが出力されていないように見えますが、これは出力が正しくてAzure AD上に当該ユーザーが存在しないことを表します。
この話については次回「ステータスコードが複雑」編で解説したいと思います。
総括すると、どうしてUserIdがUserPrincipalNameに出力されてしまうかはわかりませんし、いつ改修されるかについても不明ですが、現時点ではユーザーの主キーとしてはUserIdを使ってKQLを記載することを推奨します。
その仕様の割にUserPrincipalNameをキーとして記載されているKQLが多いのでMicrosoft様何とか改修してください。。。
part1は以下です。良ければご一緒にどうぞ。
この記事が気に入ったらサポートをしてみませんか?