Chapter 7: AD domain
WARNING: THIS IS AN UNFINISHED CHAPTER, AND THE WINDOWS EVENT FORWARDING STUFF DOES NOT YET WORK. CURRENTLY, THIS CHAPTER MAY FAIL TO DEPLOY ENTIRELY.
Starting with the network from Chapter 6, add a domain controller.
Last tested: NEVER
On this page
Adding a domain controller
Our domain controller lets us scale the lab more easily. Now that we have one, we can use it to configure user accounts and assign DHCP leases, so adding a new machine to the network is very easy.
Creating AD user accounts
Creating the accounts themselves are very easy.
One potentially surprising behavior in the configuration as we’ve written it here is that
it uses the local admin password for the password of the user1
user also.
Of course, you could pass in a different password instead,
but for a disposable lab like this one,
using the same password is unlikely to cause any security problems.
Once the account is created, it can be used to log on to any machine in the domain,
and since the user1
account is a member of both Domain Admins
and Enterprise Admins
,
that account will have administrative privileges on all VMs in the domain and over the domain itself.
Use DHCP for clients, and static IP addresses for servers
You may notice that we wrap the networking configuration in an if statement:
node $AllNodes.Where({$_.Role -NotIn 'EDGE'}).NodeName {
if (-not [System.String]::IsNullOrEmpty($node.IPAddress)) {
xIPAddress 'PrimaryIPAddress' {
# ... snip ...
This lets us apply that node
block to all machines on the network except the gateway,
but only configure manual networking if a static IP address was defined in the configuration data.
(If networking is not configured manually, Windows will use DHCP to try to obtain a configuration.)
Windows event forwarding
Now that we have a domain, we can easily enable Windows event forwarding. This can be very helpful when debugging problems with labs consisting of multiple VMs, because (assuming the event forwarding configuration gets applied) you should only need to log on to the VM where the events are being forwarded in order to see the logs from any other VM.
If you are in the target audience for this tutorial, you probably know that there are dozens of logging solutions available. We choose WEF in this chapter because it is agentless and supported out of the box. In fact, it’s supported all the way back to Windows XP SP2 / Server 2003 SP1.
TODO: Finish this section
Adding event source subscriptions
See the Query
setting in the xWEFSubscription
DSC resource in our configuration.
By default, that looks like this:
Query = @(
'Application:*'
'System:*'
'Microsoft-Windows-Desired State Configuration-Admin:*'
'Microsoft-Windows-Desired State Configuration-Operational:*'
)
These go in to the Windows event subscription XML something like:
<Select Path="Application">*</Select>
<Select Path="System">*</Select>
<Select Path="Microsoft-Windows-Desired State Configuration-Admin">*</Select>
<Select Path="Microsoft-Windows-Desired State Configuration-Operational">*</Select>
One thing that may not be obvious is that events can actually be filtered from these sources -
for instance, by replacing the *
with *[System[EventId=2]]
,
you can ignore all events with an EventId other than 2.
It may be useful to see examples from other organizations.
- Event Forwarding Guidance from NSA - a long list of possible subscriptions, especially useful for security engineering and analytics.
Configuring servers to push their events to the collector
TODO: Write this section
See also
- The Windows Event Forwarding Survival Guide - A quick overview article
- Quick and Dirty Large Scale Eventing for Windows - another quick overview
- How to set event log security locally or by using Group Policy - I believe this will help define the registry keys that the group policy objects create for you (and we can’t use GPO in DSC because there’s no way to save GPO objects or import them into a new domain, you have to use the GUI).
- Windows Event Forwarding to a workgroup collector server - This shows how to forward Windows events between non-domain-joined servers
- xWindowsEventForwarding help: ReadMe.Md, MSFT_xWEFSubscription.psm1
Resource ordering
As we have discussed in Chapter 3, when writing the DSC configuration, you can minimize confusing errors by paying careful attention to ordering.
This chapter gives us a good place to illustrate this. Our Configure.ADLAB.ps1 script has these sections:
TODO: don’t forget to update when I add WEF
node $AllNodes.Where({$true}).NodeName { ... }
: LCM setup and ICMP ECHO firewall rulesnode $AllNodes.Where({$_.Role -in 'EDGE'}).NodeName { ... }
: Networking for the gateway servernode $AllNodes.Where({$_.Role -NotIn 'EDGE'}).NodeName { ... }
: Networking for all other serversnode $AllNodes.Where({$_.Role -in 'DC'}).NodeName { ... }
: Creating the AD domain on the domain controllernode $AllNodes.Where({$_.Role -NotIn 'DC'}).NodeName { ... }
: Joining all other servers to the AD domainnode $Allnodes.Where({'Firefox' -in $_.Lability_Resource}).NodeName { ... }
: Installing Firefox
Note how the new, complicated functionality of creating and joining the AD domain is not configured until basic networking is configured. Keeping networking as early in the configuration as possible, and certainly before new, untested functionality, ensures you will be able to log in via PS Remoting if something were to go wrong with the new functionality in the configuration.
Lab exercises and files
-
Add more users via active directory
-
Change the
CORPNET
Hyper-V switch to “internal” instead of “private”.Redeploy, then see if you notice any network problems from your host. What problems are you seeing? Why are they manifesting?
(Once finished, delete the
CORPNET
internal Hyper-V switch, and any problems you were seeing should dissipate.) -
Log on to the domain controller and view Windows events forwarded from the other machines.
-
Collect more logs, perhaps WinRM logs from
Applications and Services Logs\Microsoft\Windows\Windows Remote Management
, then redeploy the lab, log on to the domain controller, and view the newly forwarded events. -
Advanced/bonus exercise: Follow the Microsoft Advanced Threat Analytics deploy instructions to deploy MS ATA to your lab using the GUI. MS ATA uses Windows Event Forwarding and is a good real-world use case for this functionality.
Follow-up bonus: automate the installation by adding an ATA Lability resource and install ATA using Powershell DSC.
ConfigurationData.ADLAB.psd1
@{
AllNodes = @(
@{
NodeName = '*';
InterfaceAlias = 'Ethernet';
AddressFamily = 'IPv4';
DnsConnectionSuffix = 'adlab.invalid';
DnsServerAddress = '10.0.0.1';
DefaultGateway = '10.0.0.2';
DomainName = 'adlab.invalid';
PSDscAllowPlainTextPassword = $true;
PSDscAllowDomainUser = $true; # Removes 'It is not recommended to use domain credential for node X' messages
Lability_SwitchName = 'ADLAB-CORPNET';
Lability_ProcessorCount = 1;
Lability_StartupMemory = 2GB;
Lability_Media = "2016_x64_Standard_EN_Eval";
Lability_Timezone = "Central Standard Time";
}
@{
NodeName = 'ADLAB-DC1';
IPAddress = '10.0.0.1/24';
DnsServerAddress = '127.0.0.1';
Role = 'DC';
}
@{
NodeName = 'ADLAB-EDGE1';
Role = 'EDGE'
IPAddress = '10.0.0.2/24';
Lability_MACAddress = @('00-15-5d-cf-01-01', '00-15-5d-cf-01-02')
Lability_SwitchName = @('Wifi-HyperV-VSwitch', 'ADLAB-CORPNET')
InterfaceAlias = @('Public', 'ADLAB-CORPNET')
}
@{
NodeName = 'ADLAB-CLIENT1';
Role = 'CLIENT';
Lability_Media = 'WIN10_x64_Enterprise_EN_Eval';
Lability_Resource = @(
'Firefox'
)
PSDscAllowPlainTextPassword = $true;
}
)
NonNodeData = @{
Lability = @{
Media = @()
Network = @(
# Use a *private* switch, not an internal one,
# so that our Hyper-V host doesn't get a NIC w/ DHCP lease on the corporate network,
# which can cause networking problems on the host.
@{ Name = 'ADLAB-CORPNET'; Type = 'Private'; }
# The Wifi-HyperV-VSwitch is already defined on my machine - do not manage it here
# If that switch does not exist on your machine, you should define an External switch and set its name here
# @{ Name = 'Wifi-HyperV-VSwitch'; Type = 'External'; NetAdapterName = 'WiFi'; AllowManagementOS = $true; }
)
DSCResource = @(
@{ Name = 'xActiveDirectory'; RequiredVersion = '2.17.0.0'; }
@{ Name = 'xComputerManagement'; RequiredVersion = '4.1.0.0'; }
@{ Name = 'xDhcpServer'; RequiredVersion = '1.6.0.0'; }
@{ Name = 'xDnsServer'; RequiredVersion = '1.7.0.0'; }
@{ Name = 'xNetworking'; RequiredVersion = '5.7.0.0'; }
@{ Name = 'xSmbShare'; RequiredVersion = '2.0.0.0'; }
@{ Name = 'xWindowsEventForwarding'; RequiredVersion = '1.0.0.0'; }
)
Resource = @(
@{
Id = 'Firefox'
Filename = 'Firefox-Latest.exe'
Uri = 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=en-US'
}
)
}
}
}
Configure.ADLAB.ps1
Configuration AdLabConfig {
param (
[Parameter()] [ValidateNotNull()] [PSCredential] $Credential = (Get-Credential -Credential 'Administrator')
)
Import-DscResource -Module PSDesiredStateConfiguration
Import-DscResource -Module xActiveDirectory -ModuleVersion 2.17.0.0
Import-DscResource -Module xComputerManagement -ModuleVersion 4.1.0.0
Import-DscResource -Module xDHCPServer -ModuleVersion 1.6.0.0
Import-DscResource -Module xDnsServer -ModuleVersion 1.7.0.0
Import-DscResource -Module xNetworking -ModuleVersion 5.7.0.0
Import-DscResource -Module xSmbShare -ModuleVersion 2.0.0.0
Import-DscResource -Module xWindowsEventForwarding -ModuleVersion 1.0.0.0
# Common configuration for all nodes
node $AllNodes.Where({$true}).NodeName {
LocalConfigurationManager {
RebootNodeIfNeeded = $true;
AllowModuleOverwrite = $true;
ConfigurationMode = 'ApplyOnly';
CertificateID = $node.Thumbprint;
}
xFirewall 'FPS-ICMP4-ERQ-In' {
Name = 'FPS-ICMP4-ERQ-In';
DisplayName = 'File and Printer Sharing (Echo Request - ICMPv4-In)';
Description = 'Echo request messages are sent as ping requests to other nodes.';
Direction = 'Inbound';
Action = 'Allow';
Enabled = 'True';
Profile = 'Any';
}
xFirewall 'FPS-ICMP6-ERQ-In' {
Name = 'FPS-ICMP6-ERQ-In';
DisplayName = 'File and Printer Sharing (Echo Request - ICMPv6-In)';
Description = 'Echo request messages are sent as ping requests to other nodes.';
Direction = 'Inbound';
Action = 'Allow';
Enabled = 'True';
Profile = 'Any';
}
}
node $AllNodes.Where({$_.Role -in 'EDGE'}).NodeName {
xNetAdapterName "RenamePublicAdapter" {
NewName = $node.InterfaceAlias[0];
MacAddress = $node.Lability_MACAddress[0];
}
# Do not specify an IP address for the public adapter so that it gets one via DHCP
xNetAdapterName "RenameCorpnetAdapter" {
NewName = $node.InterfaceAlias[1];
MacAddress = $node.Lability_MACAddress[1];
}
xIPAddress 'CorpnetIPAddress' {
IPAddress = $node.IPAddress;
InterfaceAlias = $node.InterfaceAlias[1];
AddressFamily = $node.AddressFamily;
DependsOn = '[xNetAdapterName]RenameCorpnetAdapter';
}
xDnsServerAddress 'CorpnetDNSClient' {
Address = $node.DnsServerAddress;
InterfaceAlias = $node.InterfaceAlias[1];
AddressFamily = $node.AddressFamily;
DependsOn = '[xIPAddress]CorpnetIPAddress';
}
xDnsConnectionSuffix 'CorpnetConnectionSuffix' {
InterfaceAlias = $node.InterfaceAlias[1];
ConnectionSpecificSuffix = $node.DnsConnectionSuffix;
DependsOn = '[xIPAddress]CorpnetIPAddress';
}
Script "NewNetNat" {
GetScript = { return @{ Result = "" } }
TestScript = {
try {
Get-NetNat -Name NATNetwork -ErrorAction Stop | Out-Null
return $true
} catch {
return $false
}
}
SetScript = {
New-NetNat -Name NATNetwork -InternalIPInterfaceAddressPrefix "10.0.0.0/24"
}
PsDscRunAsCredential = $Credential
DependsOn = '[xIPAddress]CorpnetIPAddress';
}
}
node $AllNodes.Where({$_.Role -NotIn 'EDGE'}).NodeName {
if (-not [System.String]::IsNullOrEmpty($node.IPAddress)) {
xIPAddress 'PrimaryIPAddress' {
IPAddress = $node.IPAddress
InterfaceAlias = $node.InterfaceAlias
AddressFamily = $node.AddressFamily
}
if (-not [System.String]::IsNullOrEmpty($node.DnsServerAddress)) {
xDnsServerAddress 'PrimaryDNSClient' {
Address = $node.DnsServerAddress;
InterfaceAlias = $node.InterfaceAlias;
AddressFamily = $node.AddressFamily;
DependsOn = '[xIPAddress]PrimaryIPAddress';
}
}
if (-not [System.String]::IsNullOrEmpty($node.DnsConnectionSuffix)) {
xDnsConnectionSuffix 'PrimaryConnectionSuffix' {
InterfaceAlias = $node.InterfaceAlias;
ConnectionSpecificSuffix = $node.DnsConnectionSuffix;
DependsOn = '[xIPAddress]PrimaryIPAddress';
}
}
# Do not set the default gateway for the EDGE server to avoid errors like
# 'New-NetRoute : Instance MSFT_NetRoute already exists'
# When this configuration was part of the .Where({$true}) stanza above,
# I got those errors on EDGE all the time.
xDefaultGatewayAddress 'NonEdgePrimaryDefaultGateway' {
InterfaceAlias = $node.InterfaceAlias;
Address = $node.DefaultGateway;
AddressFamily = $node.AddressFamily;
DependsOn = '[xIPAddress]PrimaryIPAddress';
}
} #end if IPAddress
}
# Configure the AD domain
node $AllNodes.Where({$_.Role -in 'DC'}).NodeName {
xComputer 'Hostname' {
Name = $node.NodeName;
}
## Hack to fix DependsOn with hyphens "bug" :(
foreach ($feature in @(
'AD-Domain-Services',
'GPMC',
'RSAT-AD-Tools',
'DHCP',
'RSAT-DHCP'
)) {
WindowsFeature $feature.Replace('-','') {
Ensure = 'Present';
Name = $feature;
IncludeAllSubFeature = $true;
}
}
xADDomain 'ADDomain' {
DomainName = $node.DomainName;
SafemodeAdministratorPassword = $Credential;
DomainAdministratorCredential = $Credential;
DependsOn = '[WindowsFeature]ADDomainServices';
}
xDhcpServerAuthorization 'DhcpServerAuthorization' {
Ensure = 'Present';
DependsOn = '[WindowsFeature]DHCP','[xADDomain]ADDomain';
}
xDhcpServerScope 'DhcpScope10_0_0_0' {
Name = 'Corpnet';
IPStartRange = '10.0.0.100';
IPEndRange = '10.0.0.200';
SubnetMask = '255.255.255.0';
LeaseDuration = '00:08:00';
State = 'Active';
AddressFamily = 'IPv4';
DependsOn = '[WindowsFeature]DHCP';
}
xDhcpServerOption 'DhcpScope10_0_0_0_Option' {
ScopeID = '10.0.0.0';
DnsDomain = 'corp.contoso.com';
DnsServerIPAddress = '10.0.0.1';
Router = '10.0.0.2';
AddressFamily = 'IPv4';
DependsOn = '[xDhcpServerScope]DhcpScope10_0_0_0';
}
xADUser User1 {
DomainName = $node.DomainName;
UserName = 'User1';
Description = 'Lability Test Lab user';
Password = $Credential;
Ensure = 'Present';
DependsOn = '[xADDomain]ADDomain';
}
xADGroup DomainAdmins {
GroupName = 'Domain Admins';
MembersToInclude = 'User1';
DependsOn = '[xADUser]User1';
}
xADGroup EnterpriseAdmins {
GroupName = 'Enterprise Admins';
GroupScope = 'Universal';
MembersToInclude = 'User1';
DependsOn = '[xADUser]User1';
}
}
node $AllNodes.Where({$_.Role -NotIn 'DC'}).NodeName {
# Use user@domain for the domain joining credential
$upn = "$($Credential.UserName)@$($node.DomainName)"
$domainCred = New-Object -TypeName PSCredential -ArgumentList ($upn, $Credential.Password);
xComputer 'DomainMembership' {
Name = $node.NodeName;
DomainName = $node.DomainName;
Credential = $domainCred
}
}
# Configure Windows Event Forwarding on all source machines
node $AllNodes.Where({$_.Role -NotIn 'DC'}).NodeName {
# 1. Get the computer account for the WEF Collector
# 2. Add that account to the local "Event Log Readers" group on each other server
}
# Configure Windows Event Forwarding
node $AllNodes.Where({$_.Role -in 'DC'}).NodeName {
xWEFCollector "CreateWefCollector" {
Ensure = "Present"
Name = "UniqueIgnoredNameLolWhatever"
}
xWEFSubscription "WebSubscription" {
SubscriptionId = "AdLabSub"
Ensure = "Present"
SubscriptionType = "CollectorInitiated"
DeliveryMode = "Push"
ReadExistingEvents = $true
# Create a list of FQDNs like 'dc1.adlab.invalid'
Address = $configData.AllNodes.Where({$_.NodeName -ne '*'}).NodeName | Foreach-Object -Proces { "$_.$node.DomainName" }
# Which event logs to request to be forwarded
Query = @(
'Application:*'
'System:*'
'Microsoft-Windows-Desired State Configuration-Admin:*'
'Microsoft-Windows-Desired State Configuration-Operational:*'
)
DependsOn = "[xWEFCollector]CreateWefCollector"
}
}
node $Allnodes.Where({'Firefox' -in $_.Lability_Resource}).NodeName {
Script "InstallFirefox" {
GetScript = { return @{ Result = "" } }
TestScript = {
Test-Path -Path "C:\Program Files\Mozilla Firefox"
}
SetScript = {
$process = Start-Process -FilePath "C:\Resources\Firefox-Latest.exe" -Wait -PassThru -ArgumentList @('-ms')
if ($process.ExitCode -ne 0) {
throw "Firefox installer at $ffInstaller exited with code $($process.ExitCode)"
}
}
PsDscRunAsCredential = $Credential
}
}
} #end Configuration Example
Deploy-ADLAB.ps1
[CmdletBinding()] Param(
[SecureString] $AdminPassword = (Read-Host -AsSecureString -Prompt "Admin password"),
[string] $ConfigurationData = (Join-Path -Path $PSScriptRoot -ChildPath ConfigurationData.ADLAB.psd1),
[string] $ConfigureScript = (Join-Path -Path $PSScriptRoot -ChildPath Configure.ADLAB.ps1),
[string] $DscConfigName = "AdLabConfig",
[switch] $IgnorePendingReboot
)
$ErrorActionPreference = "Stop"
. $ConfigureScript
& $DscConfigName -ConfigurationData $ConfigurationData -OutputPath $env:LabilityConfigurationPath -Verbose
Start-LabConfiguration -ConfigurationData $ConfigurationData -Path $env:LabilityConfigurationPath -Verbose -Password $AdminPassword -IgnorePendingReboot:$IgnorePendingReboot
Start-Lab -ConfigurationData $ConfigurationData -Verbose