Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e62a65316 | ||
|
|
1332f546fa |
@@ -178,6 +178,7 @@ function Set-ClientDomainAddress {
|
|||||||
$matchingAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
$matchingAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
||||||
-ErrorAction SilentlyContinue |
|
-ErrorAction SilentlyContinue |
|
||||||
Where-Object {
|
Where-Object {
|
||||||
|
$_.AddressState -eq 'Preferred' -and
|
||||||
$_.IPAddress -notmatch '^(127\.|169\.254\.)' -and
|
$_.IPAddress -notmatch '^(127\.|169\.254\.)' -and
|
||||||
(Test-IPv4AddressesSharePrefix -FirstAddress ([ipaddress]$_.IPAddress) `
|
(Test-IPv4AddressesSharePrefix -FirstAddress ([ipaddress]$_.IPAddress) `
|
||||||
-SecondAddress $DomainControllerAddress -PrefixLength $PrefixLength)
|
-SecondAddress $DomainControllerAddress -PrefixLength $PrefixLength)
|
||||||
@@ -207,7 +208,20 @@ function Set-ClientDomainAddress {
|
|||||||
New-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
New-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
||||||
-IPAddress $RequestedAddress.IPAddressToString -PrefixLength $PrefixLength | Out-Null
|
-IPAddress $RequestedAddress.IPAddressToString -PrefixLength $PrefixLength | Out-Null
|
||||||
}
|
}
|
||||||
return $RequestedAddress
|
|
||||||
|
$addressReadyDeadline = (Get-Date).AddSeconds(20)
|
||||||
|
do {
|
||||||
|
$configuredAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex `
|
||||||
|
-AddressFamily IPv4 -IPAddress $RequestedAddress.IPAddressToString `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
if ($configuredAddress -and $configuredAddress.AddressState -eq 'Preferred') {
|
||||||
|
return $RequestedAddress
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
} while ((Get-Date) -lt $addressReadyDeadline)
|
||||||
|
|
||||||
|
$observedState = if ($configuredAddress) { $configuredAddress.AddressState } else { 'Missing' }
|
||||||
|
throw "The SGU client address '$RequestedAddress' did not become ready on '$InterfaceAlias' within 20 seconds. Observed state: $observedState."
|
||||||
}
|
}
|
||||||
|
|
||||||
function Test-TcpPort {
|
function Test-TcpPort {
|
||||||
@@ -234,6 +248,23 @@ function Test-TcpPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Wait-TcpPort {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][ipaddress]$Address,
|
||||||
|
[Parameter(Mandatory)][int]$Port,
|
||||||
|
[int]$TimeoutSeconds = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||||
|
do {
|
||||||
|
if (Test-TcpPort -Address $Address -Port $Port -TimeoutMilliseconds 2000) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 750
|
||||||
|
} while ((Get-Date) -lt $deadline)
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
function Connect-SguAzureP2s {
|
function Connect-SguAzureP2s {
|
||||||
param([Parameter(Mandatory)][string]$ConnectionName)
|
param([Parameter(Mandatory)][string]$ConnectionName)
|
||||||
|
|
||||||
@@ -348,8 +379,8 @@ else {
|
|||||||
-ServerAddresses $DomainControllerIPv4Address.IPAddressToString
|
-ServerAddresses $DomainControllerIPv4Address.IPAddressToString
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not (Test-TcpPort -Address $DomainControllerIPv4Address -Port 5985)) {
|
if (-not (Wait-TcpPort -Address $DomainControllerIPv4Address -Port 5985 -TimeoutSeconds 20)) {
|
||||||
throw "The domain controller at $DomainControllerIPv4Address is not accepting WinRM on TCP 5985. Run the server bootstrap first and verify the selected IP."
|
throw "The domain controller at $DomainControllerIPv4Address did not accept WinRM on TCP 5985 after 20 seconds. Run the server bootstrap first and verify the selected IP."
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $DomainCredential) {
|
if (-not $DomainCredential) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ Bootstrap reproducible para el laboratorio SGU.
|
|||||||
- **Advertencia:** el bootstrap de servidor crea un bosque nuevo. No restaura los SID, contraseñas ni relaciones de confianza del bosque anterior; para conservarlos se requiere una recuperación de bosque desde una copia de estado del sistema.
|
- **Advertencia:** el bootstrap de servidor crea un bosque nuevo. No restaura los SID, contraseñas ni relaciones de confianza del bosque anterior; para conservarlos se requiere una recuperación de bosque desde una copia de estado del sistema.
|
||||||
- `sgu-server-bootstrap-$Version.zip`: crea el bosque AD/DNS, OUs, grupo RDP, GPO, recurso `Packages`, broker mTLS y administración remota; se reanuda solo después del reinicio.
|
- `sgu-server-bootstrap-$Version.zip`: crea el bosque AD/DNS, OUs, grupo RDP, GPO, recurso `Packages`, broker mTLS y administración remota; se reanuda solo después del reinicio.
|
||||||
- `sgu-client-bootstrap-$Version.zip`: registra un certificado mTLS único, instala y valida el Credential Provider antes de unir el equipo al dominio, habilita RDP/WinRM y se repara al arranque.
|
- `sgu-client-bootstrap-$Version.zip`: registra un certificado mTLS único, instala y valida el Credential Provider antes de unir el equipo al dominio, habilita RDP/WinRM y se repara al arranque.
|
||||||
- En clientes Hyper-V con dos NIC, el bootstrap selecciona la red privada sin puerta de enlace, solicita o acepta la IP fija del cliente y conserva en pantalla y archivo cualquier error de enrolamiento.
|
- En clientes Hyper-V con dos NIC, el bootstrap selecciona la red privada sin puerta de enlace, solicita o acepta la IP fija del cliente, espera a que la dirección y WinRM estén disponibles y conserva en pantalla y archivo cualquier error de enrolamiento.
|
||||||
- `sgu-linux-client-bootstrap-$Version.zip`: une clientes Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux con realmd, Kerberos y SSSD. Solicita interactivamente la contraseña de unión y no instala el Credential Provider de Windows.
|
- `sgu-linux-client-bootstrap-$Version.zip`: une clientes Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux con realmd, Kerberos y SSSD. Solicita interactivamente la contraseña de unión y no instala el Credential Provider de Windows.
|
||||||
- `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente.
|
- `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente.
|
||||||
- El bootstrap Azure conserva la IP privada administrada por la NIC de Azure, autoriza el pool P2S en los firewalls SGU y nunca publica LDAP, Kerberos, SMB, RPC, WinRM ni el Auth Broker directamente a Internet.
|
- El bootstrap Azure conserva la IP privada administrada por la NIC de Azure, autoriza el pool P2S en los firewalls SGU y nunca publica LDAP, Kerberos, SMB, RPC, WinRM ni el Auth Broker directamente a Internet.
|
||||||
@@ -120,6 +120,7 @@ Bootstrap reproducible para el laboratorio SGU.
|
|||||||
- El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea cada grupo dentro de la OU de su rol y migra idempotentemente cualquier grupo heredado sin cambiar su SID.
|
- El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea cada grupo dentro de la OU de su rol y migra idempotentemente cualquier grupo heredado sin cambiar su SID.
|
||||||
- El Auth Broker resuelve la dirección guardada de administrativos y docentes mediante `GetDireccion`, `GetLocalidadListado` y `GetColoniasListado`, evitando conservar los valores transitorios `Seleccione...` de los controles dinámicos de SGU.
|
- El Auth Broker resuelve la dirección guardada de administrativos y docentes mediante `GetDireccion`, `GetLocalidadListado` y `GetColoniasListado`, evitando conservar los valores transitorios `Seleccione...` de los controles dinámicos de SGU.
|
||||||
- El enrolamiento y la reparación de clientes Windows crean y verifican idempotentemente la cuenta local estándar `alumno`, sin pertenencia al grupo de administradores.
|
- El enrolamiento y la reparación de clientes Windows crean y verifican idempotentemente la cuenta local estándar `alumno`, sin pertenencia al grupo de administradores.
|
||||||
|
- La descripción de la cuenta local administrada respeta el límite de 48 caracteres de Windows 10 Enterprise.
|
||||||
- El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral.
|
- El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral.
|
||||||
- El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker.
|
- El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker.
|
||||||
- Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.
|
- Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ param()
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$userName = 'alumno'
|
$userName = 'alumno'
|
||||||
$plainTextPassword = 'ingenieria'
|
$plainTextPassword = 'ingenieria'
|
||||||
$description = 'Cuenta local estandar de recuperacion para equipos SGU'
|
$description = 'Cuenta local estandar SGU para recuperacion'
|
||||||
|
|
||||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ Describe 'SGU public-cloud network safety' {
|
|||||||
$source | Should Not Match "Get-NetRoute -AddressFamily IPv4 -DestinationPrefix '0\.0\.0\.0/0'"
|
$source | Should Not Match "Get-NetRoute -AddressFamily IPv4 -DestinationPrefix '0\.0\.0\.0/0'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It 'waits for the new address and WinRM route to stabilize' {
|
||||||
|
$source = Get-Content -LiteralPath $clientBootstrapPath -Raw
|
||||||
|
$source | Should Match "AddressState -eq 'Preferred'"
|
||||||
|
$source | Should Match 'function Wait-TcpPort'
|
||||||
|
$source | Should Match 'Wait-TcpPort -Address \$DomainControllerIPv4Address -Port 5985'
|
||||||
|
}
|
||||||
|
|
||||||
It 'uses an all-user machine-certificate VPN profile' {
|
It 'uses an all-user machine-certificate VPN profile' {
|
||||||
$source = Get-Content -LiteralPath $azureClientPath -Raw
|
$source = Get-Content -LiteralPath $azureClientPath -Raw
|
||||||
$source | Should Match '-AuthenticationMethod MachineCertificate'
|
$source | Should Match '-AuthenticationMethod MachineCertificate'
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||||
|
$localUserScriptPath = Join-Path $repositoryRoot 'scripts\Set-SguStandardLocalUser.ps1'
|
||||||
|
|
||||||
|
$tokens = $null
|
||||||
|
$parseErrors = $null
|
||||||
|
$scriptAst = [Management.Automation.Language.Parser]::ParseFile(
|
||||||
|
$localUserScriptPath,
|
||||||
|
[ref]$tokens,
|
||||||
|
[ref]$parseErrors)
|
||||||
|
if ($parseErrors.Count -gt 0) {
|
||||||
|
throw ($parseErrors -join [Environment]::NewLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
$descriptionAssignment = $scriptAst.Find({
|
||||||
|
param($node)
|
||||||
|
$node -is [Management.Automation.Language.AssignmentStatementAst] -and
|
||||||
|
$node.Left.Extent.Text -eq '$description'
|
||||||
|
}, $true)
|
||||||
|
$description = $descriptionAssignment.Right.Extent.Text.Trim("'")
|
||||||
|
|
||||||
|
Describe 'SGU Windows client enrollment scripts' {
|
||||||
|
It 'keeps the local-user description within the Windows 10 limit' {
|
||||||
|
($description.Length -le 48) | Should Be $true
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'declares the managed local student account' {
|
||||||
|
$source = Get-Content -LiteralPath $localUserScriptPath -Raw
|
||||||
|
$source | Should Match "\$userName = 'alumno'"
|
||||||
|
$source | Should Match "\$plainTextPassword = 'ingenieria'"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user