Automate direct domain enrollment across Windows versions

This commit is contained in:
2026-09-11 17:34:19 -06:00
parent 7f8a9eed4e
commit 520b4be955
23 changed files with 1244 additions and 108 deletions
+98 -15
View File
@@ -34,6 +34,9 @@ param domainControllerSubnetPrefix string = '10.77.0.0/24'
@description('Reserved Azure VPN Gateway subnet. Use /27 or larger.')
param gatewaySubnetPrefix string = '10.77.255.0/27'
@description('Deploy the optional Azure Point-to-Site VPN Gateway. Direct public enrollment does not require it.')
param deployVpnGateway bool = true
@description('Static private IP reserved on the Azure NIC for AD DS and DNS.')
param domainControllerPrivateIp string = '10.77.0.4'
@@ -44,7 +47,10 @@ param vpnClientAddressPoolPrefix string = '172.30.0.0/24'
param p2sRootCertificateName string = 'SGU-P2S-Root'
@description('Base64 DER bytes of the trusted P2S root certificate, without PEM markers.')
param p2sRootCertificateData string
param p2sRootCertificateData string = ''
@description('Public IPv4 CIDRs allowed to enroll clients directly without VPN. Leave empty to expose no AD enrollment ports.')
param publicEnrollmentSourceAddressPrefixes array = []
@description('Optional public CIDR allowed to RDP to the VM public IP, for example 203.0.113.10/32. Leave empty to expose no management port.')
param administratorSourceAddressPrefix string = ''
@@ -63,7 +69,7 @@ resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2024-05-0
name: networkSecurityGroupName
location: location
properties: {
securityRules: concat([
securityRules: concat(deployVpnGateway ? [
{
name: 'Allow-SGU-P2S-clients'
properties: {
@@ -78,11 +84,77 @@ resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2024-05-0
description: 'AD, DNS, broker, monitoring, and RustDesk are reachable only through the authenticated P2S address pool.'
}
}
] : [], empty(publicEnrollmentSourceAddressPrefixes) ? [] : [
{
name: 'Allow-Direct-AD-TCP'
properties: {
priority: 110
access: 'Allow'
direction: 'Inbound'
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRanges: [
'53'
'88'
'135'
'389'
'443'
'445'
'464'
'636'
'3268'
'3269'
'21115-21117'
'49152-65535'
]
sourceAddressPrefixes: publicEnrollmentSourceAddressPrefixes
destinationAddressPrefix: domainControllerPrivateIp
description: 'AD, DoH, and RustDesk TCP access for explicitly authorized public enrollment networks.'
}
}
{
name: 'Allow-Direct-AD-UDP'
properties: {
priority: 120
access: 'Allow'
direction: 'Inbound'
protocol: 'Udp'
sourcePortRange: '*'
destinationPortRanges: [
'53'
'88'
'123'
'389'
'464'
'21116'
]
sourceAddressPrefixes: publicEnrollmentSourceAddressPrefixes
destinationAddressPrefix: domainControllerPrivateIp
description: 'AD, time, and RustDesk UDP access for explicitly authorized public enrollment networks.'
}
}
{
name: 'Allow-Direct-SGU-Enrollment-TCP'
properties: {
priority: 130
access: 'Allow'
direction: 'Inbound'
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRanges: [
'5985'
'8443'
]
sourceAddressPrefixes: publicEnrollmentSourceAddressPrefixes
destinationAddressPrefix: domainControllerPrivateIp
description: 'WinRM discovery and SGU broker access for direct enrollment.'
}
}
], empty(administratorSourceAddressPrefix) ? [] : [
{
name: 'Allow-RDP-from-administrator'
properties: {
priority: 110
priority: 140
access: 'Allow'
direction: 'Inbound'
protocol: 'Tcp'
@@ -106,7 +178,7 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = {
virtualNetworkAddressPrefix
]
}
subnets: [
subnets: concat([
{
name: domainControllerSubnetName
properties: {
@@ -116,13 +188,14 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = {
}
}
}
], deployVpnGateway ? [
{
name: gatewaySubnetName
properties: {
addressPrefix: gatewaySubnetPrefix
}
}
]
] : [])
}
}
@@ -139,9 +212,14 @@ resource domainControllerPublicIp 'Microsoft.Network/publicIPAddresses@2024-05-0
}
}
resource gatewayPublicIp 'Microsoft.Network/publicIPAddresses@2024-05-01' = {
resource gatewayPublicIp 'Microsoft.Network/publicIPAddresses@2024-05-01' = if (deployVpnGateway) {
name: gatewayPublicIpName
location: location
zones: [
'1'
'2'
'3'
]
sku: {
name: 'Standard'
}
@@ -223,6 +301,8 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = {
}
osDisk: {
createOption: 'FromImage'
// AD DS requires durable writes; the bootstrap stores NTDS on this disk.
caching: 'None'
managedDisk: {
storageAccountType: 'Premium_LRS'
}
@@ -248,7 +328,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = {
}
}
resource virtualNetworkGateway 'Microsoft.Network/virtualNetworkGateways@2024-05-01' = {
resource virtualNetworkGateway 'Microsoft.Network/virtualNetworkGateways@2024-05-01' = if (deployVpnGateway) {
name: virtualNetworkGatewayName
location: location
properties: {
@@ -271,8 +351,8 @@ resource virtualNetworkGateway 'Microsoft.Network/virtualNetworkGateways@2024-05
}
]
sku: {
name: 'VpnGw1'
tier: 'VpnGw1'
name: 'VpnGw1AZ'
tier: 'VpnGw1AZ'
}
vpnClientConfiguration: {
vpnClientAddressPool: {
@@ -282,7 +362,7 @@ resource virtualNetworkGateway 'Microsoft.Network/virtualNetworkGateways@2024-05
}
vpnClientProtocols: [
'IkeV2'
'SSTP'
'OpenVPN'
]
vpnAuthenticationTypes: [
'Certificate'
@@ -307,17 +387,20 @@ output domainControllerPrivateIp string = domainControllerPrivateIp
output domainControllerPublicIp string = domainControllerPublicIp.properties.ipAddress
output virtualNetworkName string = virtualNetwork.name
output virtualNetworkAddressPrefix string = virtualNetworkAddressPrefix
output vpnGatewayName string = virtualNetworkGateway.name
output vpnGatewayName string = deployVpnGateway ? virtualNetworkGateway.name : ''
output vpnClientAddressPoolPrefix string = vpnClientAddressPoolPrefix
output serverBootstrapArguments array = [
output serverBootstrapArguments array = concat([
'-ServerIPv4Address'
domainControllerPrivateIp
'-PrefixLength'
last(split(domainControllerSubnetPrefix, '/'))
'-NetworkConfigurationMode'
'PlatformManaged'
'-TrustedClientNetworks'
vpnClientAddressPoolPrefix
'-DnsForwarders'
'168.63.129.16'
]
], deployVpnGateway ? [
'-TrustedClientNetworks'
vpnClientAddressPoolPrefix
] : [], empty(publicEnrollmentSourceAddressPrefixes) ? [] : concat([
'-PublicEnrollmentNetworks'
], publicEnrollmentSourceAddressPrefixes))