Powershell : expiration des secrets et certificats des applications Azure ActiveDirectory

Ce script permet d’afficher la liste des App Registrations Azure ActiveDirectory ayant un ou des secrets ou certificats expirant à 90 jours, 30 jours ou déjà expirés.

Il est bien évidemment possible d’adapter le script pour le faire tourner dans un runbook Azure ou sur un serveur de manière non-interactive en adaptant la partie authentification.

Connect-AzureAD
$AADAppsColl = Get-AzureADApplication -All:$true
foreach($AADApps in $AADAppsColl) {
    $AppID = $AADApps.AppID
    $AADApp = Get-AzureADApplication -Filter "AppID eq '$AppID'"
    $PassCreds = $AADApp.PasswordCredentials
    if ($null -ne $PassCreds) {
        foreach($PassCred in $PassCreds) {
            if($PassCred.EndDate -gt (Get-Date).AddDays(30) -and $PassCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($PassCred.EndDate -gt (Get-Date) -and $PassCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($PassCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expired: $($PassCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
    $KeyCreds = $AADApp.KeyCredentials
    if ($null -ne $KeyCreds) {
        foreach($KeyCred in $KeyCreds) {
            if($KeyCred.EndDate -gt (Get-Date).AddDays(30) -and $KeyCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($KeyCred.EndDate -gt (Get-Date) -and $KeyCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($KeyCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expired: $($KeyCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
}

Une version commentée du script est disponible en téléchargement.

Perte de connectivité LDAP après upgrade vCenter en 6.7 u3f

Cette semaine, j’ai été confronté à un incident plutôt particulier suite à un upgrade d’une appliance vCenter (6.5 vers 6.7 u3f – build 15976714). En effet, après l’upgrade, j’étais incapable de me reconnecter en utilisant mon login ActiveDirectory, pas plus qu’en utilisant un autre compte lié à ce même ActiveDirectory. Cependant, la connexion avec l’utilisateur local fonctionne bien.

Les vérifications de base ne donnent rien de particulier :

  • L’appliance existe bien dans l’ActiveDirectory en tant qu’objet Computer (je n’utilise pas de compte pour faire la connexion)
  • Les entrées DNS directes et reverse sont bien correctes

La sortie et réintégration à l’ActiveDirectory de l’appliance n’ont rien donné non plus, cependant il y a du mieux : tous les comptes renseignés en dur dans les autorisations globales peuvent ouvrir une session sur le vCenter. Cependant les comptes qui sont membres des groupes étant dans les autorisations ne fonctionnent toujours pas. De plus, l’appel aux webservices SOAP est toujours impossible par un compte sur l’ActiveDirectory (Veeam utilise ces webservices pour interagir avec le vCenter afin de sauvegarder les VM).

Étant donné le caractère plutôt urgent de la résolution de cet incident puisqu’il impacte les sauvegardes, nous avons contacté le support VMware qui nous a confirmé que ce dysfonctionnement pouvait arriver de manière aléatoire lors d’un upgrade de VCSA vers ce dernier build (6.7 update 3f) et que ce bug sera corrigé lors d’une prochaine release.

Malgré tout, le support ne nous a pas permis de trouver la cause du dysfonctionnement ; en faisant jouer nos réseaux, nous avons été mis sur la piste du fichier /etc/resolve.conf, que j’aurais dû vérifier en me connectant en SSH sur l’appliance (si je peux avoir une excuse, il était plus de 22h quand je me suis penché sur le sujet en urgence !).

Pour ce faire, ouvrir une session SSH sur le VCSA, puis saisir shell dans le prompt afin d’accéder au prompt standard de PhotonOS qui est le système d’exploitation socle de vCenter (basé sur GNU/Linux), puis saisir la commande suivante :

more /etc/resolve.conf

Cette instruction doit imprimer le contenu du fichier qui doit à minima contenir l’IP loopback et les DNS renseignés, ainsi que les zones DNS dans lesquelles chercher les enregistrements. Et c’est précisément ici que ça coinçait puisque notre zone ActiveDirectory n’était pas renseignée dans le paramètre search, alors qu’elle l’était sur l’ancienne appliance.

Une fois la zone ActiveDirectory renseignée et un redémarrage complet de l’appliance plus tard, la connexion avec des comptes LDAP était fonctionnelle et les sauvegardes opérationnelles.

Introduction à Windows Server Core

Proposée déjà depuis quelques éditions, la version Core de Windows Server permet de s’affranchir de l’interface graphique habituellement livrée avec le système d’exploitation. Si certaines API graphiques sont disponibles et permettent l’exécution d’applications graphiques comme le bloc-notes, la philosophie de cette version est orientée ligne de commandes et plus précisément Powershell.

Dans ce dernier article de l’année, je vais présenter cette version de Windows en commençant par l’installation, la configuration et quelques exemples d’utilisation du système.

A noter que la version utilisée pour cet article est Windows Server 2019 ; si sur certaines versions précédentes de Windows il était possible d’installer ou désinstaller tout l’environnement graphique une fois l’OS déployé, cela n’est pas possible sur 2019. Il est donc important bien réfléchir au type d’environnement nécessaire avant de procéder à l’installation.

La phase d’installation ne présente aucune différence ; nous choisissons donc la version Windows Server 2019 et non celle avec le Desktop Experience. Une fois l’installation terminée, une fenêtre cmd s’ouvre avec un premier prompt de login.

Mot de passe configuré et utilisateur connecté, nous sommes face à un prompt cmd tout simple. Un outil permet d’effectuer toute la configuration de base du système : cet outil s’appelle sconfig et peut être appelé directement dans le prompt en tapant sconfig.

Nous allons donc renommer la machine, puis lui affecter une adresse IP ; tout ceci se fait sans la moindre ligne de commande. D’autres paramètres sont accessibles, mais je ne vais pas les aborder ici car l’outil est plutôt simple d’usage.

Le serveur a donc son nom et une adresse IP. La prochaine étape va être le déploiement des rôles ADDS et la création d’une nouvelle forêt. Dans un premier temps, il va falloir installer les rôles ADDS, puis ensuite procéder à la création de la forêt. Ceci se réalisant en Powershell, nous allons quitter sconfig pour revenir à notre prompt cmd, puis appeler le moteur Powershell en saisissant simplement powershell.

Cette instruction va installer les rôles :

Install-WindowsFeature -name AD-Domain-Services

Celle-ci va créer la forêt. Les paramètres DomainMode et ForestMode permettent de définir le niveau de fonctionnalité ; ici, WinThreshold représente le niveau maximal (2016). Pour plus d’informations concernant le commandlet Install-ADDSForest, je vous renvoie vers la documentation en ligne.

Install-ADDSForest -DomainName dundermifflin.inc -DomainMode WinThreshold -ForestMode WinThreshold -Database "C:\Windows\NTDS" -SysvolPath "C:\Windows\Sysvol" -Logpath "C:\Windows\Logs"

En validant, il ne reste plus qu’à confirmer que l’on souhaite bien procéder à la création d’une forêt avec un domaine pour que celle–ci commence, puis le serveur redémarre.

Une fois le serveur de nouveau accessible, nous allons nous connecter avec notre nouveau compte Administrator. De nouveau sous Powershell, nous allons voir les rôles et fonctionnalités installés sur le système :

Get-WindowsFeature

Le retour de la commande est vraiment bien fait car il présente hiérarchiquement les rôles et fonctionnalités comme sur la version à interface graphique. Dans le cadre de cet article, nous allons déployer le rôle DFS Namespace car nous souhaitons partager des répertoires et les présenter avec un nom de domaine plus parlant que le nom de la machine. Pour installer une nouvelle fonctionnalité, il suffit de reprendre le nom de la deuxième colonne en paramètre de la commande, comme ceci :

Install-WindowsFeature FS-DFS-Namespace

En validant par entrée, le système va déployer la fonctionnalité. Pour celle-ci, il n’y a pas besoin de redémarrer.

Procédons à la création d’un répertoire qui va être partagé pour être utilisé avec DFS. Ce répertoire nommé docs sera dans C:\dfs.

cd \
mkdir dfs
cd dfs
mkdir docs

Afin d’être déclaré en tant que répertoire DFS, ce répertoire doit être partagé au niveau serveur :

New-SmbShare -Name "docs" -Path "C:\dfs\docs" -FullAccess "DUNDERMIFFLIN\Administrator"

Une fois le répertoire partagé, il peut être ajouté en tant que racine DFS. Ici, je vais procéder à la création d’une racine DFS au niveau domaine. Pour plus d’informations, consultez la documentation en ligne du commandlet New-DfsnRoot.

New-DfsnRoot -TargetPath "\\w2019core01\docs" -Type DomainV2 -Path "\\dundermifflin.inc\docs" -EnableAccessBasedEnumeration:$true

Désormais, je peux parcourir les dossiers partagées grâce à l’URL \\dundermifflin.inc\docs. Procédons à la création d’un répertoire partagé accessible via DFS, nommé dsi.

New-DfsnFolder -Path "\\dundermifflin.inc\docs\dsi" -TargetPath "\\w2019core01\dsi"

Nous avons donc notre répertoire DFS et sa racine, mais pas d’utilisateurs ; notre AD fraîchement installé est toujours vide. Grâce aux modules ActiveDirectory pour Powershell installés, nous allons administrer notre annuaire local en Powershell également. Nous allons créer quelques OU pour ranger nos objets.

New-ADOrganizationalUnit "DUNDERMIFFLIN.INC"
New-ADOrganizationalUnit "Users" -Path "OU=DUNDERMIFFLIN.INC,DC=dundermifflin,DC=inc"
New-ADOrganizationalUnit "Groups" -Path "OU=DUNDERMIFFLIN.INC,DC=dundermifflin,DC=inc"

Ainsi, dans notre AD existera une OU DUNDERMIFFLIN.INC, comprenant 2 autres OU nommées Users et Groups. Procédons à la création d’un utilisateur et d’un groupe :

New-ADGroup -SamAccountName "admins" -Name "Admins" -Path "OU=Groups,OU=DUNDERMIFFLIN.INC,DC=dundermifflin,DC=inc" -GroupScope "Domain"
New-ADUser -SamAccountName "michael.scott" -Name "Michael Scott" -Path "OU=Users,OU=DUNDERMIFFLIN.INC,DC=dundermifflin,DC=inc" -AccountPassword (ConvertTo-SecureString -AsPlainText "Password01" -Force)
Enable-ADAccount "michael.scott"

Ajoutons ce compte au groupe des admins du domaine :

Add-ADGroupMember -Identity "Domain Admins" -Members michael.scott

Puis connectons-nous avec. Tentons d’accéder au répertoire DFS créé plus haut avec le compte de Michael Scott, et nous aurons bien une erreur de droits insuffisants, car nous avons seulement autorisé Administrator à accéder à ce répertoire.

Get-ChildItem \\dundermifflin.inc\docs

Nous allons donc autoriser le compte à accéder à ce partage, mais seulement en lecture.

Grant-SmbShareAccess -Name "docs" -AccountName "michael.scott" -AccessRight Read

En validant, le retour du commandlet va afficher les droits positionnés sur le partage. Si on reexécute la commande plus haut, cela doit désormais fonctionner.

Voici qui conclut pour cette introduction à Windows Server Core. L’administration d’une telle machine n’a rien de spécialement compliqué pour peu que l’on soit habitué à travailler avec Powershell ; de plus la documentation en ligne de Microsoft est une véritable mine d’or permettant d’obtenir les syntaxes des commandlets. Plus une question d’habitude que de véritable connaissance technique, l’administration d’un serveur Core peut également se faire à distance depuis un serveur à interface graphique si ce dernier possède les RSAT (Remote Server Administration Tools) installés. En dehors de certains serveurs applicatifs qui nécessitent une interface graphique, il est tout à fait possible d’avoir une ferme de serveurs d’infrastructure sous Windows tous dépourvus d’interface graphique. Représentant un gain non négligeable de ressources, la démocratisation du mode Core dans une infrastructure pourra également pousser les équipes d’administration à scripter de plus en plus leur travail et tâches récurrentes, à centrer sur un serveur d’administration tous les outils et consoles à distance, ce qui va de pair avec une homogénéisation des processus et un travail plus efficace.

Powershell : alimentation d’un groupe AD à partir des ACL

J’ai développé ce script Powershell afin de séparer les utilisateurs de plusieurs serveurs de fichiers dans des groupes ActiveDirectory liées à une liste de distribution Exchange. Ainsi, en cas de downtime prévu d’un serveur, il suffit d’envoyer une communication au groupe AD.

Ce script récupère les groupes AD placés dans les ACL des répertoires partagés, vide le groupe en question puis le repeuple à partir des utilisateurs qu’il a récupéré dans les groupes. Un filtre est fait sur l’OU contenant les utilisateurs afin de ne pas inclure d’éventuels comptes de service. Naturellement, on adaptera les filtres (ici, on recherche des groupes, mais on peut très bien lister directement les utilisateurs) et la troncature de la chaîne en fonction de ses besoins.

function update {
	param([string] $srv)
	Get-ADGroupMember "_users-$srv" | foreach { Remove-AdGroupMember "_users-$srv" -Member $_ -Confirm:$false }
	$folders = get-childitem "\\$srv.dundermifflin.inc\d$\shares" | get-acl
	foreach($folder in $folders){
		$domacl = $folder.Access | where-object { $_.IdentityReference -like "DUNDERMIFFLIN\*" }
		foreach($acl in $domacl){
		$grpm = Get-ADGroupMember $acl.IdentityReference.Value.SubString(14,($acl.IdentityReference.Value.Length-14)) -Recursive
			foreach($mbr in $grpm) {
				$adu = Get-ADuser $mbr -Properties CanonicalName
				if($adu.CanonicalName -like "dundermifflin.inc/USERS/*") { Add-AdGroupMember -Identity "_users-$srv" -Members $mbr }
			}			
		}
	}
}

update("filesrv01")
update("filesrv02")
update("filesrv03")

Le script l’exécute donc pour 3 serveurs de fichiers différents afin de peupler trois groupes différents. Si des utilisateurs sont placés directement dans les ACL, alors la commande Get-ADGroupMember renverra une erreur mais celle-ci n’est pas fatale. Afin d’être toujours à jour, on pourra appeler ce script via une tâche planifiée toutes les semaines.

Le script est disponible en téléchargement dans une version commentée.