AD – Exploiter des pseudo-groupes dynamiques par OS

I. Présentation

L’article que je vous propose aujourd’hui constitue un petit défi, car il consiste à inventer un mécanisme de gestion dynamique des groupes de sécurité dans l’Active Directory. En fait, contrairement aux groupes de distribution, essentiellement exploités par Exchange, cette notion de gestion automatique des membres d’un groupe n’est pas proposée nativement par Microsoft sur les groupes de sécurité. Certains retoqueront qu’il en existe, dans Azure et même dans un AD standard, « Ordinateurs du domaine », « Controleurs de domaine » …, mais avouez qu’ils sont bien peu nombreux et de toute façon les critères ne sont pas modifiables.

Donc, si vous n’avez pas les moyens d’acquérir des produits spécialisés, tels que Softerra Adaxes ou FirstWare DynamicGroup, la suite peut vous intéresser …

 

II. Le challenge – Cibler des GPO par OS sans filtre WMI

Mon scénario est très ciblé : Le but recherché consiste à appliquer des stratégies de groupe propres aux systèmes d’exploitation des postes sans recourir à des filtres WMI pour le ciblage.

Pour cette recette, je vais donc utiliser les 3 ingrédients suivants :

  • Le journal d’événements « Sécurité » : Sous réserve que la politique d’audit par défaut  sur les contrôleurs de domaine, est été conservée, ce journal doit contenir des événements spécifiques, tels que « ID 4741 » lors de la création d’un compte d’ordinateur. (Typiquement lors de la jonction au domaine) et le cas échéant, « ID 4743 » lors de la suppression.
  • Le planificateur de tache : Depuis Vista, cette solution permet d’associer une action, telle que l’exécution d’un script ou d’un programme, lorsqu’un événement est créé dans un journal donné. Cela constitue donc un déclencheur idéal pour le besoin que je viens d’exprimer.
  • Le script de gestion des groupes : J’ai choisi Powershell pour des raisons de simplicité (un vbs aurait été plus complexe.)

 

III. La mise en œuvre par étapes

Je réalise cet atelier avec un environnement de domaine 2012R2, mais cela devrait fonctionner sans difficultés particulières sur d’autres versions.

On débute cet atelier par une ouverture de session sur le contrôleur de domaine avec le compte « Administrateur » (Une fois n’est pas coutume, et cette proposition couvre vos besoins, vous pourrez toujours revenir sur un ajustement des privilèges le cas échéant).

A. Le script

Pour ne pas trop charger le code, je me suis focalisé sur les systèmes d’exploitation clients. Le premier script aura la charge d’interroger l’objet correspondant à l’ordinateur nouvellement Active Directory afin de récupérer les propriétés « operatingSystem* ». Mais, afin d’éviter une requête trop couteuse, j’effectue une lecture de l’événement 4741 dans le journal Sécurité afin d’en extraire le nom de l’ordinateur en question.

# Si Powershell v2, il faut charger le module
if ($PSVersionTable.PSVersion.Major -eq 2) { Import-Module ActiveDirectory }

# Récupère le nom de l'ordinateur (dans les détails du message)
$ComputerName = (Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4741} -MaxEvents 1).Properties.value[8]

# Ajout d'une temporisation - sinon il y a un risque que les propriétés ne soient pas encore peuplées.
sleep -Seconds 5

# Interroge l'AD pour connaitre l'OS du poste
#Get-ADComputer -Identity $Computer -Properties * | Format-Table Name,operatingSystem,operatingSystemVersion
$ADComputer = Get-ADComputer -Identity $ComputerName -Properties *

# Crée un groupe de sécurité par OS
 switch (($ADComputer.operatingSystemVersion).Substring(0,3)) {
 "6.0" { $GrpName = 'GS_Postes_Windows_Vista';break}
 "6.1" { $GrpName = 'GS_Postes_Windows_7';break }
 "6.2" { $GrpName = 'GS_Postes_Windows_8';break }
 "6.3" { $GrpName = 'GS_Postes_Windows_8.1';break }
 "10." { $GrpName = 'GS_Postes_Windows_10';break }
 }
