From bda8b354b7740f4649b004417525a630f30701f1 Mon Sep 17 00:00:00 2001 From: Alejandro Rosales Date: Fri, 18 Sep 2026 09:55:39 -0600 Subject: [PATCH] Enable public Azure Files roaming deployment --- docs/release-0.6.1.md | 39 ++++++++++++++++++++++++ docs/user-roaming.md | 15 ++++++++++ scripts/Enable-SguAzureUserRoaming.ps1 | 41 ++++++++++++++++---------- tests/UserRoaming.Tests.ps1 | 21 ++++++++++--- 4 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 docs/release-0.6.1.md diff --git a/docs/release-0.6.1.md b/docs/release-0.6.1.md new file mode 100644 index 0000000..454dfd2 --- /dev/null +++ b/docs/release-0.6.1.md @@ -0,0 +1,39 @@ +# SGU Credential Provider 0.6.1 + +Continúa la serie publicada después de `0.6.0`. + +## Cambios + +- El configurador de roaming admite explícitamente Azure Files mediante endpoint + público con `-EndpointAccess Public`; el modo privado sigue siendo el valor + predeterminado. +- Se corrige la generación del nombre de la identidad de almacenamiento para + respetar el límite de 15 caracteres de una cuenta de equipo de Active + Directory. +- Las ACL de `profiles` y `redirected` aplican mínimo privilegio en la raíz: los + grupos autorizados pueden crear su directorio, pero no modificar ni eliminar + directorios ajenos; `CREATOR OWNER` conserva el control dentro del directorio + propio. +- Se documenta la secuencia de despliegue con firewall de Azure Storage en modo + `Deny`, autorizando finalmente sólo la red institucional `200.13.89.0/24`. + +## Validación del despliegue + +- Azure Files usa autenticación AD DS con Kerberos AES-256. +- `AF-02` y `AF-03` validaron ticket Kerberos, TCP 445 y lectura/escritura en los + shares `profiles` y `redirected`. +- FSLogix 26.08 (`3.26.826.17182`) quedó instalado y activo en ambos clientes. +- La GPO de FSLogix mantiene una base deshabilitada y se habilita sólo para los + SID de `SGU-Docentes` y `SGU-Administrativos`. +- La GPO de Alumnos redirige únicamente Escritorio y Documentos. + +## Descargas + +- `sgu-windows-client-bootstrap-0.6.1.zip` +- `sgu-server-bootstrap-0.6.1.zip` +- `sgu-linux-client-bootstrap-0.6.1.zip` +- `sgu-azure-infrastructure-0.6.1.zip` +- `SHA256SUMS-0.6.1.txt` + +Verifique los hashes SHA-256 antes de ejecutar los paquetes. No se incluyen +credenciales, claves de almacenamiento ni artefactos temporales del despliegue. diff --git a/docs/user-roaming.md b/docs/user-roaming.md index 1ded99f..f758362 100644 --- a/docs/user-roaming.md +++ b/docs/user-roaming.md @@ -26,6 +26,16 @@ VNet. El modo de enrolamiento público directo no puede usar estos recursos y el script de despliegue exige `-DeployUserRoaming $false` cuando no se despliega el gateway P2S. +Para una cuenta SMB ya existente cuyo endpoint público se limite mediante el +firewall de Storage a las redes institucionales, el configurador admite +`-EndpointAccess Public`. En ese modo no exige una dirección RFC1918, pero sigue +comprobando resolución DNS y TCP 445. La creación de los shares y las reglas de +red de la cuenta existente se realiza antes de ejecutar el configurador. El host +que aplica la integración AD/ACL debe conservar acceso temporal a SMB durante +esa ejecución; después se establece `defaultAction=Deny` y se deja únicamente la +red institucional autorizada. En el despliegue LCI actual esa regla final es +`200.13.89.0/24`. + Azure Files se integra con el AD DS SGU, no con cuentas o claves guardadas en cada cliente. El script configura AES-256 para Kerberos, una ACL de mínimo privilegio con `CREATOR OWNER`, administradores y los grupos del rol, y permiso @@ -33,6 +43,11 @@ SMB predeterminado para identidades autenticadas. La ACL NTFS mantiene aislado el contenido de cada usuario. La clave de la cuenta se usa en memoria sólo para crear las ACL iniciales y no se escribe en el dominio, en GPO ni en disco. +Los grupos de rol sólo pueden enumerar la raíz y crear su propio directorio; no +reciben `Modify` en la raíz compartida. El directorio creado hereda `CREATOR +OWNER`, de modo que un usuario no puede modificar ni eliminar el contenedor de +otro usuario. + ## 1. Desplegar la infraestructura El soporte está activado por omisión para despliegues P2S: diff --git a/scripts/Enable-SguAzureUserRoaming.ps1 b/scripts/Enable-SguAzureUserRoaming.ps1 index 5b1b4d3..e5440cf 100644 --- a/scripts/Enable-SguAzureUserRoaming.ps1 +++ b/scripts/Enable-SguAzureUserRoaming.ps1 @@ -21,6 +21,8 @@ param( [string]$AdministrativeGroupName = 'SGU-Administrativos', [string]$StudentGpoName = 'SGU - AL redirected folders', [string]$StaffGpoName = 'SGU - AD-DO FSLogix profiles', + [ValidateSet('Private', 'Public')] + [string]$EndpointAccess = 'Private', [ValidateRange(1024, 1048576)] [int]$FsLogixProfileSizeMiB = 30000, [string]$AzFilesHybridModulePath, @@ -76,13 +78,13 @@ function Import-SguAzFilesHybrid { function Get-SguStorageSamAccountName { param([Parameter(Mandatory)][string]$StorageName) - if ($StorageName.Length -le 20) { + if ($StorageName.Length -le 15) { return $StorageName } $sha256 = [Security.Cryptography.SHA256]::Create() try { $hash = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($StorageName)) - $suffix = ([BitConverter]::ToString($hash) -replace '-', '').Substring(0, 15).ToLowerInvariant() + $suffix = ([BitConverter]::ToString($hash) -replace '-', '').Substring(0, 10).ToLowerInvariant() return "sgufs$suffix" } finally { @@ -322,15 +324,19 @@ if ($PSCmdlet.ShouldProcess($StorageAccountName, 'Grant authenticated AD identit -DefaultSharePermission StorageFileDataSmbShareContributor } -$privateAddresses = @(Resolve-DnsName -Name $fileEndpointHost -Type A -ErrorAction Stop | +$endpointAddresses = @(Resolve-DnsName -Name $fileEndpointHost -Type A -ErrorAction Stop | Where-Object IPAddress | Select-Object -ExpandProperty IPAddress) -if ($privateAddresses.Count -eq 0 -or @($privateAddresses | Where-Object { - $_ -match '^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)' - }).Count -eq 0) { +if ($endpointAddresses.Count -eq 0) { + throw "$fileEndpointHost did not resolve to an IPv4 address." +} +$privateAddresses = @($endpointAddresses | Where-Object { + $_ -match '^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)' + }) +if ($EndpointAccess -eq 'Private' -and $privateAddresses.Count -eq 0) { throw "$fileEndpointHost did not resolve to a private endpoint. Verify the privatelink.file.core.windows.net VNet link and the DC Azure DNS forwarder." } if (-not (Test-NetConnection -ComputerName $fileEndpointHost -Port 445 -InformationLevel Quiet)) { - throw "The domain controller cannot reach $fileEndpointHost on TCP 445 through the private endpoint." + throw "The domain controller cannot reach $fileEndpointHost on TCP 445 using the selected $EndpointAccess endpoint." } $storageKey = @(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroupName ` @@ -344,23 +350,24 @@ $storageCredential = [PSCredential]::new( (ConvertTo-SecureString -String $storageKey -AsPlainText -Force)) $profilesSharePath = "\\$fileEndpointHost\$FsLogixProfilesShareName" $redirectedFoldersSharePath = "\\$fileEndpointHost\$RedirectedFoldersShareName" +$perUserRootRights = [Security.AccessControl.FileSystemRights]::CreateDirectories -bor + [Security.AccessControl.FileSystemRights]::ListDirectory -bor + [Security.AccessControl.FileSystemRights]::ReadAttributes -bor + [Security.AccessControl.FileSystemRights]::ReadExtendedAttributes -bor + [Security.AccessControl.FileSystemRights]::ReadPermissions -bor + [Security.AccessControl.FileSystemRights]::Traverse -bor + [Security.AccessControl.FileSystemRights]::Synchronize try { if ($PSCmdlet.ShouldProcess($profilesSharePath, 'Apply isolated FSLogix root ACLs')) { Set-SguAzureFileRootAcl -UncPath $profilesSharePath -Credential $storageCredential ` -DomainAdminsSid $domainAdminsSid ` - -ContributorSids @($professorGroup.SID, $administrativeGroup.SID) + -ContributorSids @($professorGroup.SID, $administrativeGroup.SID) ` + -ContributorRights $perUserRootRights } if ($PSCmdlet.ShouldProcess($redirectedFoldersSharePath, 'Apply isolated student-folder root ACLs')) { - $studentRootRights = [Security.AccessControl.FileSystemRights]::CreateDirectories -bor - [Security.AccessControl.FileSystemRights]::ListDirectory -bor - [Security.AccessControl.FileSystemRights]::ReadAttributes -bor - [Security.AccessControl.FileSystemRights]::ReadExtendedAttributes -bor - [Security.AccessControl.FileSystemRights]::ReadPermissions -bor - [Security.AccessControl.FileSystemRights]::Traverse -bor - [Security.AccessControl.FileSystemRights]::Synchronize Set-SguAzureFileRootAcl -UncPath $redirectedFoldersSharePath -Credential $storageCredential ` -DomainAdminsSid $domainAdminsSid -ContributorSids @($studentGroup.SID) ` - -ContributorRights $studentRootRights + -ContributorRights $perUserRootRights } } finally { @@ -415,6 +422,8 @@ foreach ($staffGroup in @($professorGroup, $administrativeGroup)) { [pscustomobject]@{ StorageAccountName = $StorageAccountName FileEndpoint = $fileEndpointHost + EndpointAccess = $EndpointAccess + EndpointAddresses = $endpointAddresses PrivateEndpointAddresses = $privateAddresses DirectoryService = $directoryService KerberosEncryption = 'AES256' diff --git a/tests/UserRoaming.Tests.ps1 b/tests/UserRoaming.Tests.ps1 index 82fd1eb..c6e8f9c 100644 --- a/tests/UserRoaming.Tests.ps1 +++ b/tests/UserRoaming.Tests.ps1 @@ -77,6 +77,16 @@ Describe 'SGU Azure user-roaming infrastructure' { } Describe 'SGU role-specific roaming policies' { + It 'supports an explicitly selected public SMB endpoint while keeping private as the default' { + (((Get-Command $configurationPath).Parameters['EndpointAccess'].Attributes | + Where-Object { $_ -is [Management.Automation.ValidateSetAttribute] }).ValidValues -join ',') | + Should Be 'Private,Public' + $source = Get-Content -LiteralPath $configurationPath -Raw + $source | Should Match ([regex]::Escape("[string]`$EndpointAccess = 'Private'")) + $source | Should Match ([regex]::Escape("`$EndpointAccess -eq 'Private'")) + $source | Should Match 'selected \$EndpointAccess endpoint' + } + It 'uses an AD computer identity with AES-256 Kerberos' { $source = Get-Content -LiteralPath $configurationPath -Raw $source | Should Match "DomainAccountType = 'ComputerAccount'" @@ -90,6 +100,9 @@ Describe 'SGU role-specific roaming policies' { $source | Should Match "SecurityIdentifier\]::new\('S-1-3-0'\)" $source | Should Match 'PropagationFlags\]::InheritOnly' $source | Should Match 'SetAccessRuleProtection\(\$true, \$false\)' + $source | Should Match '\$perUserRootRights' + $source | Should Match 'CreateDirectories' + $source | Should Match 'ContributorRights \$perUserRootRights' } It 'redirects only the student Desktop and Documents folders' { @@ -118,11 +131,11 @@ Describe 'SGU role-specific roaming policies' { It 'derives valid deterministic AD names for long storage account names' { $name = Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx' - $name.Length | Should Be 20 - $name | Should Match '^sgufs[0-9a-f]{15}$' + $name.Length | Should Be 15 + $name | Should Match '^sgufs[0-9a-f]{10}$' (Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx') | Should Be $name - (Get-SguStorageSamAccountName -StorageName 'sguroam1234567890123') | - Should Be 'sguroam1234567890123' + (Get-SguStorageSamAccountName -StorageName 'sguroam1234567') | + Should Be 'sguroam1234567' } }