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
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, pasFILE_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 avecsmbclient getetput(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...) etPrevHash, 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-PSGPOAuditSharepasse 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 |
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
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.



