Ведение журнала аудита в Базе данных Azure для PostgreSQL для принципалов Microsoft Entra ID

Аудит базы данных является важным компонентом требований к соответствию вашей организации. Отслеживая целевые действия, вы можете достичь базовых показателей безопасности. В гибком сервере Базы данных Azure для PostgreSQL можно настроить аудиты с помощью расширения PGaudit PG, как описано в журнале аудита в Базе данных Azure для PostgreSQL.

Одна из проблем заключается в использовании функции аудита вместе с аутентификацией Microsoft Entra ID, когда вы используете группы Microsoft Entra ID и хотите провести аудит действий отдельных участников группы. Проблема заключается в том, что участники группы входят в систему, используя свои личные токены доступа, но применяют имя группы в качестве имени пользователя.

Язык запросов Kusto (KQL) — это мощный язык запросов, управляемый конвейером, который позволяет запрашивать журналы служб Azure. KQL поддерживает запросы журналов Azure для быстрого анализа большого объема данных. В этой статье используйте KQL для запроса журналов Azure Postgres и извлечения сведений о пользователях идентификатора Microsoft Entra из журналов аудита.

Предпосылки

  1. Включение ведения журнала аудита — ведение журнала аудита в базе данных Azure для PostgreSQL
  2. Включите отправку журналов Azure Postgres в Службу аналитики журналов Azure — Настройка Log Analytics
  3. log_line_prefix Настройте параметр сервера: в колонке "Параметры сервера" задайте log_line_prefix для включения escape-адресов user=%u,db=%d,session=%c,sess_time=%s в ту же последовательность, чтобы получить нужные результаты.
    • Перед: log_line_prefix = %t-%c-
    • После: log_line_prefix = %t-%c-user=%u,db=%d,session=%c,sess_time=%s

Запрос Kusto

Следующий запрос Kusto запрашивает AzureDiagnostics два раза.
Первый вложенный запрос находит все строки, содержащие строку Microsoft Entra ID connection authorized, и извлекает из них PrincipalName вместе со строкой SessionId журнала.
Второй вложенный запрос находит все журналы аудита.
Наконец, эти два подзапроса соединены по SessionId.

let lookbackTime = ago(3d);
let opindex = 3;
let startIndex = toscalar(range thirdIndex from opindex to opindex step 1
    | project thirdIndex);
AzureDiagnostics
| where ResourceProvider == 'MICROSOFT.DBFORPOSTGRESQL'
| where TimeGenerated >= lookbackTime
| where Message contains 'Microsoft Entra ID connection authorized'
| extend SessionId = tostring(split(tostring(split(Message, 'session=')[-1]), ',sess_time')[-2])
| extend UPN = iff(Message contains 'UPN',tostring(split(tostring(split(Message, 'UPN=')[-1]), 'oid=')[-2]), '')
| extend appId = iff(Message contains 'appid', tostring(split(tostring(split(Message, 'appid=')[-1]), 'oid=')[-2]), '')
| extend PrincipalName = strcat(UPN, appId)
| project SessionId, PrincipalName
| join kind=leftouter
    (
    AzureDiagnostics
    | where ResourceProvider == 'MICROSOFT.DBFORPOSTGRESQL'
    | where TimeGenerated >= lookbackTime
    | where Message contains 'AUDIT: SESSION'
    | extend RoleName = tostring(split(tostring(split(Message, 'user=')[-1]), ',db')[-2])
    | where RoleName !in ('azuresu', '[unknown]', 'postgres', '')
    | extend SessionId = tostring(split(tostring(split(Message, 'session=')[-1]), ',sess_time')[-2])
    | extend SubMessage = tostring(split(Message, 'SESSION,')[-1])
    | extend splitArray = split(SubMessage, ',')
    | extend SqlQueryP1 = tostring(split(tostring(split(Message, ',,,')[-1]), ',<')[-2])
    | extend SqlQueryP2 = replace_string(tostring(split(SqlQueryP1, ',\"')[-1]), '"', '')
    | extend SqlQueryP3 = tostring(split(Message, ',,,')[1])
    | extend OperationType = tostring(splitArray[startIndex])
    | extend SqlQuery = trim('"', case(OperationType == 'EXECUTE', SqlQueryP2, SqlQueryP1 == '', SqlQueryP3, SqlQueryP1))
    )
    on $left.SessionId == $right.SessionId
| project TimeGenerated, PrincipalName, RoleName, OperationType, SqlQuery

Пример результатов

Результирующая таблица выглядит следующим образом:

Время генерации Основное имя НазваниеРоли Тип операции SqlQuery
2025-12-12T16:25:05.104Z user@example.com ИмяПримераГруппы SELECT выберите * из pg_seclabels;
2025-12-12T16:25:04.000Z user@example.com user@example.com SELECT выберите * из pg_seclabels;

Если пользователь входит в роль группы, столбцы PrincipalName и RoleName отображают разные значения, например, в первой строке примера.
Значение PrincipalName определяет пользователя, выполнившего вход. Значение RoleName определяет роль в PostgreSQL, к которой пользователь получает доступ после входа.

PrincipalNameимя субъекта-пользователя (UPN) или AppId в зависимости от того, входит ли субъект-пользователь или субъект-служба.