Overview

Years ago I had to build some quick-and-dirty Active Directory (AD) infrastructure in a virtualised lab in order to reproduce some cross-domain trusts and Red Hat Identity Manager integration.

This post will cover the basic steps to provision an Active Direcory Domain Controller.

We’ll use Windows 2022 on Vagrant with the Libvirt Provider as our starting point to update the end-to-end process. (Last time I built this was on Windows Server 2016 on VMware.)

Steps

Provision Virtual Machine / Vagrant Box

  1. Start a Windows 2022 Vagrant Box
     [wmcdonald@fedora windows2022 (main ✓)]$ vagrant up
    
  2. Connect to the Vagrant Box and start Powershell
     [wmcdonald@fedora windows2022 (main ✓)]$ vagrant ssh
     vagrant@WIN-Q5TRJJGJS2J C:\Users\vagrant>pwsh
     PS C:\Users\vagrant> 
    

    Note #1: default password: vagrant

    Note #2: This step will be required after any restarts or reconnection.

  3. Rename the VM
     PS C:\Users\vagrant> Rename-Computer -NewName ad01
     WARNING: The changes will take effect after you restart the computer WIN-Q5TRJJGJS2J.
     PS C:\Users\vagrant> Restart-Computer
     PS C:\Users\vagrant> client_loop: send disconnect: Broken pipe
     <... wait a few seconds...>
    
  4. Reconnect, invoke Powershell, verify the hostname change:
     [wmcdonald@fedora windows2022 (main ✓)]$ vagrant ssh
     vagrant@WIN-Q5TRJJGJS2J C:\Users\vagrant>pwsh
     PS C:\Users\vagrant> $Env:COMPUTERNAME
     AD01
    
  5. Configure time sync:
     PS C:\Users\vagrant> w32tm /config /manualpeerlist:pool.ntp.org /syncfromflags:MANUAL
     The command completed successfully.
     PS C:\Users\vagrant> Stop-Service w32time
     PS C:\Users\vagrant> Start-Service w32time
     PS C:\Users\vagrant> w32tm /query /status
     Leap Indicator: 3(not synchronized)
     Stratum: 0 (unspecified)
     Precision: -23 (119.209ns per tick)
     Root Delay: 0.0000000s
     Root Dispersion: 0.0000000s
     ReferenceId: 0x00000000 (unspecified)
     Last Successful Sync Time: unspecified
     Source: Local CMOS Clock
     Poll Interval: 6 (64s)
    

    Note: for a pure Powershell equivalent:

     Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Servers
     Set-ItemProperty . 0 "ca.pool.ntp.org"
     Set-ItemProperty . "(Default)" "0"
     Set-Location HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Parameters
     Set-ItemProperty . NtpServer "ca.pool.ntp.org"
     Set-Location 
     Stop-Service w32time
     Start-Service w32time
    

