Automate direct domain enrollment across Windows versions
This commit is contained in:
@@ -2,6 +2,8 @@ $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$serverBootstrapPath = Join-Path $repositoryRoot 'scripts\Initialize-SguDomainController.ps1'
|
||||
$clientBootstrapPath = Join-Path $repositoryRoot 'scripts\Invoke-SguClientBootstrap.ps1'
|
||||
$azureClientPath = Join-Path $repositoryRoot 'scripts\Install-SguAzureP2sClient.ps1'
|
||||
$domainEnrollmentPath = Join-Path $repositoryRoot 'scripts\Enroll-SguDomainClient.ps1'
|
||||
$repairEnrollmentPath = Join-Path $repositoryRoot 'scripts\Repair-SguClientEnrollment.ps1'
|
||||
$bicepPath = Join-Path $repositoryRoot 'infra\azure\main.bicep'
|
||||
|
||||
$tokens = $null
|
||||
@@ -16,7 +18,10 @@ if ($parseErrors.Count -gt 0) {
|
||||
$networkFunctionNames = @(
|
||||
'Test-PrivateIPv4Address',
|
||||
'ConvertTo-NetworkCidr',
|
||||
'ConvertTo-PrivateNetworkCidr'
|
||||
'ConvertTo-PrivateNetworkCidr',
|
||||
'ConvertTo-PublicNetworkCidr',
|
||||
'Get-ActiveIPv4Adapters',
|
||||
'Resolve-PrivateInterfaceAlias'
|
||||
)
|
||||
$networkFunctions = $serverAst.FindAll({
|
||||
param($node)
|
||||
@@ -39,7 +44,7 @@ $clientNetworkFunctions = $clientAst.FindAll({
|
||||
$node -is [Management.Automation.Language.FunctionDefinitionAst] -and
|
||||
$node.Name -in @('Test-IPv4AddressesSharePrefix', 'Resolve-ClientInterfaceAlias',
|
||||
'Set-ClientServerRoute', 'Set-ClientDomainDns', 'Test-TcpPort', 'Assert-ClientOperatingSystem',
|
||||
'Wait-ClientInterface')
|
||||
'Wait-ClientInterface', 'Test-PrivateIPv4Address', 'Test-ClientDomainDns')
|
||||
}, $true)
|
||||
Invoke-Expression (($clientNetworkFunctions | ForEach-Object { $_.Extent.Text }) -join [Environment]::NewLine)
|
||||
|
||||
@@ -65,11 +70,29 @@ Describe 'SGU public-cloud network safety' {
|
||||
$wasRejected | Should Be $true
|
||||
}
|
||||
|
||||
It 'canonicalizes an explicitly authorized public enrollment network' {
|
||||
ConvertTo-PublicNetworkCidr -Cidr '200.13.89.183/24' |
|
||||
Should Be '200.13.89.0/24'
|
||||
}
|
||||
|
||||
It 'rejects private space in the public enrollment allowlist' {
|
||||
$wasRejected = $false
|
||||
try {
|
||||
ConvertTo-PublicNetworkCidr -Cidr '10.77.0.0/16' | Out-Null
|
||||
}
|
||||
catch {
|
||||
$wasRejected = $true
|
||||
}
|
||||
$wasRejected | Should Be $true
|
||||
}
|
||||
|
||||
It 'exposes explicit Azure modes on both bootstraps' {
|
||||
((Get-Command $serverBootstrapPath).Parameters.Keys -contains
|
||||
'NetworkConfigurationMode') | Should Be $true
|
||||
((Get-Command $serverBootstrapPath).Parameters.Keys -contains
|
||||
'TrustedClientNetworks') | Should Be $true
|
||||
((Get-Command $serverBootstrapPath).Parameters.Keys -contains
|
||||
'PublicEnrollmentNetworks') | Should Be $true
|
||||
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
||||
'ConnectivityMode') | Should Be $true
|
||||
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
||||
@@ -117,14 +140,57 @@ Describe 'SGU public-cloud network safety' {
|
||||
$source | Should Match 'Add-DnsClientNrptRule'
|
||||
}
|
||||
|
||||
It 'limits optional public administration to RDP' {
|
||||
It 'keeps public enrollment closed unless explicit source CIDRs are supplied' {
|
||||
$template = Get-Content -LiteralPath $bicepPath -Raw
|
||||
$template | Should Match "name: 'Allow-RDP-from-administrator'"
|
||||
$template | Should Match "destinationPortRange: '3389'"
|
||||
$template | Should Match 'param publicEnrollmentSourceAddressPrefixes array = \[\]'
|
||||
$template | Should Match "name: 'Allow-Direct-AD-TCP'"
|
||||
$template | Should Match 'sourceAddressPrefixes: publicEnrollmentSourceAddressPrefixes'
|
||||
$template | Should Match 'param deployVpnGateway bool = true'
|
||||
$template | Should Not Match "sourceAddressPrefix: '0\.0\.0\.0/0'"
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'SGU direct public enrollment discovery' {
|
||||
It 'distinguishes public server addresses from LAN and VPN addresses' {
|
||||
Test-PrivateIPv4Address -Address ([ipaddress]'10.77.0.4') | Should Be $true
|
||||
Test-PrivateIPv4Address -Address ([ipaddress]'172.30.0.4') | Should Be $true
|
||||
Test-PrivateIPv4Address -Address ([ipaddress]'192.168.50.10') | Should Be $true
|
||||
Test-PrivateIPv4Address -Address ([ipaddress]'20.9.81.130') | Should Be $false
|
||||
}
|
||||
|
||||
It 'bootstraps DoH and host mappings after authenticated server discovery' {
|
||||
$source = Get-Content -LiteralPath $clientBootstrapPath -Raw
|
||||
$source | Should Match 'Set-DnsServerEncryptionProtocol'
|
||||
$source | Should Match 'Enable-ClientDnsOverHttps'
|
||||
$source | Should Match 'Set-ClientHostMappings'
|
||||
$source | Should Match 'Test-ClientDomainDns'
|
||||
$source | Should Match 'Get-DnsClientDohServerAddress'
|
||||
$source | Should Match 'Add-DnsClientDohServerAddress'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Azure accelerated server adapters' {
|
||||
It 'ignores an Up accelerated VF that has no IPv4 interface' {
|
||||
Mock Get-NetAdapter {
|
||||
[pscustomobject]@{ Name = 'Ethernet'; ifIndex = 4; Status = 'Up' }
|
||||
[pscustomobject]@{ Name = 'Ethernet VF'; ifIndex = 10; Status = 'Up' }
|
||||
[pscustomobject]@{ Name = 'Disconnected'; ifIndex = 12; Status = 'Disconnected' }
|
||||
}
|
||||
Mock Get-NetIPInterface {
|
||||
if ($InterfaceIndex -eq 4) {
|
||||
[pscustomobject]@{ InterfaceIndex = 4; ConnectionState = 'Connected' }
|
||||
}
|
||||
}
|
||||
Mock Get-NetIPConfiguration { [pscustomobject]@{ IPv4DefaultGateway = '10.77.0.1' } }
|
||||
$adapters = @(Get-ActiveIPv4Adapters)
|
||||
$adapters.Count | Should Be 1
|
||||
$adapters[0].Name | Should Be 'Ethernet'
|
||||
Resolve-PrivateInterfaceAlias | Should Be 'Ethernet'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'SGU route and interface discovery' {
|
||||
BeforeEach {
|
||||
Mock Get-NetIPInterface {
|
||||
@@ -293,6 +359,23 @@ Describe 'SGU Windows capability checks' {
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'SGU repeated domain enrollment' {
|
||||
It 'rejoins a same-name forest when the machine secure channel is broken' {
|
||||
$source = Get-Content -LiteralPath $domainEnrollmentPath -Raw
|
||||
$source | Should Match 'Test-ComputerSecureChannel'
|
||||
$source | Should Match '\$computer\.PartOfDomain -and \$domainMembershipHealthy'
|
||||
$source | Should Match 'Reset-ComputerMachinePassword'
|
||||
$source | Should Match 'DomainControllerDnsName'
|
||||
$source | Should Match 'Add-Computer @joinParams'
|
||||
}
|
||||
|
||||
It 'defers domain-only repair until the secure channel is healthy' {
|
||||
$source = Get-Content -LiteralPath $repairEnrollmentPath -Raw
|
||||
$source | Should Match 'Test-ComputerSecureChannel'
|
||||
$source | Should Match 'if \(\$domainReady\)'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'SGU real TCP probe' {
|
||||
It 'connects with a bound source and interface without relying on ICMP' {
|
||||
$listener = [Net.Sockets.TcpListener]::new([ipaddress]'127.0.0.1', 0)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$wallpaperScript = Join-Path $repositoryRoot 'scripts\Set-SguWelcomeWallpaper.ps1'
|
||||
$source = Get-Content -LiteralPath $wallpaperScript -Raw
|
||||
$tokens = $null
|
||||
$parseErrors = $null
|
||||
$ast = [Management.Automation.Language.Parser]::ParseFile($wallpaperScript, [ref]$tokens, [ref]$parseErrors)
|
||||
if ($parseErrors.Count) { throw ($parseErrors -join [Environment]::NewLine) }
|
||||
$lookup = $ast.Find({
|
||||
param($node)
|
||||
$node -is [Management.Automation.Language.FunctionDefinitionAst] -and
|
||||
$node.Name -eq 'Get-DirectoryWelcomeMetadata'
|
||||
}, $true)
|
||||
|
||||
function Invoke-WelcomeFixture {
|
||||
param($DirectoryGender, [string]$ExplicitGender)
|
||||
|
||||
# Replace only the external directory lookup. Execute the actual script,
|
||||
# including its validated parameters, metadata assignment and JPEG renderer.
|
||||
$fixtureJson = [pscustomobject]@{
|
||||
DisplayName = 'Usuario de prueba'
|
||||
Gender = $DirectoryGender
|
||||
Location = 'Sala de pruebas'
|
||||
OrganizationalUnit = 'Laboratorio'
|
||||
} | ConvertTo-Json -Compress
|
||||
$fixtureFunction = 'function Get-DirectoryWelcomeMetadata { param($UserName, $MachineName); ConvertFrom-Json ''' +
|
||||
$fixtureJson.Replace("'", "''") + ''' }'
|
||||
$fixtureSource = $source.Remove($lookup.Extent.StartOffset, $lookup.Extent.EndOffset - $lookup.Extent.StartOffset).
|
||||
Insert($lookup.Extent.StartOffset, $fixtureFunction)
|
||||
$testScript = Join-Path $TestDrive ('wallpaper-' + [Guid]::NewGuid().ToString('N') + '.ps1')
|
||||
[IO.File]::WriteAllText($testScript, $fixtureSource, [Text.UTF8Encoding]::new($false))
|
||||
$parameters = @{
|
||||
BaseImagePath = Join-Path $repositoryRoot 'assets\branding\darkblue.jpg'
|
||||
FontsPath = Join-Path $repositoryRoot 'assets\branding\fonts'
|
||||
OutputPath = Join-Path $TestDrive ([IO.Path]::GetFileNameWithoutExtension($testScript) + '.jpg')
|
||||
CanvasWidth = 640
|
||||
CanvasHeight = 480
|
||||
SkipApply = $true
|
||||
}
|
||||
if ($ExplicitGender) { $parameters.Gender = $ExplicitGender }
|
||||
$previousLocalAppData = $env:LOCALAPPDATA
|
||||
try {
|
||||
$env:LOCALAPPDATA = $TestDrive
|
||||
& $testScript @parameters
|
||||
}
|
||||
finally { $env:LOCALAPPDATA = $previousLocalAppData }
|
||||
}
|
||||
|
||||
Describe 'Welcome wallpaper with AD metadata' {
|
||||
It 'renders a neutral JPEG when AD has no gender' {
|
||||
$result = Invoke-WelcomeFixture -DirectoryGender $null
|
||||
$result.WelcomeHeading | Should Be 'Te damos la bienvenida,'
|
||||
$result.Applied | Should Be $false
|
||||
$bitmap = [Drawing.Image]::FromFile($result.OutputPath)
|
||||
try { $bitmap.Width | Should Be 640; $bitmap.Height | Should Be 480 }
|
||||
finally { $bitmap.Dispose() }
|
||||
}
|
||||
|
||||
It 'uses neutral wording for empty or unrecognized metadata' {
|
||||
foreach ($value in @('', 'Unknown')) {
|
||||
(Invoke-WelcomeFixture -DirectoryGender $value).WelcomeHeading | Should Be 'Te damos la bienvenida,'
|
||||
}
|
||||
}
|
||||
|
||||
It 'keeps the gendered greetings for recognized directory values' {
|
||||
(Invoke-WelcomeFixture -DirectoryGender 'Female').WelcomeHeading | Should Be 'Bienvenida,'
|
||||
(Invoke-WelcomeFixture -DirectoryGender 'Male').WelcomeHeading | Should Be 'Bienvenido,'
|
||||
}
|
||||
|
||||
It 'honors an explicit gender over directory metadata' {
|
||||
(Invoke-WelcomeFixture -DirectoryGender 'Female' -ExplicitGender 'Male').WelcomeHeading | Should Be 'Bienvenido,'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user