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)
|
||||
|
||||
Reference in New Issue
Block a user