Azure SQL Database の Defender for SQL「最終スキャン日時」がKQLで取れない・ズレる原因と対処(0001/01/01対策)

Azure SQL Database の Defender for SQL 画面では「最終スキャン日時」が見えるのに、Azure Resource Graph の KQL で取得すると古い日時や 0001/01/01 が返ることがあります。本記事では原因の整理と、現場で迷わない切り分け・対処手順をまとめます。

目次

現象:ポータルの「最終スキャン日時」と KQL が噛み合わない

Microsoft Defender for Cloud(Defender for SQL)を運用していると、Azure ポータル上では「最終スキャン日時」が最新になっているのに、Azure Resource Graph(ARG)などで KQL を使って同じ値を引こうとしても一致しない、という事象に遭遇することがあります。

確認場所見えている/返ってくる値の例現場で起きがちな困りごと
Azure ポータル(Defender for SQL の画面)最終スキャン日時:2025/8/3運用担当は「最新スキャン済み」と判断できる
KQL(SecurityResources など)properties.timeGenerated:想定と違う(=スキャン実行日時ではなさそう)どの時刻が「スキャン」なのか判断できない
KQL(SecurityResources など)properties.additionalData.lastReportedScanTime:2024/3/23 または 0001/01/01古い/初期値になり、ポータルと整合しない

この相談で最も重要なのは、「このフィールドを見れば必ずポータルと同じ最終スキャン日時が取れる」という前提が、現場では崩れることがある点です。特定の SQL サーバー/DB に限って lastReportedScanTime が欠落(初期値扱い)し、KQL だけで完結した棚卸しや監視が難しくなります。

まず押さえるべき前提:Defender for SQL の「最終スキャン日時」は何を指すのか

ポータルに表示される「最終スキャン日時」は、一般に SQL Vulnerability Assessment(脆弱性評価、VA) のスキャン実行に紐づく日時として扱われます。

一方で Defender for SQL という名称の中には複数の要素が混在します。ここを混同すると「KQL で見ているもの」と「ポータルが見せているもの」がズレる原因になります。

機能/概念何をするものか「最終スキャン日時」との関係
Vulnerability Assessment(VA)構成や設定から脆弱性/推奨事項をスキャンし、レポートを生成する主にここ(スキャンの実行・結果の生成に紐づく)
セキュリティ推奨事項(Assessments)リソース状態を評価し「対応すべき推奨事項」を算出する評価の更新時刻(timeGenerated)と混同しやすい
脅威検知/アラート不審な挙動を検知しアラートを出す通常「最終スキャン日時」とは別の時系列で動く

つまり、ポータルの「最終スキャン日時」は VA のスキャン履歴 を元に表示される一方、KQL で参照しがちな SecurityResources(assessments) は「評価レコード」です。ここを同一視すると、ズレが“障害”なのか“見ているものが違う”だけなのかを見誤ります。

補足:同じ KQL でも「どこで実行しているか」で意味が変わる

「KQL」と一口に言っても、Azure ではデータソースが複数あります。今回の話題(SecurityResources)は主に Azure Resource Graph 側の KQL で、Log Analytics の KQL とはデータの性質が違います。

KQL を実行する場所代表例得意なこと今回のポイント
Azure Resource GraphResource Graph Explorer、ワークブックなどリソース全体の棚卸し、状態の俯瞰SecurityResources を見る場合はこちら
Log AnalyticsLog Analytics workspace、Azure Monitorログ/テレメトリの時系列分析、詳細な相関VA の「スキャン実行ログ」が常に入るとは限らない

混乱しやすい点ですが、どちらも見た目が KQL なので「同じはず」と思いがちです。まずは どのデータを KQL で見ているかを明確にしてから切り分けるのが安全です。

ズレの本質:スキャン実行時刻・レポート時刻・評価生成時刻は一致しない

この問題を整理するため、運用上よく出てくる「3つの時刻」を分けて考えます。

