Windows Domain Controller & AD CS
This runbook covers deployment of Windows Server 2022 as a Domain Controller with Active Directory Certificate Services (AD CS) for the Domus Digitalis home lab.
|
Related Documentation
|
1. Architecture
| Component | Configuration |
|---|---|
Hostname |
home-dc01 |
IP Address |
10.50.1.50 |
Domain |
inside.domusdigitalis.dev |
NetBIOS |
INSIDE |
CA Name |
HOME-ROOT-CA |
Certificate Template |
Linux-Workstation-Auth |
2. VM Deployment
3. Post-Installation Configuration
3.1. Network Configuration
Set static IP:
New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 10.50.1.50 -PrefixLength 24 -DefaultGateway 10.50.1.1
Set DNS (pfSense initially):
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.50.1.1
Rename and restart:
Rename-Computer -NewName "home-dc01" -Restart
3.2. SSH Access with YubiKey (FIDO2)
Windows Server 2022 ships with OpenSSH 8.1, which doesn’t support sk-ssh-ed25519 (FIDO2). Upgrade to v9.5+ for YubiKey support.
3.2.1. Upgrade OpenSSH
Download OpenSSH:
Invoke-WebRequest -Uri "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.5.0.0p1-Beta/OpenSSH-Win64-v9.5.0.0.msi" -OutFile "$env:TEMP\OpenSSH.msi"
Stop SSH and install:
Stop-Service sshd -Force
msiexec /i "$env:TEMP\OpenSSH.msi" /qn
Start service:
Start-Service sshd
3.2.2. Configure Authorized Keys
Create authorized_keys file:
$keys = @"
sk-ssh-ed25519@openssh.com AAAA... user@yubikey-primary
sk-ssh-ed25519@openssh.com AAAA... user@yubikey-secondary
ssh-ed25519 AAAA... user@fallback
"@
$keys | Out-File -Encoding UTF8 C:\ProgramData\ssh\administrators_authorized_keys
Set permissions:
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
3.2.3. Windows Firewall
Create SSH rule:
New-NetFirewallRule -DisplayName "OpenSSH Server" -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow
Enable firewall:
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
3.2.4. Make SSH Persistent After Reboot
Create startup script:
$script = @'
Start-Service sshd -ErrorAction SilentlyContinue
Set-Service -Name sshd -StartupType Automatic
if (-not (Get-NetFirewallRule -DisplayName "OpenSSH Server" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -DisplayName "OpenSSH Server" -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow
}
'@
$script | Out-File -Encoding UTF8 "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\ssh-startup.ps1"
Set sshd to auto-start:
Set-Service -Name sshd -StartupType Automatic
3.2.5. SSH Client Configuration (Linux)
Add to ~/.ssh/config:
Host home-dc01
HostName 10.50.1.50
User Administrator
IdentityFile ~/.ssh/id_ed25519_sk_rk_primary
IdentityFile ~/.ssh/id_ed25519_sk_rk_secondary
IdentityFile ~/.ssh/id_ed25519_fallback
PreferredAuthentications publickey,password
ControlMaster no
4. Active Directory Domain Services
4.2. Promote to Domain Controller
Multi-line version:
$SafePassword = ConvertTo-SecureString "<DSRM-PASSWORD>" -AsPlainText -Force
Install-ADDSForest `
-DomainName "inside.domusdigitalis.dev" `
-DomainNetBIOSName "INSIDE" `
-ForestMode "WinThreshold" `
-DomainMode "WinThreshold" `
-InstallDns `
-SafeModeAdministratorPassword $SafePassword `
-Force
SSH-friendly single line:
$SafePassword = ConvertTo-SecureString "<DSRM-PASSWORD>" -AsPlainText -Force; Install-ADDSForest -DomainName "inside.domusdigitalis.dev" -DomainNetBIOSName "INSIDE" -ForestMode "WinThreshold" -DomainMode "WinThreshold" -InstallDns -SafeModeAdministratorPassword $SafePassword -Force
|
After promotion, the server restarts. Log in as |
Update DNS to point to self:
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.50.1.50,10.50.1.1
4.3. Create Service Accounts and Groups
Create ISE service account:
$IsePassword = ConvertTo-SecureString "<ISE-SVC-PASSWORD>" -AsPlainText -Force
New-ADUser -Name "svc-ise" `
-SamAccountName "svc-ise" `
-UserPrincipalName "svc-ise@inside.domusdigitalis.dev" `
-AccountPassword $IsePassword `
-Enabled $true `
-PasswordNeverExpires $true `
-Description "ISE AD Join Service Account"
Create security group:
New-ADGroup -Name "Linux-Cert-Enrollers" -GroupScope DomainLocal -Description "Authorized to enroll Linux workstation certificates"
Add administrator to group:
Add-ADGroupMember -Identity "Linux-Cert-Enrollers" -Members "Administrator"
5. Active Directory Certificate Services
5.1. Install AD CS Role
Install-WindowsFeature -Name ADCS-Cert-Authority,ADCS-Web-Enrollment -IncludeManagementTools
Verify installation:
Get-WindowsFeature ADCS* | Where-Object InstallState -eq 'Installed'
5.2. Configure Enterprise Root CA
Install-AdcsCertificationAuthority `
-CAType EnterpriseRootCA `
-CACommonName "HOME-ROOT-CA" `
-KeyLength 4096 `
-HashAlgorithmName SHA256 `
-ValidityPeriod Years `
-ValidityPeriodUnits 10 `
-Force
Configure Web Enrollment:
Install-AdcsWebEnrollment -Force
Verify CA:
Get-Service certsvc
certutil -CAInfo
5.3. Export Root CA Certificate
Create certs directory:
New-Item -Path "C:\Certs" -ItemType Directory -Force
Get and export certificate:
$cert = (Get-ChildItem -Path Cert:\LocalMachine\CA | Where-Object { $_.Subject -like "*HOME-ROOT-CA*" })[0]
Export-Certificate -Cert $cert -FilePath C:\Certs\HOME-ROOT-CA.cer -Type CERT
Convert to PEM:
certutil -encode C:\Certs\HOME-ROOT-CA.cer C:\Certs\HOME-ROOT-CA.pem
Verify:
Get-ChildItem C:\Certs
6. Linux Workstation Certificate Template
|
The "Supply in Request" option is a known AD CS vulnerability (ESC1) if not properly secured. Restrict enrollment to the |
6.1. Create Template (GUI)
-
Open Certificate Templates Console:
certtmpl.msc -
Right-click Workstation Authentication > Duplicate Template
-
Configure:
| Setting | Value |
|---|---|
General: Template Name |
Linux-Workstation-Auth |
General: Validity |
1 year |
General: Renewal |
6 weeks |
Subject Name |
Supply in the request |
Extensions: Application Policies |
Client Authentication |
Security |
Remove "Domain Computers" Enroll permission |
Security |
Add "Linux-Cert-Enrollers" with Enroll permission |
6.2. Create Template (PowerShell)
$ConfigContext = (Get-ADRootDSE).configurationNamingContext
$TemplatePath = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigContext"
$NewTemplateCN = "Linux-Workstation-Auth"
$NewTemplateOID = "1.3.6.1.4.1.311.21.8." + [guid]::NewGuid().ToString().Replace("-",".")
$NewTemplate = @{
"Name" = $NewTemplateCN
"DisplayName" = "Linux-Workstation-Auth"
"objectClass" = "pKICertificateTemplate"
"flags" = 131680
"revision" = 100
"msPKI-Cert-Template-OID" = $NewTemplateOID
"msPKI-Certificate-Name-Flag" = 1
"msPKI-Enrollment-Flag" = 0
"msPKI-Minimal-Key-Size" = 2048
"msPKI-Private-Key-Flag" = 16842752
"msPKI-RA-Signature" = 0
"msPKI-Template-Minor-Revision" = 1
"msPKI-Template-Schema-Version" = 2
"pKICriticalExtensions" = "2.5.29.15"
"pKIDefaultCSPs" = "1,Microsoft RSA SChannel Cryptographic Provider"
"pKIDefaultKeySpec" = 1
"pKIExpirationPeriod" = [byte[]](0x00,0x80,0x1A,0x06,0x00,0x00,0x00,0x00)
"pKIExtendedKeyUsage" = "1.3.6.1.5.5.7.3.2"
"pKIKeyUsage" = [byte[]](0xA0,0x00)
"pKIMaxIssuingDepth" = 0
"pKIOverlapPeriod" = [byte[]](0x00,0x80,0xA6,0x0A,0x02,0x00,0x00,0x00)
}
New-ADObject -Name $NewTemplateCN -Type pKICertificateTemplate -Path $TemplatePath -OtherAttributes $NewTemplate
Add template to CA:
Add-CATemplate -Name "Linux-Workstation-Auth" -Force
7. pfSense DNS Configuration
Before ISE can join AD, it must resolve inside.domusdigitalis.dev.
7.1. Automated via netapi
Change directory:
cd ~/atelier/_projects/personal/netapi-tui/02_AUTOMATA/pfsense/python
Load credentials:
eval "$(dsec source d000 dev/network)"
Run script:
uv run python add_dns_domain_override.py
8. Verification Checklist
-
VM created and Windows Server installed
-
Static IP configured (10.50.1.50)
-
SSH with YubiKey working
-
AD DS promoted successfully
-
DNS resolving
inside.domusdigitalis.dev -
AD CS installed and configured
-
ROOT CA exported to
C:\Certs\HOME-ROOT-CA.pem -
Linux-Workstation-Auth template published
-
Service account
svc-isecreated -
Security group
Linux-Cert-Enrollerscreated -
pfSense DNS override configured