Aller au contenu

Journal d'audit

La tuile Journal d'audit

Show-AuditLogAvalonia (ou la tuile Journal d'audit de Start-HGPM) montre le journal d'audit de HGPM : chaque modification acceptée par le domaine, depuis une console ou une commande, la plus récente d'abord. Qui, quand, depuis quel ordinateur, sur quel contrôleur de domaine, quoi et sur quelle GPO, UO ou réglage. Il ne demande aucun droit sur le domaine : seulement la lecture du dossier du journal.

Show-AuditLogAvalonia
Show-AuditLogAvalonia -Path \\fichiers\gpo-audit              # un autre dossier
Show-AuditLogAvalonia -Server dc.hardenbox.labo -Language fr-FR

Le journal d'audit

Le journal d'audit sous Windows, le partage central

Les modifications

Colonne
Heure quand, à l'heure de l'ordinateur (le journal garde l'UTC)
Compte le compte de l'annuaire qui a fait la modification
Action quoi, par son nom : GPO créée, Lien modifié, Paramètres d'une GPO enregistrés, GPO approuvée...
GPO ou objet la GPO, sinon l'UO, le domaine ou la configuration
Tier le dossier du journal central où elle est : Tier 0, 1, 2, Sans tier
Vérification si la modification est celle que son compte a écrite : Vérifiée, Altérée, Tier différent, Propriétaire différent, Non chaînée (plus bas)
Ordinateur, Domaine d'où, sur quel domaine
  • La période : les dernières 24 heures, 7 jours, 30 jours (au départ) ou tout.
  • Le domaine et l'action : ceux que contient le journal.
  • La recherche lit tous les textes d'une modification, détails compris : chaque mot doit être trouvé (registry.pol tiers1).
  • Les détails de la modification choisie : tout ce que le journal en garde, comme le contrôleur de domaine, la GPO et son identifiant, et ses détails (les options d'un lien modifiées, le fichier enregistré avec son SHA-256 et sa nouvelle version, les droits des créateurs à une approbation...).
  • Exporter en CSV... : les modifications affichées, telles qu'elles sont filtrées, l'heure en UTC (ISO 8601), les détails en JSON.

Où est le journal

Le dossier est Audit.Path de la configuration du domaine : la fenêtre lit le domaine une fois affichée (-Server, sinon le domaine de l'ordinateur). Sans domaine, ou quand la configuration ne donne pas de dossier, c'est le dossier Audit des sauvegardes du compte, que seule sa console écrit. Parcourir... et Actualiser lisent un autre dossier, ou relisent le même.

Le journal est activé par défaut : Audit.Enabled = $false dans la configuration du domaine l'arrête, les modifications étant toujours faites. Voir le journal d'audit pour ce que contient chaque ligne.

# Les mêmes modifications dans un script
Get-PSGPOAuditLog -After (Get-Date).AddDays(-7) -Action SaveGpoFile, ApproveGpo |
    Export-Csv .\audit.csv -NoTypeInformation

Le journal central, un dossier par tier

Un journal partagé par les consoles est sur un partage d'un serveur de fichiers, Audit.Path de la configuration du domaine. Pas SYSVOL : il est copié sur chaque contrôleur de domaine (DFSR), le journal grossirait sur chacun d'eux, et deux contrôleurs qui complètent le même fichier perdraient des lignes ; Samba ne copie pas du tout SYSVOL.

Le partage a un dossier par tier, et les droits des rôles :

Dossier Y écrit (son propre fichier) Le lit
Tier0 les approbateurs du workflow (Workflow.Approvers), Admins du domaine le Tier 0, les lecteurs
Tier1 les groupes de Audit.Tiers.Tier1 le Tier 1, le Tier 0, les lecteurs
Tier2 les groupes de Audit.Tiers.Tier2 le Tier 2, le Tier 0, les lecteurs
Unassigned un utilisateur du domaine sans tier (Domain Users, pas les ordinateurs) le Tier 0, les lecteurs
Archive la rétention (Audit.Purgers) le Tier 0, les lecteurs
le partage lui-même personne listé par les groupes des tiers et les lecteurs ; tout autre compte ne fait que le traverser, vers Unassigned
  • Chaque compte ajoute ses modifications à son propre fichier : <tier>\<domaine>-<aaaa-MM>-<compte>-<ordinateur>.jsonl. Deux consoles n'écrivent jamais le même fichier. Son propriétaire ne fait qu'y ajouter : il ne l'écrase pas, ne le supprime pas et ne change pas ses droits ; personne d'autre que les administrateurs ne le fait.
  • Le Tier 0 lit tous les tiers, un compte du Tier 1 ou du Tier 2 les modifications de son tier seulement, les lecteurs (Audit.Readers, les auditeurs) tout, sans écrire. La fenêtre montre ce que le compte peut lire, et le tier de chaque modification.
  • Le tier d'un compte est lu dans ses groupes à la lecture du domaine : un administrateur du domaine ou un approbateur est Tier 0, puis les groupes de Audit.Tiers.

1. Le partage

Sur un serveur de fichiers Windows, en administrateur :

Télécharger windows/New-PSGPOShare.ps1

.\New-PSGPOShare.ps1 -Name gpo-audit                     # \\<serveur>\gpo-audit, D:\Shares\gpo-audit
.\New-PSGPOShare.ps1 -Name gpo-audit -Path E:\Shares\GPO-Audit -WhatIf
windows/New-PSGPOShare.ps1
#Requires -Version 5.1
#Requires -RunAsAdministrator
<#
.SYNOPSIS
    Creates a share of HGPM on a Windows file server: the central audit log or the central backups.
.DESCRIPTION
    A folder and its SMB share. The share lets the authenticated users change files and the
    administrators do everything: the NTFS permissions decide, written by
    Initialize-PSGPOAuditShare (the audit log, a folder per tier) or Initialize-PSGPOBackupShare
    (the backups, Tier 0 only) from the roles of the configuration of the domain. Until then the
    folder is for the administrators only.

    Run it as an administrator of the file server (not a domain controller: SYSVOL is copied to
    every domain controller), once per share, then from a console of a domain administrator:

        Initialize-PSGPOAuditShare -Path \\<server>\gpo-audit
        Initialize-PSGPOBackupShare -Path \\<server>\gpo-backups

    and Audit.Path or Backup.Path in the Settings of the console. What is there is kept: the script
    can run again.
.PARAMETER Name
    Name of the share: gpo-audit, gpo-backups.
.PARAMETER Path
    The folder of the share; D:\Shares\<Name> by default.
.EXAMPLE
    .\New-PSGPOShare.ps1 -Name gpo-audit
.EXAMPLE
    .\New-PSGPOShare.ps1 -Name gpo-backups -Path E:\Shares\GPO-Backups -WhatIf
.LINK
    https://jm2k69.github.io/PSGPOTools/en/features/audit-log/
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [string]$Name = 'gpo-audit',
    [string]$Path
)

$ErrorActionPreference = 'Stop'
if (-not $Path) { $Path = "D:\Shares\$Name" }

if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
    if ($PSCmdlet.ShouldProcess($Path, 'Create the folder')) {
        $null = New-Item -ItemType Directory -Path $Path
        # The administrators and SYSTEM only, until Initialize-PSGPO...Share writes the roles.
        $acl = [System.Security.AccessControl.DirectorySecurity]::new()
        $acl.SetSecurityDescriptorSddlForm('D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)')
        Set-Acl -LiteralPath $Path -AclObject $acl
    }
}

if (Get-SmbShare -Name $Name -ErrorAction SilentlyContinue) {
    Write-Output "The share $Name exists: kept."
} elseif ($PSCmdlet.ShouldProcess("\\$env:COMPUTERNAME\$Name", 'Create the share')) {
    # Share permissions: change for the accounts of the domain, the NTFS permissions decide.
    $null = New-SmbShare -Name $Name -Path $Path -FullAccess 'BUILTIN\Administrators' -ChangeAccess 'NT AUTHORITY\Authenticated Users' `
        -FolderEnumerationMode AccessBased -Description "HGPM: $Name"
    Write-Output "Share \\$env:COMPUTERNAME\$Name created: $Path"
}

Sur un serveur Samba (un serveur membre, ou le contrôleur de domaine d'un lab), en root :

Télécharger linux/New-PSGPOShare.sh

sudo ./New-PSGPOShare.sh                             # //<serveur>/gpo-audit, /srv/gpo-audit
sudo ./New-PSGPOShare.sh gpo-audit /srv/gpo-audit
linux/New-PSGPOShare.sh
#!/usr/bin/env bash
# Creates a share of HGPM on a Samba server (an AD domain controller or a member server): the
# central audit log (gpo-audit) or the central backups of the GPOs (gpo-backups). A folder, and a
# section of smb.conf whose permissions are the NT ACLs that Initialize-PSGPOAuditShare or
# Initialize-PSGPOBackupShare write (acl_xattr), not those of the file system.
#
#   sudo ./New-PSGPOShare.sh                                  # gpo-audit, /srv/gpo-audit
#   sudo ./New-PSGPOShare.sh gpo-backups /srv/gpo-backups
#
# Then, from a console with the Kerberos ticket of a domain administrator (kinit administrator):
#
#   Initialize-PSGPOAuditShare -Path //<server>/gpo-audit -Server <domain controller>
#   Initialize-PSGPOBackupShare -Path //<server>/gpo-backups -Server <domain controller>
#
# and Audit.Path or Backup.Path in the Settings of the console (the configuration of the domain).
# What is there is kept: the script can run again.
#
# https://jm2k69.github.io/PSGPOTools/en/features/audit-log/
set -euo pipefail

share="${1:-gpo-audit}"
folder="${2:-/srv/$share}"
config="$(smbd -b | awk '/CONFIGFILE:/ { print $2 }')"
config="${config:-/etc/samba/smb.conf}"

if [ "$(id -u)" -ne 0 ]; then
    echo "Run it as root: sudo $0 $*" >&2
    exit 1
fi

# The folder: every account of the domain reaches it, the NT ACLs decide what each does there.
# The sticky bit: an account cannot delete the file of another one, even from the server.
mkdir -p "$folder"
chmod 1777 "$folder"

# The share: NT ACLs kept in extended attributes, the POSIX ACLs left aside.
if grep -q "^\[$share\]" "$config"; then
    echo "[$share] is already in $config: kept."
else
    cp "$config" "$config.$(date +%Y%m%d-%H%M%S).bak"
    cat >> "$config" <<EOF

[$share]
    path = $folder
    read only = no
    vfs objects = acl_xattr
    acl_xattr:ignore system acls = yes
    map acl inherit = yes
    store dos attributes = yes
    comment = HGPM: $share
EOF
    echo "[$share] added to $config."
fi

testparm -s "$config" > /dev/null
smbcontrol all reload-config
echo "Share //$(hostname -f)/$share ready: $folder"

2. Les rôles, dans la configuration du domaine

Dans Paramètres de la console (un administrateur du domaine), ou Configuration.psd1 :

Audit = @{
    Path = '\\fichiers.contoso.com\gpo-audit'
    Tiers = @{ Tier1 = 'GPO-Tier1-Admins'; Tier2 = 'GPO-Tier2-Admins' }
    Readers = 'GPO-Auditeurs'
}

3. Les dossiers et leurs droits

Initialize-PSGPOAuditShare crée les dossiers des tiers et écrit leurs droits à partir des rôles. À relancer chaque fois que les groupes des tiers changent : les fichiers sont gardés.

Initialize-PSGPOAuditShare -WhatIf
Initialize-PSGPOAuditShare                                   # Audit.Path de la configuration
Initialize-PSGPOAuditShare -Path //fichiers.contoso.com/gpo-audit -Server dc.contoso.com    # Linux

4. Les journaux des consoles, déplacés dedans

Les modifications que les consoles écrivaient avant, dans le dossier Audit des sauvegardes de leur compte :

Import-PSGPOAuditLog -Path "$HOME/PSGPOTools Backups/Audit" -WhatIf
Import-PSGPOAuditLog -Path "$HOME/PSGPOTools Backups/Audit" -Remove

Chaque modification va dans le fichier de son compte, de son ordinateur et de son mois, dans le dossier du tier de son compte ; une modification déjà présente n'est pas écrite deux fois, et -Remove supprime les fichiers lus.

Un journal fiable

Le journal est une preuve : un technicien ne doit ni changer ce qu'il a fait, ni écrire au nom d'un autre.

  • Ajout seul : le propriétaire d'un fichier a FILE_APPEND_DATA, pas FILE_WRITE_DATA (0x12019d). Windows ajoute à la fin du fichier quoi que demande l'écrivain ; Samba refuse de l'ouvrir pour écrire (smbclient put, del : accès refusé), mais laisse un handle ouvert en ajout écrire n'importe où. Sous Linux, la console ajoute avec les bindings Python de Samba, plus avec smbclient get et put (le propriétaire ne peut pas écrire le fichier, et chaque ligne copiait tout le fichier).
  • Des lignes chaînées : chaque ligne commence par Seq (1, 2...) et PrevHash, le SHA-256 de la ligne précédente de son fichier. Une ligne modifiée ou retirée au milieu rompt la chaîne : la ligne suivante est Altérée.
  • Vérifié à la lecture : la colonne Vérification dit Vérifiée quand la ligne suit la précédente, que son tier est le dossier de son fichier (Tier différent sinon : une ligne du Tier 0 écrite dans le dossier du Tier 1), et que son fichier appartient à son compte (AccountSid, Propriétaire différent sinon), ou au compte qui l'a importée (ImportedBy). Les lignes écrites avant 0.1.15 sont Non chaînées.
  • Rien de perdu : la modification part d'abord au journal du système, puis au partage. Un partage injoignable garde la modification dans audit-waiting.jsonl, dans le dossier du journal technique, écrite avec la modification suivante (un avertissement dit combien attendent).
  • Un fichier de son nom créé par quelqu'un d'autre (un technicien du même tier peut créer un fichier dans son dossier) : la modification part dans un fichier d'un nouveau nom à côté, et un avertissement le dit.
  • Initialize-PSGPOAuditShare passe aussi en ajout seul les fichiers déjà sur le partage (Samba ne transmet pas les droits d'un dossier à ses fichiers ; Windows si).

La chaîne montre une ligne modifiée, pas un fichier entier réécrit avec une nouvelle chaîne : la limite de Samba ci-dessus demande un outil délibéré. Le journal du système, envoyé à un SIEM, garde une copie de chaque modification hors de portée des techniciens. Le durcissement dit ce qui a été testé.

Rétention et archives

Le journal central garde Audit.RetentionMonths mois (13 par défaut ; 0 garde tout) : ce mois-ci et ceux d'avant. Invoke-PSGPOAuditRetention met les fichiers d'un mois plus ancien, de tous les tiers, dans Archive\audit-<aaaa-MM>.zip (avec leur dossier), vérifie que l'archive les contient, puis les supprime ; un mois déjà archivé reçoit les fichiers écrits depuis. Le dossier Archive a les droits du partage : le Tier 0 et les lecteurs le lisent. La rétention est écrite dans le journal lui-même (PurgeAuditLog).

Seuls les administrateurs du domaine et les purgeurs suppriment des fichiers du partage. Planifiez-la avec un purgeur, pas avec un administrateur : mettez un compte dédié dans un groupe de Audit.Purgers, puis relancez Initialize-PSGPOAuditShare. Les purgeurs lisent tous les tiers, suppriment les fichiers des anciens mois et écrivent les archives, sans aucun autre droit dans le domaine.

Un compte de service géré de groupe : Windows garde son mot de passe, la tâche n'en stocke aucun.

# Une fois, en administrateur du domaine (la clé racine KDS existe déjà quand des gMSA sont utilisés)
New-ADServiceAccount -Name hgpm-purge -DNSHostName hgpm-purge.contoso.com -PrincipalsAllowedToRetrieveManagedPassword 'ADMIN-SERVER$'
Add-ADGroupMember -Identity 'GPO-Audit-Purgers' -Members 'hgpm-purge$'   # Audit.Purgers = 'GPO-Audit-Purgers'
Install-ADServiceAccount hgpm-purge                                       # sur ADMIN-SERVER

Invoke-PSGPOAuditRetention -WhatIf
# Chaque mois, en tant que gMSA : -LogonType Password sans mot de passe, Windows le récupère
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument '-NoProfile -Command "Import-Module HGPM; Invoke-PSGPOAuditRetention"'
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\hgpm-purge$' -LogonType Password
Register-ScheduledTask -TaskName 'HGPM audit retention' -Action $action -Principal $principal -Trigger (New-ScheduledTaskTrigger -Weekly -WeeksInterval 4 -DaysOfWeek Sunday -At 3am)

Un compte dédié du domaine, dans un groupe de Audit.Purgers ; son keytab lisible par le seul compte qui lance la rétention.

# Une fois, sur un contrôleur de domaine Samba, en root
samba-tool user add hgpm-purge --random-password
samba-tool group addmembers GPO-Audit-Purgers hgpm-purge
samba-tool domain exportkeytab /root/hgpm-purge.keytab --principal=hgpm-purge
# Copié sur l'ordinateur de la rétention, pour le seul compte hgpm
sudo install -o hgpm -g hgpm -m 600 hgpm-purge.keytab /etc/hgpm/hgpm-purge.keytab

# Chaque mois
echo '0 3 1 * * hgpm kinit -k -t /etc/hgpm/hgpm-purge.keytab hgpm-purge && pwsh -NoProfile -Command "Import-Module HGPM; Invoke-PSGPOAuditRetention -Server dc.contoso.com"' | sudo tee /etc/cron.d/hgpm-audit-retention

Le journal du système, pour un SIEM

Les modifications faites avec d'autres outils : l'audit sur les contrôleurs de domaine.

Avec Log.SystemLog (activé par défaut), chaque modification du journal d'audit et chaque erreur du journal technique partent aussi dans le journal du système, où un SIEM les collecte :

Windows Linux
Où le journal d'événements Application, source HGPM syslog / journald, étiquette hgpm (logger)
Une modification Information, l'ID d'événement de son action user.notice, [<ID d'événement>]
Un avertissement, une erreur Warning 2000, Error 3000 user.warning, user.err
La posture (Test-PSGPOSecurityPosture -SystemLog) 4000 un résumé (Information ; Warning avec un constat High), Warning 4001 chaque constat High [4000], [4001]
Une fois par ordinateur Register-PSGPOEventSource, en administrateur rien

Les événements de HGPM dans l'Observateur d'événements

Le message dit quoi, par qui, d'où et sur quoi, puis l'entrée du journal d'audit en JSON :

HGPM audit: ApproveGpo by HARDENBOX\Administrator from dc on hardenbox.labo: T1 - Servers - Proxy - CN={AFE8...}
{"Format":"PSGPOTools.Audit/1","Time":"2026-10-24T15:44:02Z","Domain":"hardenbox.labo",...}
ID Action ID Action
1001 NewGpo 1023 SetBlockInheritance
1002 RenameGpo 1030 SetGpoPermission
1003 SetGpoStatus 1031 SetGpoSecurityFiltering
1004 RemoveGpo 1032 NewFilteringGroups
1005 RemoveDeletedGpoBackup 1040 SetWmiFilter
1006 RestoreGpo 1041 NewWmiFilter
1007 ImportGpo 1042 EditWmiFilter
1008 SaveGpoFile 1043 RemoveWmiFilter
1009 ImportPolicyRuleSet 1044 SetWmiFilterPermission
1010 PendingGpo 1050 NewPasswordPolicy
1011 ApproveGpo 1051 SetPasswordPolicy
1020 NewLink 1052 RemovePasswordPolicy
1021 SetLink 1060 SaveConfiguration
1022 RemoveLink 1061 InstallAdmx
1062 InitializeAuditShare 1063 InitializeBackupShare
1012 SetGpoComment 1064 PurgeAuditLog
1013 SaveStarterGpo 1014 RemoveStarterGpo
2000, 3000 un avertissement, une erreur
4000 la posture : un résumé 4001 la posture : un constat High
Register-PSGPOEventSource                                                # Windows, une fois, en administrateur
Get-WinEvent -FilterHashtable @{ LogName = 'Application'; ProviderName = 'HGPM' } -MaxEvents 20
journalctl -t hgpm --since today                                         # Linux

Le journal technique

Ce qui se passe mal là où tourne une console, pour en trouver la cause après coup : les erreurs de LDAP, de SYSVOL, des fenêtres, et les commandes du système (samba-tool, smbclient, python3, secedit, auditpol) qui échouent, avec la commande, son code de sortie et ce qu'elle a écrit. C'est le journal de l'ordinateur et de son utilisateur, pas du domaine :

Où %LOCALAPPDATA%\PSGPOTools\Logs (Windows), ~/.local/share/PSGPOTools/Logs (Linux)
Fichiers hgpm-<aaaa-MM-jj>.log, une entrée par ligne, ses détails en retrait dessous
Quoi Log.Level : Error, Warning (par défaut), Information (les commandes qui échouent), Verbose (chaque commande)
Combien de temps Log.RetentionDays jours (30), supprimés quand la console y écrit la première fois
2026-10-26T09:14:03.512Z ERROR       LDAP         LDAP connection to dc.hardenbox.labo:389 failed: ...
    Exception: System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
2026-10-26T09:15:40.077Z INFORMATION smbclient    Exit code 1.
    Command: smbclient //dc.hardenbox.labo/gpo-audit --use-kerberos=required -c 'ls'
    Error: NT_STATUS_ACCESS_DENIED listing \*

Pour trouver un problème : Log.Level = 'Verbose' dans Paramètres, refaire le problème, puis le fichier du jour.