時刻の種類意味どこに出やすいかズレが起きる理由
スキャン実行時刻VA のスキャン処理を走らせた(走り始めた/終わった)時刻ポータルの VA 画面、スキャン履歴スキャンは DB 側で実行され、評価生成とは別処理
スキャン結果の報告時刻スキャン結果が Defender 側へ報告された時刻KQL の additionalData(例:lastReportedScanTime)報告経路の遅延/欠落で、空や初期値になり得る
評価レコード生成時刻Defender for Cloud が評価(推奨事項)レコードを生成・更新した時刻KQL の timeGenerated評価はスキャン以外の要因でも更新される

ここで重要なのが、ポータルに見えている「最終スキャン日時」と、KQL の timeGenerated が一致しないこと自体は“あり得る”という点です。timeGenerated は「スキャン時刻」ではなく「評価レコードの生成/更新時刻」として動くことがあります。

ただし本件の争点は、想定どおりならスキャン報告時刻として利用できそうな lastReportedScanTime が古い/0001/01/01 になるという部分です。ここは単なる“仕様上のズレ”では片付けづらく、値が格納されていない(欠落している)可能性が出てきます。

結論:KQL 側の値が一部リソースで正しく入らないケースがあり、KQL だけで完結しにくい

運用の結論としては次のとおりです。

  • lastReportedScanTime が初期値(0001/01/01)や古い値のままになり、ポータルと一致しないケースがある
  • その場合、「このフィールドを見れば必ず取れる」という形で KQL のみで完結させるのは難しい
  • 切り分けは 手動スキャン → KQL の再確認 → サポート調査 の順が現実的

KQL で見える代表フィールドと、期待値とのギャップ

Azure Resource Graph で Defender 関連を追う際、よく触るのが SecurityResources テーブルです。ここで「最終スキャン日時」に見える候補は主に次の2つです。

フィールド見え方の例読み取り上の注意ポータルの「最終スキャン日時」と一致する可能性
properties.timeGenerated2025-08-03T02:15:44Z など評価レコードの生成/更新時刻として解釈されやすい一致しないことがある(ズレても不思議ではない)
properties.additionalData.lastReportedScanTime2024-03-23T00:00:00Z / 0001-01-01T00:00:00Z など値が欠落すると “初期値” が入っているように見える本来は一致してほしいが、一致しない/初期値になる例がある

確認用 KQL(Azure Resource Graph)例

まずは「どのリソースでズレているか」を可視化します。以下は Azure Resource Graph Explorer で実行することを想定した例です(環境や評価項目により、フィルタ条件やプロパティの場所は調整してください)。

SecurityResources
| where type =~ "microsoft.security/assessments"
| extend rd = todynamic(properties.resourceDetails)
| extend ad = todynamic(properties.additionalData)
| extend targetId = tostring(rd.id)
| where targetId contains "/providers/Microsoft.Sql/servers/" and targetId contains "/databases/"
| extend timeGenerated = todatetime(properties.timeGenerated)
| extend lastReportedScanTime = todatetime(ad.lastReportedScanTime)
| extend lastReportedScanTimeIsDefault = lastReportedScanTime == datetime(0001-01-01)
| project
    subscriptionId,
    targetId,
    assessmentId = name,
    assessmentName = tostring(properties.displayName),
    status = tostring(properties.status.code),
    timeGenerated,
    lastReportedScanTime,
    lastReportedScanTimeIsDefault
| order by timeGenerated desc

この結果で、lastReportedScanTimeIsDefault が true になる DB が混じっている場合、「値が入っていない」ことが疑われます。ポータル上の最終スキャン日時が更新されているのに KQL 側が初期値のままなら、ユーザー側だけで整合させるのが難しい可能性が高まります。

“初期値” を運用上の null として扱う(表示崩れを防ぐ)

ダッシュボードや棚卸しで 0001/01/01 をそのまま出すと誤解を招くため、運用上は「不明(null)」に置き換えるのが安全です。