Configure Active Directory Domain

  1. Create the domain controller
     # Set our domain/subdomain
     # $Domain.Split('.')[0].ToUpper() will return 'NOSTROMO' for the NetBIOS domain name
        
     $Domain = 'nostromo.com'
        
     # Windows PowerShell script for AD DS Deployment
        
     Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
     Import-Module ADDSDeployment
     Install-ADDSForest `
     -CreateDnsDelegation:$false `
     -DatabasePath "C:\Windows\NTDS" `
     -DomainMode "WinThreshold" `
     -DomainName $Domain `
     -DomainNetbiosName $Domain.Split('.')[0].ToUpper() `
     -ForestMode "WinThreshold" `
     -InstallDns:$true `
     -LogPath "C:\Windows\NTDS" `
     -NoRebootOnCompletion:$false `
     -SysvolPath "C:\Windows\SYSVOL" `
     -Force:$true
    
  2. Add a suitably complex password (vagrant will not cut the mustard):
     WARNING: A script or application on the remote computer LOCALHOST is sending a prompt request. When you are prompted, enter sensitive information, such as credentials or passwords, only if you trust the remote computer and the application or script that is requesting the data.
     SafeModeAdministratorPassword: *********************
     WARNING: A script or application on the remote computer LOCALHOST is sending a prompt request. When you are prompted, enter sensitive information, such as credentials or passwords, only if you trust the remote computer and the application or script that is requesting the data.
     Confirm SafeModeAdministratorPassword: *********************
    
  3. Wait for the system to reboot, then reconnect
     [wmcdonald@fedora windows2022 (main ✓)]$ vagrant ssh
     vagrant@192.168.121.218's password: 
    
     Microsoft Windows [Version 10.0.20348.2031]
     (c) Microsoft Corporation. All rights reserved.
    
     home\vagrant@AD01 C:\Users\vagrant>pwsh      
     PowerShell 7.3.8
    
     A new PowerShell stable release is available: v7.4.6 
     Upgrade now, or check out the release page at:       
         https://aka.ms/PowerShell-Release?tag=v7.4.6       
    
     PS C:\Users\vagrant> 
    
  4. Validate running services
     PS C:\Users\vagrant> Get-Service adws,kdc,netlogon,dns
    
     Status   Name               DisplayName
     ------   ----               -----------
     Running  adws               Active Directory Web Services
     Running  dns                DNS Server
     Running  kdc                Kerberos Key Distribution Center
     Running  Netlogon           netlogon
    
  5. Validate DNS (note we’re still on a DHCP IP address at this stage where previously we’d have switched to a fixed assigned address):
    PS C:\Users\vagrant> Get-NetIPAddress -AddressFamily IPv4 | Select-Object InterfaceIndex, InterfaceAlias,  IPAddress
    
    InterfaceIndex InterfaceAlias              IPAddress
    -------------- --------------              ---------
                4 Ethernet Instance 0         192.168.121.218
                1 Loopback Pseudo-Interface 1 127.0.0.1
    
    PS C:\Users\vagrant> Resolve-DnsName ad01
    
    Name                                           Type   TTL   Section    IPAddress
    ----                                           ----   ---   -------    ---------
    ad01.nostromo.com                                 AAAA   1200  Question   fe80::d38b:106c:c73a:21b3
    ad01.nostromo.com                                 A      1200  Question   192.168.121.218
    
    PS C:\Users\vagrant> Resolve-DnsName ad01.nostromo.com
    
    Name                                           Type   TTL   Section    IPAddress
    ----                                           ----   ---   -------    ---------
    ad01.nostromo.com                                 AAAA   1200  Question   fe80::d38b:106c:c73a:21b3
    ad01.nostromo.com                                 A      1200  Question   192.168.121.218
    

Configure Users and Security Groups

Active Directory Security Groups can be used to group domain users with similar roles, departments, organisational responsibilities or to reflect other organisational concerns. Permissions can then be assigned at the group level, reducing the management overhead as users join, change role or department, or leave.

Create domain security groups, domain users and assign those users to the groups.

  1. Create Security Groups

    • Officers
        PS> New-ADGroup -Name "Officers" -SamAccountName Officers -GroupCategory Security -GroupScope Global -DisplayName "Bridge Officers" -Path "CN=Users,DC=Nostromo,DC=Com" -Description "Members of Bridge Officers"
      
    • Engineering
        PS> New-ADGroup -Name "Engineers" -SamAccountName Engineers -GroupCategory Security -GroupScope Global -DisplayName "Engineering Crew" -Path "CN=Users,DC=Nostromo,DC=Com" -Description "Members of Engineering Crew"
      
    • Pest Control
        PS> New-ADGroup -Name "Cats" -SamAccountName Cats -GroupCategory Security -GroupScope Global -DisplayName "Pest Control" -Path "CN=Users,DC=Nostromo,DC=Com" -Description "Members of Pest Control Crew"
      
  2. Create Users

    • Dallas, Officers
        PS> $Attributes = @{
            Enabled = $true
            ChangePasswordAtLogon = $false
            UserPrincipalName = "dallas@nostromo.com"
            Name = "dallas"
            GivenName = "Captain"
            Surname = "Dallas"
            DisplayName = "Captain Dallas"
            Office = "Bridge"
            AccountPassword = "Thatfigures." | ConvertTo-SecureString -AsPlainText -Force
        }
        PS> New-ADUser @Attributes
      
    • Kane & Parker, engineering crew
        PS> $Attributes = @{
            Enabled = $true
            ChangePasswordAtLogon = $false
            UserPrincipalName = "kane@nostromo.com"
            Name = "kane"
            GivenName = "XO"
            Surname = "Kane"
            DisplayName = "XO Kane"
            Office = "Bridge"
            AccountPassword = "Sillyquestion?" | ConvertTo-SecureString -AsPlainText -Force
        }
        PS> New-ADUser @Attributes
              
        PS> $Attributes = @{
            Enabled = $true
            ChangePasswordAtLogon = $false
            UserPrincipalName = "parker@nostromo.com"
            Name = "parker"
            GivenName = "Chief"
            Surname = "Parker"
            DisplayName = "Chief Parker"
            Office = "Engineering"
            AccountPassword = "Howyadoin?" | ConvertTo-SecureString -AsPlainText -Force
        }
        PS> New-ADUser @Attributes
      
    • Jones, Pest Control crew

        PS> $Attributes = @{
            Enabled = $true
            ChangePasswordAtLogon = $false
            UserPrincipalName = "jones@nostromo.com"
            Name = "jones"
            GivenName = "Jones"
            Surname = "the Cat"
            DisplayName = "Jones the Cat"
            Office = "Everywhere"
            AccountPassword = "Tunaplz?" | ConvertTo-SecureString -AsPlainText -Force
        }
        PS> New-ADUser @Attributes
      
  3. Add Users to Security Groups

     PS> Add-ADGroupMember -Identity Officers -Members dallas, kane
     PS> Add-ADGroupMember -Identity Engineers -Members parker
     PS> Add-ADGroupMember -Identity Cats -Members jones
    

Add DNS Records

This is an optional step. If we were delegating a subdomain of the DNS hierarchy to another subsystem. For example these steps would originally have delegated a leaf of the DNS hierarch to Red Hat’s Identity Management infrastructure.

  1. Add delegation for idm.nostromo.com
     PS> Add-DnsServerZoneDelegation -Name "nostromo.com" -ChildZoneName "idm" -NameServer "idm01.idm.nostromo.com" -IPAddress 192.168.0.22 -PassThru -Verbose
    

Cross-forest Trust

Again, optional but if we wanted to establish a Trust from one AD domain to another:

$localforest = [System.DirectoryServices.ActiveDirectory.Forest]::getCurrentForest()
$strRemoteForest = example.cloud
$strRemoteUser = administrator
$strRemotePassword = Read-Host -Prompt Enter $strRemoteUser password for $strRemoteForest
$remoteContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext(Forest, $strRemoteForest,$strRemoteUser,$strRemotePassword)
$remoteForest = [System.DirectoryServices.ActiveDirectory.Forest]::getForest($remoteContext)
$localForest.CreateTrustRelationship($remoteForest,Bidirectional)

Persist

Right now our configuration is limited to a single Vagrant Box. This will persist across reboot of the Vagrant Box until we vagrant destroy but a better mechanism may be to externalise the configuration and apply via a provisioner. This will have the added advantage of allowing multiple controllers for different domains to be spun up, allowing us to test cross-domain and cross-forest trusts.

References