Robocopy KO sur partage Azure Files

Azure Files est une solution proposée par Microsoft via Azure permettant de créer des partages SMB liés à des Storage Account. Ainsi, il est possible de pousser ses répertoires partagés sans avoir de serveur de fichiers en tant que tel.

Je travaille actuellement sur un sujet portant sur le déploiement de plusieurs partages portés par Azure Files. Une fois la solution mise en place, ma tâche aura été de faire un robocopy des données locales vers le partage SMB porté par Azure Files. Cependant, en montant le partage sur le serveur de transfert avec un compte ayant les bons privilèges RBAC et NTFS, Robocopy se heurtait à un manque de permissions, y compris en mode backup :

2021/08/10 15:35:59 ERROR 5 (0x00000005) Copying NTFS Security to Destination Directory \\dundermifflin.inc\shares\iso\
Access is denied.

Voici plus ou moins à quoi ressemblait mon instruction Robocopy exécutée par Powershell.

Start-Process "robocopy.exe" -ArgumentList "$src $dst /S /E /COPY:DATSOU /PURGE /MIR /MT:4 /R:1 /W:1" 

Ce qui cause l’erreur de permissions est l’argument S de /copy, ce qui demande à Robocopy de conserver les permissions NTFS des fichiers lors du transfert.

Il faut savoir que les permissions sont habituellement découpées en 2 couches au niveau d’un partage : les privilèges SMB et NTFS. Les permissions sont indépendantes ; si sur un serveur managé il est possible d’intervenir sur les permissions SMB, ce n’est pas le cas avec Azure Files car l’accès est donné au service portant les partages, pas au serveur en lui-même. Par conséquent, les permissions sont redescendues via RBAC. Il existe des rôles Storage File Data SMB qui permettent de placer des droits en lecture et/ou en écriture sur les partages portés par Azure Files et qui positionnent ensuite au niveau SMB les permissions. Dans ce cas, mon compte ayant le rôle Storage File Data SMB Share Elevated Contributor était celui utilisé.

En fouinant un peu sur le net, je suis tombé sur cette question posée sur la section Q&A du site de Microsoft. Un utilisateur a été très récemment confronté au même problème. Cette erreur de permissions est due au fait que Robocopy a besoin de devenir propriétaire du fichier qu’il vient d’écrire à destination pour lui attribuer les permissions NTFS du fichier d’origine. Or, sur Azure Files, même avec un compte ayant les privilèges RBAC et NTFS au maximum, ce n’est pas possible. Robocopy va donc se heurter à un blocage et va renvoyer le message d’erreur plus haut.

En conséquence, pour contourner le problème qui est reconnu — et pour lequel Microsoft travaille sur une solution qui consisterait à donner à un compte un privilège RBAC permettant d’autoriser de devenir propriétaire d’un fichier — Microsoft conseille de monter le partage distant Azure Files avec un compte local d’administration qui lui, possède tous les droits.

Le partage doit donc être monté en tant que AZURE\NomDuStorageAccount ; le mot de passe est l’Access Key qui est générée et déclarée au niveau du Storage Account.

Au niveau du Storage Account, dans la section Security + networking du blade, il suffit de se rendre dans Access keys pour arriver sur cette page. La première clef est le mot de passe du compte local à utiliser. Ainsi, si le compte de stockage s’appelle storagedundermifflin, le partage doit être monté avec l’utilisateur AZURE\storagedundermifflin.

Ce compte possède tous les droits sur le partage et court-circuite les privilèges RBAC. En utilisant ce compte, Robocopy est donc capable de devenir propriétaire des fichiers copiés et d’y appliquer les permissions NTFS d’origine. En exécutant de nouveau le Robocopy, tout s’est bien déroulé et les permissions NTFS ont pu être conservées entre la source et la destination.

Powershell : automatisation de la création d’un répertoire partagé DFS

J’ai développé un script Powershell permettant d’automatiser tout le process de création d’un répertoire partagé et son ajout à un namespace DFS.

Ce script, prenant en paramètre le nom du partage, va réaliser les opérations suivantes :

  • Création d’un répertoire sur le disque et création d’un partage SMB sur ce même répertoire ;
  • Création et peuplement de groupes AD qui permettront de définir les droits en lecture et écriture ;
  • Ajout des ACL sur le partage ;
  • Ajout du répertoire dans le namespace DFS.

Il sera nécessaire, avant exécution du script, d’éventuellement modifier les variables déclarées en début de code afin de les adapter à l’environnement d’exécution ou au process habituellement suivi.