SecurityResources
| where type =~ "microsoft.security/assessments"
| extend rd = todynamic(properties.resourceDetails)
| extend ad = todynamic(properties.additionalData)
| extend targetId = tostring(rd.id)
| where targetId contains "/providers/Microsoft.Sql/servers/" and targetId contains "/databases/"
| extend lastReportedScanTimeRaw = todatetime(ad.lastReportedScanTime)
| extend lastReportedScanTime =
    iff(lastReportedScanTimeRaw == datetime(0001-01-01), todatetime(null), lastReportedScanTimeRaw)
| project targetId, lastReportedScanTime

ただしこれは「表示上の応急処置」であり、ポータルと同じ最終スキャン日時を再現できる保証はありません。

なぜ 0001/01/01 や古い日時になるのか:考えられる原因と切り分け

0001/01/01 は DateTime の最小値(初期値)として扱われることが多く、「値が未設定/欠落」でも返ってきてしまう典型例です。Defender 関連の評価データでも、何らかの理由でスキャン時刻が記録されないと、結果としてこのような値に見えることがあります。

原因は1つに限りません。運用で役立つように、「現場でよく当たる原因」と「確認ポイント」を表にまとめます。

原因候補どう見えるか確認ポイント次のアクション
そもそも VA スキャンが一度も実行されていない0001/01/01、または null 相当VA が有効か、スキャン履歴が存在するか手動スキャンを実行して履歴を作る
スキャンは実行されたが、Defender 側への報告が欠落/遅延ポータルは更新されるのに KQL では古い/初期値同じ DB で「評価レコード」が更新されているか再現条件を揃えてサポート調査へ
timeGenerated を「スキャン実行時刻」と誤認しているtimeGenerated がポータル表示と一致しないtimeGenerated は評価レコードの更新時刻である可能性lastReportedScanTime を優先し、timeGenerated は補助と割り切る
UTC/ローカル表示の差、表示丸め日付が1日ズレたように見えるポータル表示がローカル時刻か、KQL が UTC かUTC で統一して比較する(変換して確認)
リソース種別や評価項目の取り違え見ている assessment が想定外対象 DB の resourceId と assessment の対応resourceDetails.id で DB を特定して絞り込む

この中で、最も厄介なのが 「手動スキャンを実行しても lastReportedScanTime が初期値のまま」というパターンです。これはユーザー側設定だけで直せない可能性があり、次章のフローで対応するのが現実的です。

推奨の対処フロー:手動スキャンで切り分け → サポートへ

「KQL で取れる/取れない」を議論する前に、まずはデータが正しく生成・報告されているかを切り分けます。運用での再現性を上げるため、順番に実施するのがポイントです。

事前確認(設定・権限)

  • 対象の Azure SQL Database(またはサーバー)で Defender for SQL が有効になっている
  • Vulnerability Assessment(VA)が有効になっており、スキャンを実行できる状態になっている
  • 手動スキャンを実行できる権限(例:SQL セキュリティ管理相当/SQL への管理権限)を持つ担当者がいる
  • Defender for Cloud の評価データを参照できる権限(例:Security Reader 以上)がある

手動スキャンの実行(再現性のある切り分け)

バックエンドログにアクセスできる管理者(または該当権限を持つ担当者)に依頼し、対象 DB に対して 手動スキャンを実行します。ここでの狙いは次の2点です。

  • ポータル上の「最終スキャン日時」が確実に更新される状態を作る
  • その直後の KQL で lastReportedScanTime が更新されるかを確認する

KQL 側での再確認

手動スキャン後、前述の KQL を同じ条件で実行し、以下を確認します。

  • lastReportedScanTime が手動スキャンの実施時刻に追従して更新されるか
  • 更新されない場合、対象が「特定のサーバー/DB に偏る」のか「全体的に発生する」のか
  • 同じ DB でも assessment によって値の入り方が異なるか(displayName/assessmentId で差がないか)

手動スキャン後も初期値/不正な値なら、Microsoft サポートへ

手動スキャン後も 0001/01/01 が返る、あるいは明らかに古い日時のまま変化しない場合、KQL 側(Defender の評価データ)の不整合として、Microsoft サポートでの調査・修正依頼が必要になることがあります。

