Prepare Azure P2S domain deployment

This commit is contained in:
2026-09-08 15:32:07 -06:00
parent 991fc70600
commit a24c25a3fb
18 changed files with 1288 additions and 28 deletions
+130 -12
View File
@@ -6,6 +6,9 @@ param(
[int]$PrefixLength = 24,
[string]$NetworkInterfaceAlias,
[ipaddress]$DefaultGateway,
[ValidateSet('GuestStatic', 'PlatformManaged')]
[string]$NetworkConfigurationMode = 'GuestStatic',
[string[]]$TrustedClientNetworks = @(),
[ipaddress[]]$DnsForwarders = @(),
[string]$DomainName = 'lci.lasalle.mx',
[string]$DomainNetbios = 'LCI',
@@ -69,6 +72,67 @@ function Get-DomainBaseDn {
return (($DnsDomainName -split '\.') | ForEach-Object { "DC=$_" }) -join ','
}
function Test-PrivateIPv4Address {
param([Parameter(Mandatory)][ipaddress]$Address)
if ($Address.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork) {
return $false
}
$bytes = $Address.GetAddressBytes()
return $bytes[0] -eq 10 -or
($bytes[0] -eq 172 -and $bytes[1] -ge 16 -and $bytes[1] -le 31) -or
($bytes[0] -eq 192 -and $bytes[1] -eq 168)
}
function ConvertTo-NetworkCidr {
param(
[Parameter(Mandatory)][ipaddress]$Address,
[Parameter(Mandatory)][ValidateRange(1, 32)][int]$NetworkPrefixLength
)
if ($Address.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork) {
throw 'Only IPv4 networks are supported by the SGU bootstrap.'
}
$addressBytes = $Address.GetAddressBytes()
$networkBytes = [byte[]]::new(4)
$remainingBits = $NetworkPrefixLength
for ($index = 0; $index -lt 4; $index++) {
$mask = if ($remainingBits -ge 8) {
255
}
elseif ($remainingBits -le 0) {
0
}
else {
256 - [Math]::Pow(2, 8 - $remainingBits)
}
$networkBytes[$index] = [byte]($addressBytes[$index] -band [int]$mask)
$remainingBits -= 8
}
return "$(($networkBytes | ForEach-Object { [string]$_ }) -join '.')/$NetworkPrefixLength"
}
function ConvertTo-PrivateNetworkCidr {
param([Parameter(Mandatory)][string]$Cidr)
if ($Cidr -notmatch '^([^/]+)/(\d{1,2})$') {
throw "Trusted client network '$Cidr' must use IPv4 CIDR notation, for example 172.30.0.0/24."
}
$address = $null
if (-not [ipaddress]::TryParse($Matches[1], [ref]$address) -or
$address.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork) {
throw "Trusted client network '$Cidr' is not a valid IPv4 network."
}
$networkPrefixLength = [int]$Matches[2]
if ($networkPrefixLength -lt 1 -or $networkPrefixLength -gt 32) {
throw "Trusted client network '$Cidr' has an invalid prefix length."
}
if (-not (Test-PrivateIPv4Address -Address $address)) {
throw "Trusted client network '$Cidr' is not private RFC1918 space. The bootstrap never exposes AD services to public client addresses."
}
return ConvertTo-NetworkCidr -Address $address -NetworkPrefixLength $networkPrefixLength
}
function Resolve-PrivateInterfaceAlias {
param([string]$RequestedAlias)
@@ -147,6 +211,27 @@ function Set-StaticDomainAddress {
-ServerAddresses $Address.IPAddressToString
}
function Assert-PlatformManagedDomainAddress {
param(
[Parameter(Mandatory)][string]$InterfaceAlias,
[Parameter(Mandatory)][ipaddress]$Address,
[Parameter(Mandatory)][int]$NetworkPrefixLength
)
$adapter = Get-NetAdapter -Name $InterfaceAlias -ErrorAction Stop
$matchingAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-IPAddress $Address.IPAddressToString -ErrorAction SilentlyContinue |
Where-Object PrefixLength -eq $NetworkPrefixLength |
Select-Object -First 1
if (-not $matchingAddress) {
$observed = @(Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-ErrorAction SilentlyContinue |
Where-Object PrefixOrigin -ne 'WellKnown' |
ForEach-Object { "$($_.IPAddress)/$($_.PrefixLength)" }) -join ', '
throw "PlatformManaged mode expected $Address/$NetworkPrefixLength on $InterfaceAlias, but found: $observed. Configure a static private IP on the Azure NIC before running the bootstrap; do not assign it inside Windows."
}
}
function Register-ResumeTask {
param([Parameter(Mandatory)][string]$ScriptPath)
@@ -310,6 +395,8 @@ if ($Resume -or (-not $ServerIPv4Address -and $existingState)) {
$PrefixLength = [int]$existingState.PrefixLength
$NetworkInterfaceAlias = [string]$existingState.NetworkInterfaceAlias
$DefaultGateway = if ($existingState.DefaultGateway) { [ipaddress][string]$existingState.DefaultGateway } else { $null }
$NetworkConfigurationMode = if ($existingState.NetworkConfigurationMode) { [string]$existingState.NetworkConfigurationMode } else { 'GuestStatic' }
$TrustedClientNetworks = if ($existingState.TrustedClientNetworks) { @($existingState.TrustedClientNetworks | ForEach-Object { [string]$_ }) } else { @() }
$DnsForwarders = @($existingState.DnsForwarders | ForEach-Object { [ipaddress][string]$_ })
$DomainName = [string]$existingState.DomainName
$DomainNetbios = [string]$existingState.DomainNetbios
@@ -321,6 +408,16 @@ if ($Resume -or (-not $ServerIPv4Address -and $existingState)) {
if (-not $ServerIPv4Address) {
$ServerIPv4Address = [ipaddress](Read-Host 'Fixed IPv4 address for this domain controller')
}
if (-not (Test-PrivateIPv4Address -Address $ServerIPv4Address)) {
throw 'ServerIPv4Address must be the private address of the domain controller. An Azure public IP is never assigned to AD or published in domain DNS.'
}
$domainSubnet = ConvertTo-NetworkCidr -Address $ServerIPv4Address `
-NetworkPrefixLength $PrefixLength
$TrustedClientNetworks = @($TrustedClientNetworks |
ForEach-Object { ConvertTo-PrivateNetworkCidr -Cidr $_ } |
Where-Object { $_ -ne $domainSubnet } |
Select-Object -Unique)
$allowedRemoteAddresses = @($domainSubnet) + $TrustedClientNetworks
$sourceRoot = $PSScriptRoot
if (-not $Resume) {
@@ -383,6 +480,8 @@ if (-not $existingState) {
PrefixLength = $PrefixLength
NetworkInterfaceAlias = $NetworkInterfaceAlias
DefaultGateway = if ($DefaultGateway) { $DefaultGateway.IPAddressToString } else { $null }
NetworkConfigurationMode = $NetworkConfigurationMode
TrustedClientNetworks = $TrustedClientNetworks
DnsForwarders = @($DnsForwarders | ForEach-Object IPAddressToString)
DomainName = $DomainName
DomainNetbios = $DomainNetbios
@@ -396,9 +495,16 @@ if (-not $existingState) {
[Text.UTF8Encoding]::new($false))
}
Write-BootstrapLog "Configuring $NetworkInterfaceAlias as $ServerIPv4Address/$PrefixLength."
Set-StaticDomainAddress -InterfaceAlias $NetworkInterfaceAlias `
-Address $ServerIPv4Address -NetworkPrefixLength $PrefixLength -Gateway $DefaultGateway
if ($NetworkConfigurationMode -eq 'PlatformManaged') {
Write-BootstrapLog "Validating platform-managed address $ServerIPv4Address/$PrefixLength on $NetworkInterfaceAlias without changing DHCP, routes, or the Azure NIC."
Assert-PlatformManagedDomainAddress -InterfaceAlias $NetworkInterfaceAlias `
-Address $ServerIPv4Address -NetworkPrefixLength $PrefixLength
}
else {
Write-BootstrapLog "Configuring $NetworkInterfaceAlias as $ServerIPv4Address/$PrefixLength."
Set-StaticDomainAddress -InterfaceAlias $NetworkInterfaceAlias `
-Address $ServerIPv4Address -NetworkPrefixLength $PrefixLength -Gateway $DefaultGateway
}
$computer = Get-CimInstance Win32_ComputerSystem
if (-not $computer.PartOfDomain) {
@@ -472,8 +578,17 @@ Wait-ActiveDirectoryReady -ExpectedBaseDn $baseDn
$domainProfile = Get-NetConnectionProfile -InterfaceAlias $NetworkInterfaceAlias `
-ErrorAction SilentlyContinue
if (-not $domainProfile -or $domainProfile.NetworkCategory -ne 'DomainAuthenticated') {
Write-BootstrapLog "Refreshing $NetworkInterfaceAlias so Windows detects the domain network profile."
Restart-NetAdapter -Name $NetworkInterfaceAlias -Confirm:$false
if ($NetworkConfigurationMode -eq 'GuestStatic') {
Write-BootstrapLog "Refreshing $NetworkInterfaceAlias so Windows detects the domain network profile."
Restart-NetAdapter -Name $NetworkInterfaceAlias -Confirm:$false
}
else {
# Restarting an Azure NIC from inside the guest can sever the only
# management path. Refresh NLA instead; this does not change the
# platform-managed address, DHCP lease, route, or link state.
Write-BootstrapLog 'Refreshing Network Location Awareness without restarting the Azure adapter.'
Restart-Service NlaSvc -Force -ErrorAction SilentlyContinue
}
for ($attempt = 1; $attempt -le 15; $attempt++) {
Start-Sleep -Seconds 2
$domainProfile = Get-NetConnectionProfile -InterfaceAlias $NetworkInterfaceAlias `
@@ -577,7 +692,7 @@ if (Test-Path -LiteralPath $brokerConfigurationPath -PathType Leaf) {
-RemoteDesktopGroupDn $remoteDesktopGroup.DistinguishedName `
-DefaultCompany 'La Salle' `
-FirewallLocalAddress $ServerIPv4Address `
-FirewallRemoteAddress "$($ServerIPv4Address.IPAddressToString)/$PrefixLength" `
-FirewallRemoteAddress $allowedRemoteAddresses `
-CreateMissingOus `
-DisableCertificateRevocationCheckForLab | Out-Null
@@ -591,9 +706,8 @@ foreach ($hostRecord in $hostRecords) {
}
}
$privateSubnet = "$($ServerIPv4Address.IPAddressToString)/$PrefixLength"
& (Join-Path $scriptsRoot 'Enable-SguServerRemoteManagement.ps1') `
-AllowedRemoteAddress $privateSubnet | Out-Null
-AllowedRemoteAddress $allowedRemoteAddresses | Out-Null
$contentPath = Join-Path $bootstrapRoot 'payload\server-content\Packages'
if (Test-Path -LiteralPath $contentPath -PathType Container) {
@@ -613,7 +727,7 @@ if (-not $packageFirewallRule) {
-Protocol TCP `
-LocalPort 445 `
-LocalAddress $ServerIPv4Address.IPAddressToString `
-RemoteAddress $privateSubnet `
-RemoteAddress $allowedRemoteAddresses `
-Profile Any | Out-Null
}
else {
@@ -621,7 +735,7 @@ else {
$packageFirewallRule | Get-NetFirewallAddressFilter |
Set-NetFirewallAddressFilter `
-LocalAddress $ServerIPv4Address.IPAddressToString `
-RemoteAddress $privateSubnet | Out-Null
-RemoteAddress $allowedRemoteAddresses | Out-Null
}
$collectorFqdn = "$env:COMPUTERNAME.$DomainName"
@@ -642,7 +756,7 @@ $userPolicyParameters = @{
$rustDeskServer = & (Join-Path $scriptsRoot 'Install-SguRustDeskServer.ps1') `
-ServerAddress $rustDeskDnsName `
-FirewallRemoteAddress $privateSubnet
-FirewallRemoteAddress $allowedRemoteAddresses
$rustDeskManagementRoot = Join-Path $env:ProgramData 'SGU\RustDesk'
New-Item -ItemType Directory -Path $rustDeskManagementRoot -Force | Out-Null
foreach ($scriptName in @(
@@ -680,6 +794,9 @@ $validation = [ordered]@{
ComputerName = $env:COMPUTERNAME
DomainName = $DomainName
ServerIPv4Address = $ServerIPv4Address.IPAddressToString
NetworkConfigurationMode = $NetworkConfigurationMode
TrustedClientNetworks = $TrustedClientNetworks
AllowedRemoteAddresses = $allowedRemoteAddresses
BrokerDnsName = $brokerDnsName
BrokerCertificateThumbprint = $serverCertificate.Thumbprint
BrokerService = (Get-Service SGUAuthBroker).Status.ToString()
@@ -714,7 +831,8 @@ if ($validation.BrokerService -ne 'Running' -or
-not $validation.RustDeskHbbsListening -or
-not $validation.RustDeskHbbrListening -or
$validation.EventCollector -ne 'Running' -or
-not $validation.EventSubscription) {
-not $validation.EventSubscription -or
$validation.DomainNetworkProfile -ne 'DomainAuthenticated') {
throw 'Server finalization did not pass service validation. Review bootstrap.log and re-run the bootstrap.'
}