param([Parameter(Mandatory=$true)][string]$ShareName)
 $path = "D:\DFS"
 $dfspath = "\\dundermifflin.com\DFS"
 $srvname = "PWFILE01"
 $ou = "OU=DFS,OU=Access_Groups,DC=dundermifflin,DC=com"
 $readarray = @()
 $writearray = @()
 $readgroup = "DFS-$ShareName-RX"
 $writegroup = "DFS-$ShareName-W"
 New-Item -path $path -name $ShareName -ItemType "Directory" -ErrorAction Stop | Out-Null 
 New-ADGroup -Name $readgroup -GroupCategory Security -GroupScope DomainLocal -Path $ou -Description "\\dundermifflin.com\DFS\$ShareName - Read Only" -ErrorAction Stop
 New-ADGroup -Name $writegroup -GroupCategory Security -GroupScope DomainLocal -Path $ou -Description "\\dundermifflin.com\DFS\$ShareName - Write" -ErrorAction Stop
 New-SmbShare -Name $ShareName -Path "$path\$ShareName" -FullAccess "BUILTIN\Administrators" -ReadAccess $readgroup -ChangeAccess $writegroup -ErrorAction Stop
 Write-Host "You will be prompted the usernames to add to the read-only access group. Please input only one user at a time. When you are done, leave the field empty and press enter."
 do {
     $readacc = Read-Host "Account name to add to the READ group"
     if($readacc -ne "") {$readarray+=$readacc}
 }
 while($readacc -ne "")
 Write-Host "Now you will be prompted the usernames to add to the modification group. Please input only one user at a time. When you are done, leave the field empty and press enter."
 do {
     $writeacc = Read-Host "Account name to add to the WRITE group"
     if($writeacc -ne "") {$writearray+=$writeacc}
 }
 while($writeacc -ne "")
 if($readarray -ne $null) { Add-ADGroupMember -Identity $readgroup -Members $readarray }
 if($writearray -ne $null) { Add-ADGroupMember -Identity $writegroup -Members $writearray }
 $acl = Get-Acl "$path\$ShareName"
 $acl.SetAccessRuleProtection($true,$false)
 $readacl = New-Object Security.AccessControl.FileSystemAccessRule("DUNDERMIFFLIN\$readgroup","ReadAndExecute, Synchronize",3,0,"Allow")
 $acl.SetAccessRule($readacl)
 $writeacl = New-Object Security.AccessControl.FileSystemAccessRule("DUNDERMIFFLIN\$writegroup","Modify, Synchronize",3,0,"Allow")
 $acl.SetAccessRule($writeacl)
 $sysacl = New-Object Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\System","FullControl",3,0,"Allow")
 $acl.SetAccessRule($sysacl)
 $admacl = New-Object Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl",3,0,"Allow")
 $acl.SetAccessRule($admacl)
 $acl | Set-Acl
 New-DfsnFolder -path "$dfspath\$ShareName" -Target "\\$srvname\$ShareName"

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

The account is not authorized to login from this station

J’ai obtenu cette erreur en tentant de monter un partage réseau d’une machine en Windows XP sur un Server 2016, suite à une migration d’un Server 2003 sur lequel tout fonctionnait bien.

Après comparaison de multiples clefs de registre sur des machines Windows XP similaires pour lesquelles la connexion du partage réseau était possible, j’ai fait chou blanc ; jusqu’à tomber sur ce réglage de GPO locale (les machines sont toutes en standalone, je n’ai donc pas de politique de domaine) :

Situé dans Configuration Ordinateur > Paramètres Windows > Paramètres de sécurité > Stratégies locales > Options de sécurité > Accès réseau : modèle de partage et de sécurité pour les comptes locaux, ce paramètre était réglé sur Classique pour la machine incriminée et sur Guest (invité) sur toutes les autres. Une fois le paramètre passé sur Guest, j’ai pu monter ce partage SMB.

Il semblerait donc que les versions suivantes de SMB présentes avec 2016 (3.1+) aient logiquement des politiques de sécurité renforcées par rapport au SMB 1.x livré avec 2003. Ce billet d’un blog TechNet offre plus d’informations concernant les évolutions de Server Message Block.

Powershell : liste des utilisateurs de tous les partages SMB d’un serveur

J’ai réalisé un script permettant de générer une liste d’utilisateurs des partages SMB d’un serveur, ce qui peut être pratique à des fins d’audit ou si l’on souhaite simplement contacter les utilisateurs d’un serveur de fichiers pour une maintenance par exemple.

Il existe bien l’instruction Get-SmbShare mais elle n’est disponible qu’à partir de 2012 R2 ! Afin de permettre une compatibilité avec les OS 2008 (qui seront par ailleurs bientôt plus maintenus 😱), j’ai utilisé la classe WMI Win32_share.

Le script fonctionne en plusieurs phases : tout d’abord, il liste les partages existants (tels qu’on peut les voir dans la console de Computer Management), puis liste leurs ACL et récupère les membres des groupes AD éventuellement positionnés pour n’obtenir au final que des identifiants utilisateur. Naturellement, il est nécessaire de lancer le script avec des droits d’administration sur la machine afin d’être sûr de pouvoir lire toutes les ACL et d’avoir les modules AD pour Powershell ; sans quoi il vous faudra vous résoudre à utiliser un Invoke-Command qui a de grandes chances d’être bloqué par le pare-feu.

function parse{
param($objectName)
$doneparsing = $false
for($i=0; $doneparsing -eq $false; $i++){
if ($i+1 -gt $parsedArray.Count) { Write-Host "Added user $objectName." ; $parsedArray.Add($objectName) > $null; $doneparsing = $true }
if ($parsedArray[$i] -eq $objectName){ $doneparsing = $true }
}
}

Write-Host "Share user ACL listing script"
Write-Host "============================="
Write-Host "This script will retrieve all the SMB shares and their ACLs with a 'MYDOMAIN' domain object filter to a file located in the temp folder."
$parsedArray = New-Object System.Collections.ArrayList
$sharelist = Get-WmiObject -Class Win32_Share
foreach($share in $shareList){
if ($share.Path -ne ""){
$aclList = (Get-Acl $share.Path).Access | Where-Object {$_.IdentityReference.ToString().Contains('MYDOMAIN') }
foreach ($acl in $aclList){
$objectName = $acl.IdentityReference.Value
$canoName = $objectName.ToString().SubString(9,$objectName.Length-9)
$objectType = Get-ADObject -Filter {CN -eq $canoName}
if ($objectType.ObjectClass -eq "user"){ parse($objectName) }
if ($objectType.ObjectClass -eq "group") {
Write-Host "Processing group $canoName..."
$PplInGrp = Get-ADGroupMember $canoName
foreach($ppl in $PplInGrp){ parse($ppl.SamAccountName) }
}
}
}
}
cd $env:temp
foreach ($line in $parsedArray){
Add-Content share-acl.txt $line }
Write-host "Done processing."