# ou affecte un groupe OS_inconnu par défaut
if ($GrpName -eq $null) { $GrpName = 'GS_Postes_OS_Inconnu' }

# Crée une entrée dans le journal
New-EventLog -LogName Application -Source Scripting -EA 0
$Message = "Création d'ordinateur détectée - Exécution du script $($MyInvocation.MyCommand.Name) `n`r" +`
 "Le groupe OS du poste [$($ComputerName)] est [$GrpName]"
Write-EventLog -LogName Application -Source Scripting -EntryType Information -EventId 100 -Category 0 -Message $Message

# Crée le groupe OS du poste
New-ADGroup -GroupScope Global -Name $GrpName -EA 0 | Out-Null

# Ajoute le poste dans le groupe correspondant a son OS
$ADGroup = Get-ADGroup $GrpName
Add-ADGroupMember $ADGroup -Member $ADComputer

 

On enregistre ce code dans un dossier du contrôleur de domaine (PDC de préférence), comme par exemple « C:\Scripts\Add-ComputerTo-OSGroup.ps1 »

Pour aller à l’essentiel, je me suis focalisé sur un simple exemple perfectible d’ajout d’ordinateur et une journalisation succincte. De plus, ce script évalue les versions des systèmes sans faire la distinction entre les postes ou les serveurs.

Note : Lorsque vous sortez un ordinateur du domaine, l’objet correspondant est désactivé. Dans notre cas, cela n’a pas d’incidence fonctionnelle et il suffit de supprimer ces comptes désactivés pour qu’ils soient supprimés des groupes auxquels ils appartiennent.

Au besoin, je reviendrais ultérieurement sur un éventuel script plus élaboré de régénération des groupes afin de prendre en charge les postes existants déjà joints au domaine et les éventuels loupés.

 

B. La tache planifiée

Ouvrez le planificateur de taches (taskschd.msc) puis commencez par « Créez une tache » à la racine de la Bibliothèque via le menu contextuel.

  • Sous l’onglet « Général« 

Affectez-lui un nom tel que « Add Computer in AD Group by OS« , et une description si vous le souhaitez.

Déclaration de la nouvelle tache planifiée

N’oubliez pas de modifier l’option « Exécuter même si l’utilisateur n’est pas connecté » ainsi que de cocher la case « Ne pas enregistrer le mot de passe. La tâche n’accède qu’aux ressources locales« . Cochez également la case « Exécuter avec les autorisations maximales« . Je vous laisse le choix de la liste déroulante « Configurer pour: »

 

  • Sous l’onglet « Déclencheurs« 

Cliquez sur « Nouveau« , puis choisissez l’option « Sur un événement » dans la liste déroulante « Lancer la tâche« . Sélectionnez le journal « Sécurité » dans la liste déroulante, laissez le champ « Source » vide et entrez la valeur « 4741 » dans le champ « ID de l’événement »

Déclaration du nouveau déclencheur

Cliquez sur « OK » afin de valider ce nouveau déclencheur.

  • Sous l’onglet « Actions« 

Cliquez sur « Nouveau« , et conservez le choix « Démarrer un programme« dans la liste déroulante. Pour l’exécution d’un script programmé, il est préférable d’indiquer l’interpréteur suivi de ses propres paramètres, plutôt que d’indiquer directement le nom du script.

Dans le champ « Programme/script« , entrez le texte « C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe » puis dans le champ « Ajouter des arguments (facultatif) », entrez les arguments suivants : « -executionPolicy bypass -file C:\Scripts\Add-ComputerTo-OSGroup.ps1« .

Déclaration de la nouvelle action

Cliquez sur « OK« afin de valider cette nouvelle action.

  • Sous l’onglet « Conditions« 

Cet onglet ne contient pas d’informations pertinentes (pour notre contrôleur de domaine), mais vous pouvez décocher toutes les options proposées.

  • Sous l’onglet « Paramètres« 

Cet onglet permet d’ajuster le comportement de la tâche planifiée. Veillez à conserver l’option « Autoriser l’exécution de la tâche à la demande »

Ajustement des paramètres de la tache

Je laisse les autres réglages à votre discrétion. Cliquez enfin sur « OK » afin de créer cette nouvelle tache.

Ces étapes sont plus délicates qu’il n’y parait, car les programmes ou scripts planifiés n’interagissent pas avec l’interface utilisateur. Si cet atelier ne fonctionne pas, vous devrez procéder par étape, en vérifiant que le script ne contient pas d’erreur, que la tache s’exécute correctement à la demande, et que l’éventement est bien intercepté (visible dans l’onglet « Historique » de la tache.)

Vue d’ensemble de la tache fraichement créée

C. Le test fonctionnel de base

Voila maintenant que le plus dur est fait 🙂 passons à un test basique en simulant la création d’un nouvel ordinateur. Pour faire court, lancez une invite de commande puis entrez les commandes suivantes :

net computer \\BidonDemo01 /add
net computer \\BidonDemo02 /add
net computer \\BidonDemo03 /add

 

Patientez au moins un dizaine de secondes entre chaque commande. Du fait que le script ne gère qu’un événement à la fois, cette technique s’accommode mal d’une création d’ordinateur en rafale.

Si vous n’avez pas fermé la console du planificateur de tache, vous devriez voir que la tache à bien été traitée sous son onglet « Historique »

Pour vérifier le résultat, lancez la console « Utilisateurs et ordinateurs Active Directory » (DSA.msc), puis vérifiez la présence des ordinateurs précédemment créés « BidonDemo* » sous le conteneur « Computers » par défaut.

Du fait que ces objets « factices » ne disposent pas de propriété « operatingSystem » valide, notre script a donc, si tout va bien 🙂 créé un groupe de sécurité « GS_Postes_OS_Inconnu » sous le conteneur « Users » par défaut et ajouté ces ordinateurs en tant que membres.

Exemple de résultat avec des ordinateurs factices.

Si vous disposez de quelques postes (éventuellement virtuels) à joindre au domaine, vous pouvez effectuer le test en grandeur nature.

Exemple de résultat avec 2 VM Windows 7 et Windows 10 🙂

Et un extrait de la journalisation obtenue (cf eventvwr)

Journalisation basique d’exécution du script

D. Ciblage des stratégies de groupe GPO

Si les tes tests sont concluants, il ne nous reste plus qu’a affecter les groupes des différents systèmes d’exploitation dans le filtrage de sécurité de chaque GPO dont on souhaite un ciblage.

Exemple de filtrage GPO

Pour cet exemple, la configuration Utilisateur est désactivée (donc ignorée durant le traitement). Pensez à ajouter un groupe d’utilisateur spécifique ou « Tout le monde » si vous souhaitez positionner des paramètres de niveau Utilisateur (et réactiver l’ensemble du GPO).

Pour rappel, l’ayant-droit « Utilisateurs authentifiés » positionné par défaut dans le filtrage de sécurité, concerne tous les utilisateurs mais aussi tous les ordinateurs du domaine…

Je laisse à votre discrétion, les éventuels déplacements des comptes d’ordinateurs dans leurs unités d’organisation (OU) respectives.

 

E. Bonus

Notez que pour mes maquettes de démonstration, j’ai souvent recours à une astuce que je vous livre par la même occasion.

Par défaut, (hors scripts ou automates) les ordinateurs joints au domaine sont stockés dans le conteneur « Computers » et les utilisateurs sont créés dans le conteneur « Users« . Cette affirmation ne se vérifie que pour des commandes antérieures à Windows 2000, telles que « net user …, /add ». Les autres outils, DS*.exe , Module Powershell  et GUI permettent de stipuler le conteneur de destination

Ce comportement est configuré dans le schéma Active Directory, mais depuis Windows 2003, il est possible de changer facilement cette affectation. Pour cela, assurez-vous de créer préalablement les OU nécessaires, tels que « Postes » et « Utilisateurs », par exemple, à la racine de votre domaine. Puis exécutez les commandes suivantes avec le compte d’administrateur (de l’entreprise). A adapter selon votre nom de domaine bien sûr 🙂

Redircmp OU=Postes,DC=labs,DC=local

Redirusr OU=Utilisateurs,DC=labs,DC=local

Pour revenir aux valeurs par défaut, exécutez les commandes suivantes :

Redircmp CN=Computers,DC=labs,DC=local

Redirusr CN=Users,DC=labs,DC=local

Ces commandes n’ont pas d’incidence sur les objets déjà existants dans le domaine Active Directory et il faut être administrateur du schéma ou de l’entreprise pour qu’elles soient prises en compte.

 

IV. Le script d’ajustement

Comme convenu, afin de prendre en considération les postes déjà joints au domaine, et les ranger dans leur groupe d’OS respectifs, je vous propose le petit script suivant :

 

# Si Powershell v2, il faut charger le module
if ($PSVersionTable.PSVersion.Major -eq 2) { Import-Module ActiveDirectory }

# Crée une entrée dans le journal pour initialiser le nom de la source
New-EventLog -LogName Application -Source Scripting -EA 0

# Interroge l'AD pour énumerer les ordinateurs et leur OS (on exclut les serveurs)
$Scope = (Get-ADRootDSE).rootDomainNamingContext
$ADComputers = Get-ADComputer -Filter * -Properties Name,operatingSystem,operatingSystemVersion -SearchBase $Scope | 
 where { $_.operatingSystem -notmatch 'server' }
# $ADComputers | fl Name,operatingSystem,operatingSystemVersion #Pour verifier le résultat

write "Nombre d'ordinateurs trouvés : $($ADComputers.Count)"

foreach ($ADComputer in $ADComputers) {
 write "Nom du poste : $($ADComputer.Name)"
 if ($ADComputer.operatingSystemVersion -eq $null) {
 $GrpName = 'GS_Postes_OS_Inconnu'
 }else { 
 switch (($ADComputer.operatingSystemVersion).Substring(0,3)) {
 "6.0" { $GrpName = 'GS_Postes_Windows_Vista';break}
 "6.1" { $GrpName = 'GS_Postes_Windows_7';break }
 "6.2" { $GrpName = 'GS_Postes_Windows_8';break }
 "6.3" { $GrpName = 'GS_Postes_Windows_8.1';break }
 "10." { $GrpName = 'GS_Postes_Windows_10';break }
 }
 }
 write "--> Affectation au groupe : $GrpName"

# Crée le groupe OS du poste
 try { Get-ADGroup -Identity $GrpName }
 catch {
 New-ADGroup -GroupScope Global -Name $GrpName 
 write "Création du groupe $GrpName"
 # Journalisation de l'action
 $Message = "Exécution du script $($MyInvocation.MyCommand.Name) `n`r" +`
 "Création du groupe $GrpName"
 Write-EventLog -LogName Application -Source Scripting -EntryType Information -EventId 102 -Category 0 -Message $Message
 }
 # Ajoute le poste dans le groupe correspondant a son OS
 $ADGroup = Get-ADGroup $GrpName
 Add-ADGroupMember $ADGroup -Member $ADComputer -EA 0 | Out-Null

# Journalisation de l'action
 $Message = "Exécution du script $($MyInvocation.MyCommand.Name) `n`r" +`
 "Nom du poste : [$($ADComputer.Name)]`n" +`
 "Système d'exploitation : [$($ADComputer.operatingSystem)]`n" +`
 "Version associée : [$($ADComputer.operatingSystemVersion)]`n" +`
 "Ajouté au groupe OS : [$GrpName]"
 Write-EventLog -LogName Application -Source Scripting -EntryType Information -EventId 101 -Category 0 -Message $Message

} # end foreach

 

Ce script est loin d’être parfait mais peut suffire à couvrir un besoin de base. J’ajouterais que si vous n’avez pas une contrainte de réactivité importante, et pour couvrir un parc de quelques centaines d’ordinateurs, vous pourriez vous contenter d’exécuter ce script ponctuellement ou de manière planifiée durant des horaires bien choisis.

Mais avouez que ce serait dommage (pour moi 🙂 )  d’avoir rédigé tout cela pour un simple script planifié ….

N’hésitez-pas à commenter cet article afin de l’améliorer ou me soumettre vos remarques.

Bien à vous

Christophe