サポートに渡す情報を揃えると、調査がスムーズになります。

用意すると良い情報具体例意図
対象リソースの Resource ID/subscriptions/…/resourceGroups/…/providers/Microsoft.Sql/servers/…/databases/…どの DB で発生しているかを一意に特定
ポータル表示の最終スキャン日時2025/8/3(スクリーンショット含む)期待値(正しいと考える値)を明確化
KQL の実行結果assessmentId / lastReportedScanTime / timeGenerated など「KQL 側の欠落」を客観化
手動スキャン実施の記録実施日時、実行者、対象 DB再現条件を固定し、因果関係を示す
影響範囲特定サーバーのみ/複数サブスクリプション/Managed Instance も含む 等障害か設定かを切り分けやすくする

運用で困らないための回避策:KQL に依存しすぎない

運用の理想は KQL だけで「最終スキャン日時」を棚卸し・監視できることです。しかし、lastReportedScanTime が安定しないケースがある以上、KQL を唯一の正としない設計が安全です。

回避策1:KQL は “異常検知” に寄せる

最終スキャン日時を厳密に揃えるのではなく、次のような使い方に寄せると、欠落しているリソースを早期にあぶり出せます。

  • lastReportedScanTime が 0001/01/01 の DB を「要確認」として抽出する
  • 一定期間更新がない DB を抽出し、手動スキャンや設定確認につなげる
SecurityResources
| where type =~ "microsoft.security/assessments"
| extend rd = todynamic(properties.resourceDetails)
| extend ad = todynamic(properties.additionalData)
| extend targetId = tostring(rd.id)
| where targetId contains "/providers/Microsoft.Sql/servers/" and targetId contains "/databases/"
| extend lastReported = todatetime(ad.lastReportedScanTime)
| extend isMissing = (isnull(lastReported) or lastReported == datetime(0001-01-01))
| where isMissing
| project targetId, assessmentId = name, assessmentName = tostring(properties.displayName)

回避策2:正の「スキャン履歴」を別経路で取得し、KQL で参照できる場所へ集約する

ポータルが参照しているスキャン履歴に近い情報は、評価レコード(SecurityResources)とは別経路で取得できる場合があります。運用としては、

  • スキャン履歴から「最新のスキャン日時」を取得する
  • その値を Log Analytics(カスタムログ)や Storage に蓄積する
  • 蓄積先を KQL で参照してダッシュボード化する

という構成にすると、「Defender 側の評価データに欠落がある」状況でも、監査対応や月次レポートが回ります。特に 日時の正確性が求められる用途では有効です。

比較時のチェックリスト:見落としを減らす

最後に、ポータルと KQL を比較するときの見落としを防ぐチェックリストです。

チェック項目見落とすとどうなるかおすすめの確認方法
対象 DB の Resource ID を揃えているか別 DB の assessment を見て「値が違う」と誤判定properties.resourceDetails.id で一致確認
ポータル表示のタイムゾーン1日ズレて見えるUTC に換算して比較(日時の表示形式も統一)
どの assessment を見ているか関係ない評価項目の値を拾うdisplayName/assessmentId を記録し、対象項目を固定
0001/01/01 を「スキャン済み」と誤認していないか実際は未報告なのに正常扱いしてしまう初期値は null と同等に扱い、アラート対象にする

まとめ

Azure ポータルで見える Defender for SQL の「最終スキャン日時」を、KQL(SecurityResources など)から同じ値として取得しようとすると、timeGenerated が別の意味になっていたり、lastReportedScanTime が初期値/古い値のままだったりして一致しないことがあります。

  • timeGenerated は“スキャン時刻”とは限らない(評価レコードの生成・更新時刻として動く)
  • lastReportedScanTime が初期値になるのは、値が正しく反映されていない可能性がある
  • 手動スキャンで切り分け、再現するなら Microsoft サポートへ不整合として調査依頼するのが近道

KQL だけで完結しない状況でも、切り分けの順番を押さえておけば、運用を止めずに原因の特定と改善に進めます。

この記事を書いた人

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

コメント

コメントする

目次