Registro de auditoría para entidades de Microsoft Entra ID en Servidor flexible de Azure Database for PostgreSQL

Las auditorías de base de datos son un componente importante de los requisitos de cumplimiento de la organización. Al supervisar las actividades específicas, puede alcanzar su línea base de seguridad. En el servidor flexible de Azure Database for PostgreSQL, puede configurar auditorías mediante la extensión pgaudit PG, como se describe en Registro de auditoría en Azure Database for PostgreSQL.

Un desafío consiste en usar la función de auditoría junto con la autenticación de Microsoft Entra ID cuando usas grupos de Microsoft Entra ID y quieres auditar las acciones de los miembros individuales del grupo. Este desafío existe porque los miembros del grupo inician sesión con sus tokens de acceso personal, pero usan el nombre del grupo como nombre de usuario.

El lenguaje de consulta Kusto (KQL) es un eficaz lenguaje de consulta basado en canalización y de solo lectura que permite consultar registros de servicio de Azure. KQL admite la consulta de registros de Azure para analizar rápidamente un gran volumen de datos. Para este artículo, utilice KQL para consultar los registros de Azure Postgres y extraer la información de los usuarios de Microsoft Entra ID de los registros de auditoría.

Prerrequisitos

  1. Habilitación del registro de auditoría: registro de auditoría en Azure Database for PostgreSQL
  2. Habilitación de los registros de Azure Postgres que se enviarán a Azure Log Analytics: configuración y acceso a los registros
  3. Ajuste el parámetro log_line_prefix: en el panel Parámetros, configure log_line_prefix para que incluya los escapes user=%u,db=%d,session=%c,sess_time=%s en la misma secuencia para obtener los resultados deseados.
    • Antes: log_line_prefix = %t-%c-
    • Después: log_line_prefix = %t-%c-user=%u,db=%d,session=%c,sess_time=%s

Consulta de Kusto

La siguiente consulta de Kusto consulta las AzureDiagnostics dos veces.
La primera subconsulta busca todas las líneas que contienen la cadena Microsoft Entra ID connection authorized y extrae las PrincipalName de estas líneas de registro, junto con SessionId.
La segunda subconsulta busca todos los registros de auditoría.
Por último, estas dos subconsultas se unen en el 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

Ejemplos de resultado

La tabla resultante tiene este aspecto:

TimeGenerated Nombre principal NombreDelRol Tipo de operación SqlQuery
2025-12-12T16:25:05.104Z user@example.com ExampleGroupName SELECT seleccione * en pg_seclabels;
2025-12-12T16:25:04.000Z user@example.com user@example.com SELECT seleccione * en pg_seclabels;

Si el usuario inicia sesión como rol de grupo, las PrincipalName columnas y RoleName muestran valores diferentes, como en la primera fila del ejemplo.
El PrincipalName valor identifica al usuario que inició sesión. El RoleName valor identifica el rol en PostgreSQL al que el usuario accede después de iniciar sesión.

PrincipalName es el nombre principal de usuario (UPN) o AppId, dependiendo de si inicia sesión un principal de usuario o un principal de servicio.