Journalisation d’audit pour les principaux Microsoft Entra ID dans Azure Database pour PostgreSQL Flexible Server

Les audits de base de données constituent un composant important des exigences de conformité de votre organisation. En surveillant les activités ciblées, vous pouvez atteindre votre base de référence de sécurité. Dans le serveur flexible Azure Database pour PostgreSQL, vous pouvez configurer des audits à l’aide de l’extension pgaudit PG, comme décrit dans la journalisation d’audit dans Azure Database pour PostgreSQL.

L’un des défis consiste à utiliser la fonctionnalité d’audit en même temps que l’authentification d’ID Microsoft Entra lorsque vous utilisez des groupes d’ID Microsoft Entra et que vous souhaitez auditer les actions des membres individuels du groupe. Ce défi existe, car les membres du groupe se connectent à l’aide de leurs jetons d’accès personnels, mais utilisent le nom du groupe comme nom d’utilisateur.

Kusto Query Language (KQL) est un langage de requête puissant piloté par pipeline et en lecture seule qui permet d’interroger les journaux du service Azure. KQL permet de consulter les journaux Azure pour analyser rapidement un volume important de données. Pour cet article, utilisez KQL pour interroger les journaux Azure Postgres et extraire les informations utilisateur Microsoft Entra ID des journaux d'audit.

Prerequisites

  1. Activer la journalisation d’audit - Journalisation d’audit dans Azure Database pour PostgreSQL
  2. Activer l’envoi des journaux Azure Database for PostgreSQL vers Azure Log Analytics - Configurer les journaux et y accéder
  3. Ajustez le paramètre log_line_prefix : dans le volet Paramètres, définissez log_line_prefix de manière à inclure les séquences d’échappement user=%u,db=%d,session=%c,sess_time=%s dans le même ordre afin d’obtenir les résultats souhaités.
    • Avant: log_line_prefix = %t-%c-
    • Après: log_line_prefix = %t-%c-user=%u,db=%d,session=%c,sess_time=%s

Requête Kusto

La requête Kusto suivante interroge les AzureDiagnostics deux fois.
La première sous-requête recherche toutes les lignes qui contiennent la chaîne Microsoft Entra ID connection authorized et extrait le PrincipalName de ces lignes de journal, ainsi que le SessionId.
La deuxième sous-requête recherche tous les journaux d’audit.
Enfin, ces deux sous-requêtes sont jointes sur le 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

Exemple de résultats

La table résultante ressemble à ceci :

TimeGenerated PrincipalNom Nom du rôle Type d'opération SqlQuery
2025-12-12T16:25:05.104Z user@example.com ExampleGroupName SELECT sélectionnez * dans pg_seclabels ;
2025-12-12T16:25:04.000Z user@example.com user@example.com SELECT sélectionnez * dans pg_seclabels ;

Si l’utilisateur se connecte en tant que rôle de groupe, les colonnes PrincipalName et RoleName affichent des valeurs différentes, comme dans la première ligne de l’exemple.
La PrincipalName valeur identifie l’utilisateur qui s’est connecté. La RoleName valeur identifie le rôle dans PostgreSQL auquel l’utilisateur accède après la connexion.

PrincipalName correspond soit au nom principal de l'utilisateur (UPN) soit à l'AppId, selon que c'est le principal utilisateur ou le principal de service qui se connecte.