Prepare Azure P2S domain deployment
This commit is contained in:
@@ -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.'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user