Now that we’ve got a functional Active Directory domain, we can leverage it to provide DNS and DHCP services to the lab network segment.
Creating DNS Records for the Lab
By default, Active Directory creates a DNS zone for its own domain name. In order to manage DNS records for other zones, or to manage reverse DNS lookups, we’ll need to make additional zones. To create forward and reverse DNS zones for the lab network manually, I’d normally run the following in PowerShell:
add-dnsserverprimaryzone -name "blab.renf.ro" -replicationscope forest -passthru add-dnsserverprimaryzome -networkid "192.168.1.0/24" -replicationscope forest -passthru
But for this work, I’ll use another Bolt task instead of running the commands manually.
Creating DNS Zones with Bolt
We’ll replace the above PowerShell commands with a Bolt task named create_dns_zone
.
The contents of create_dns_zone.ps1
are:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$false)] [string] $domainname, [Parameter(Mandatory=$false)] [string] $network ) if ($domainname -ne "" -and $network -ne "") { write-host "Cannot specify both domainname and network" exit 1 } if ($domainname -eq "" -and $network -eq "") { write-host "Cannot omit both domainname and network" exit 1 } try { if ($domainname -ne "") { Add-DnsServerPrimaryZone -Name $domainname -ReplicationScope "Forest" -PassThru } else { Add-DnsServerPrimaryZone -NetworkID $network -ReplicationScope "Forest" -Passthru } } catch { exit 2 }
The contents of create_dns_zone.json
are:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Create DNS zone.", "parameters": { "domainname": { "description": "The DNS domain name.", "type": "Optional[String]" }, "network": { "description": "The network ID (CIDR format).", "type": "Optional[String]" } } }
Run the task to create a second forward DNS zone for the lab hosts, and a reverse lookup zone for the lab network segment:
bolt task run --targets dc renfro::create_dns_zone domainname=blab.renf.ro bolt task run --targets dc renfro::create_dns_zone network=192.168.1.0/24
Creating DNS Records with Bolt
As before, we’ll create a Bolt task for creating DNS records. The most common set of records to create are:
- forward lookup records from a hostname to an IP (A records)
- reverse lookup records from an IP to a hostname (PTR records)
- forward lookup records from a canonical name to a fully-qualified domain name (CNAME records)
A PowerShell script (create_dns_record.ps1
) to create each of these three types of records is:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$false)] [string] $name, [Parameter(Mandatory=$true)] [string] $domain, [Parameter(Mandatory=$false)] [string] $cname, [Parameter(Mandatory=$false)] [string] $ip ) if (($name -ne "" -and $cname -ne "" -and $ip -ne "") -or ($name -ne "" -and $cname -ne "") -or ($name -ne "" -and $ip -ne "")) { # Continue with record creation if ($name -ne "" -and $cname -ne "" -and $ip -ne "") { # Add A and PTR records, add CNAME record Add-DnsServerResourceRecordA -Name $name -IPv4Address $ip -ZoneName $domain -CreatePtr Add-DnsServerResourceRecordCName -Name $cname -HostNameAlias ("{0}.{1}" -f $name, $domain) -ZoneName $domain exit 0 } if ($name -ne "" -and $ip -ne "") { # Add A and PTR records Add-DnsServerResourceRecordA -Name $name -IPv4Address $ip -ZoneName $domain -CreatePtr exit 0 } if ($name -ne "" -and $cname -ne "") { # Add CNAME record Add-DnsServerResourceRecordCName -Name $cname -HostNameAlias ("{0}.{1}" -f $name, $domain) -ZoneName $domain exit 0 } } else { write-host "Must specify: name, cname, ip; name, cname; name, ip" exit 1 }
and the corresponding JSON metadata is:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Create DNS A, PTR, CNAME records.", "parameters": { "name": { "description": "The host name.", "type": "Optional[String]" }, "domain": { "description": "The domain name.", "type": "String" }, "cname": { "description": "The host CNAME (alias).", "type": "Optional[String]" }, "ip": { "description": "The host IP.", "type": "Optional[String]" } } }
This task can be tested with records for the gold server as:
bolt task run --targets=dc renfro::create_dns_record name=puppet domain=blab.renf.ro ip=192.168.1.2 cname=gold
And verified with the commands:
bolt command run "Get-DnsServerResourceRecord -ZoneName blab.renf.ro" --target dc bolt command run "Get-DnsServerResourceRecord -ZoneName 1.168.192.in-addr.arpa" --target dc
both of which should show:
- a CNAME record from hostname ‘gold’ to ‘puppet.blab.renf.ro’
- an A record from hostname ‘puppet’ to ‘192.168.1.3’
- a PTR record from hostname ‘2’ to ‘puppet.blab.renf.ro’
Removing DNS Records with Bolt
The PowerShell script to remove DNS records (remove_dns_record.ps1
) is a bit longer, since it includes logic to find the correct reverse DNS zone without having to specify it explicitly:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$false)] [string] $name, [Parameter(Mandatory=$true)] [string] $domain, [Parameter(Mandatory=$false)] [string] $cname, [Parameter(Mandatory=$false)] [string] $ip ) if (($name -ne "" -and $cname -ne "" -and $ip -ne "") -or ($name -ne "" -and $cname -ne "") -or ($name -ne "" -and $ip -ne "")) { # Continue with record removal # Lots of this lifted from comments at # https://rcmtech.wordpress.com/2014/02/26/get-and-delete-dns-a-and-ptr-records-via-powershell/ # Requires Server 2012R2 or later $DNSARecord = Resolve-DnsName ("{0}.{1}" -f $name, $domain) $DNSPtrRecord = Resolve-DnsName $DNSARecord.IPAddress $DNSReverseZone = (Get-DnsServerZone | ?{$DNSPtrRecord.Name -match $_.ZoneName -and $_.IsDsIntegrated -eq $true}).ZoneName $PtrHostName = $DNSARecord.IPAddress -split "\." [array]::Reverse($PtrHostName) $DNSReverseZoneSuffix = $DNSReverseZone -replace ".in-addr.arpa","" $PtrHostName = $PtrHostName -join "." -replace $DNSReverseZoneSuffix,"" -replace "\.$","" if ($name -ne "" -and $cname -ne "" -and $ip -ne "") { # Remove CNAME record, remove A and PTR records Remove-DnsServerResourceRecord -RRType "CNAME" -Name $cname -ZoneName $domain -Force Remove-DnsServerResourceRecord -RRType "A" -Name $name -RecordData $ip -ZoneName $domain -Force exit 0 } elseif ($name -ne "" -and $ip -ne "") { # Remove A and PTR records Remove-DnsServerResourceRecord -RRType "A" -Name $name -RecordData $ip -ZoneName $domain -Force exit 0 } elseif ($name -ne "" -and $cname -ne "") { # Remove CNAME record Remove-DnsServerResourceRecord -RRType "CNAME" -Name $cname -ZoneName $domain -Force exit 0 } } else { write-host "Must specify: name, cname, ip; name, cname; name, ip" exit 1 }
The metadata JSON (remove_dns_record.json
) looks like the others:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Remove DNS A, PTR, CNAME records.", "parameters": { "name": { "description": "The host name.", "type": "Optional[String]" }, "domain": { "description": "The domain name.", "type": "String" }, "cname": { "description": "The host CNAME (alias).", "type": "Optional[String]" }, "ip": { "description": "The host IP.", "type": "Optional[String]" } } }
Rather than remove any existing DNS records for the domain controller or the gold server, we’ll create a set of new throwaway records, verify them, remove them, and verify their removal.
Creation:
bolt task run --targets=dc renfro::create_dns_record name=foo domain=blab.renf.ro ip=192.168.1.4 cname=bar
Verification:
bolt command run "Get-DnsServerResourceRecord -ZoneName blab.renf.ro" --target dc bolt command run "Get-DnsServerResourceRecord -ZoneName 1.168.192.in-addr.arpa" --target dc
Removal:
bolt task run --targets=192.168.1.3 renfro::remove_dns_record name=foo domain=blab.renf.ro ip=192.168.1.4 cname=bar
Verification:
bolt command run "Get-DnsServerResourceRecord -ZoneName blab.renf.ro" --target dc bolt command run "Get-DnsServerResourceRecord -ZoneName 1.168.192.in-addr.arpa" --target dc
Using Active Directory DNS on the Gold Server
Now that the domain controller’s DNS is managed from the gold server, we can point the gold server’s /etc/resolv.conf
to it instead of pfSense.
By default, CentOS 7 uses NetworkManager to manage /etc/resolv.conf
, so use either nmcli
or nmtui
to change the DNS settings for the default Ethernet interface.
When complete, you should be able to ping both dc1.ad.blab.renf.ro
and gold.blab.renf.ro
.
Setting up Name Resolution from Outside the Lab Network
To resolve the lab DNS entries outside the lab network, you’ll need:
- a firewall rule in pfSense allowing DNS queries to the domain controller on both TCP and UDP port 53, and
- have the home DNS servers forward lab domain queries to the domain controller.
On the home DNS servers running BIND9 on Debian, add:
zone "blab.renf.ro" { type forward; forwarders { 192.168.1.3; }; }; zone "1.168.192.in-addr.arpa" { type forward; forwarders { 192.168.1.3; }; };
to /etc/bind/named.conf.local
, add empty-zones-enable no;
to the options
structure in /etc/bind/named.conf.options
, and restart the BIND service.
Test outside name resolution with:
nslookup gold.blab.renf.ro nslookup 192.168.1.2
Lab Domain DHCP Setup
The manual PowerShell commands to install and configure DHCP equivalent to pfSense would be:
install-windowsfeature dhcp
and:
add-dhcpserverindc -dnsname dc1.blab.renf.ro -ipaddress 192.168.1.3 set-dhcpserverv4dnssetting -dynamicupdates always -deletednsrronleaseexpiry $true add-dhcpserverv4scope -name 'blab.renf.ro' -startrange 192.168.1.1 -endrange 192.168.1.254 -subnetmask 255.255.255.0 -state active add-dhcpserverv4exclusionrange -scopeid 192.168.1.0 -startrange 192.168.1.1 -endrange 192.168.1.99 add-dhcpserverv4exclusionrange -scopeid 192.168.1.0 -startrange 192.168.1.200 -endrange 192.168.1.254 set-dhcpserverv4optionvalue -optionid 3 -value 192.168.1.1 -scopeid 192.168.1.0 set-dhcpserverv4optionvalue -dnsdomain blab.renf.ro -scopeid 192.168.1.0 -dnsserver 192.168.1.3 restart-service dhcpserver
Creating a DHCP Scope with Bolt
As before, we’ll replace those commands with a Bolt task named create_dhcp_server
.
The contents of create_dhcp_server.ps1
are:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$true)] [string] $dhcpserver, [Parameter(Mandatory=$true)] [string] $dhcpserverip, [Parameter(Mandatory=$true)] [string] $scopeid, [Parameter(Mandatory=$true)] [string] $scopename, [Parameter(Mandatory=$true)] [string] $netmask, [Parameter(Mandatory=$true)] [string] $scopestartrange, [Parameter(Mandatory=$true)] [string] $scopeendrange, [Parameter(Mandatory=$true)] [string] $exclusionrange, [Parameter(Mandatory=$true)] [string] $router, [Parameter(Mandatory=$true)] [string] $dnsserverip ) try { install-windowsfeature dhcp } catch { Write-Output "Cannot install dhcp windows feature" exit 1 } try { add-dhcpserverindc -dnsname $dhcpserver -ipaddress $dhcpserverip set-dhcpserverv4dnssetting -dynamicupdates always ` -deletednsrronleaseexpiry $true add-dhcpserverv4scope -name $scopename -startrange $scopestartrange ` -endrange $scopeendrange -subnetmask $netmask -state active foreach ($range in ($exclusionrange -split ",")) { $start = ($range -split "-")[0] $end = ($range -split "-")[1] add-dhcpserverv4exclusionrange -scopeid $scopeid ` -startrange $start -endrange $end } set-dhcpserverv4optionvalue -optionid 3 -value $router -scopeid $scopeid set-dhcpserverv4optionvalue -dnsdomain blab.renf.ro -scopeid $scopeid ` -dnsserver $dnsserverip restart-service dhcpserver } catch { exit 2 }
and the contents of the create_dhcp_scope.json
JSON metadata are:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Create DHCP scope and server.", "parameters": { "dhcpserver": { "description": "The DHCP server name.", "type": "String" }, "dhcpserverip": { "description": "The DHCP server IP address.", "type": "String" }, "scopeid": { "description": "The DHCP scope ID (network segment address).", "type": "String" }, "scopename": { "description": "The DHCP scope name (network segment name).", "type": "String" }, "netmask": { "description": "The DHCP scope netmask (dotted form).", "type": "String" }, "scopestartrange": { "description": "The DHCP scope starting IP address.", "type": "String" }, "scopeendrange": { "description": "The DHCP scope ending IP address.", "type": "String" }, "exclusionrange": { "description": "The DHCP scope exclusion ranges (A-B,C-D,...).", "type": "String" }, "router": { "description": "The DHCP scope default router IP.", "type": "String" }, "dnsserverip": { "description": "The DHCP scope DNS server IP.", "type": "String" } } }
Create the DHCP scope with Bolt by running:
bolt task run --targets dc renfro::create_dhcp_scope dhcpserver=dc1.ad.blab.renf.ro dhcpserverip=192.168.1.3 scopeid=192.168.1.0 scopename=blab.renf.ro netmask=255.255.255.0 scopestartrange=192.168.1.1 scopeendrange=192.168.1.254 exclusionrange=192.168.1.1-192.168.1.99,192.168.1.200-192.168.1.254 router=192.168.1.1 dnsserverip=192.168.1.3
Verify the new DHCP scope settings are correct with:
bolt command run "get-dhcpserverv4scope" --target dc bolt command run "get-dhcpserverv4scope -scopeid 192.168.1.0" --target dc bolt command run "get-dhcpserverv4exclusionrange -scopeid 192.168.1.0" --target dc bolt command run "get-dhcpserverv4optionvalue -scopeid 192.168.1.0" --target dc
Disable the DHCP service on pfSense, then disconnect and reconnect the Xubuntu system from the network segment to verify it gets an IP from the domain controller.
Managing DHCP Reservations with Bolt
In the long run, we may want to have future hosts get their IP addresses over DHCP, but we’ll probably want to reserve those IPs to enable easier firewall rule management.
In manual PowerShell, we’d normally run:
Add-DhcpServerv4Reservation -ScopeId 192.168.1.0 -IPAddress 192.168.1.4 -ClientId "00-22-44-66-88-AA" -Description "foo.blab.renf.ro"
and
Remove-DhcpServerv4Reservation -IPAddress 192.168.1.4
Since there’s only one command for each operation and no duplicated variables, there’s little need to make a Bolt task just for DHCP reservations. But when we start provisioning new hosts automatically, we’d like the host to:
- boot over the network (PXE)
- determine its own hostname via reverse DNS lookup
Combining these steps would require a process with parameters of a hostname, an IP address, and a MAC address. The hostname and IP would be used to create forward and reverse DNS records, and the MAC address and IP would be used for a DHCP reservation. We’ve already got a Bolt task to manage DNS, so let’s write up a simple Bolt task to manage DHCP.
Creating DHCP Reservations
The PowerShell script create_dhcp_reservation.ps1
contains:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$true)] [string] $scope, [Parameter(Mandatory=$true)] [string] $ip, [Parameter(Mandatory=$true)] [string] $mac, [Parameter(Mandatory=$true)] [string] $hostname ) $mac = $mac -replace ":","-" Add-DhcpServerv4Reservation -ScopeId $scope -IPAddress $ip -ClientId $mac ` -Description $hostname
The metadata JSON create_dhcp_reservation.json
contains:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Create DHCP reservation.", "parameters": { "scope": { "description": "The DHCP scope (network address).", "type": "String" }, "ip": { "description": "The DHCP client IP address.", "type": "String" }, "mac": { "description": "The DHCP client MAC address.", "type": "String" }, "hostname": { "description": "The DHCP client hostname.", "type": "String" } } }
Run the task with:
bolt task run renfro::create_dhcp_reservation scope=192.168.1.0 ip=192.168.1.4 mac=00:50:56:22:44:66 hostname=xubuntu --targets dc
and disconnect and reconnect the Xubuntu system’s network interface to verify it gets a new IP of 192.168.1.4.
Removing DHCP Reservations
The PowerShell script remove_dhcp_reservation.ps1
contains:
[CmdletBinding(PositionalBinding=$false)] param ( [Parameter(Mandatory=$true)] [string] $ip ) $mac = $mac -replace ":","-" Remove-DhcpServerv4Reservation -IPAddress $ip
The metadata JSON remove_dhcp_reservation.json
contains:
{ "puppet_task_version": 1, "supports_noop": false, "description": "Remove DHCP reservation.", "parameters": { "ip": { "description": "The DHCP client IP address.", "type": "String" } } }
Run the task with:
bolt task run renfro::remove_dhcp_reservation ip=192.168.1.4 --targets dc
and disconnect and reconnect the Xubuntu system’s network interface to verify it gets a new IP between 192.168.1.100 and 192.168.1.199.
Combining DHCP and DNS Settings to Provision New Hosts and Retire Old Hosts with a Bolt Plan
We used a Bolt plan during the creation of the Active Directory domain because it let us prompt for a safe-mode administator password. We’ll use one here for its original purpose: orchestrating tasks into a higher-level workflow. Since new server hosts will need a DHCP reservation and a DNS entry, we’ll create a plan to call our earlier tasks that manage DHCP and DNS entries. When a server host is retired, we’ll want to release its DHCP reservation and clear its DNS entries. The same principle applies, and we’ll make a second plan for clearing those settings.
Im the Boltdir/site-modules/renfro/plans
directory, we make two plan files using the Puppet language.
The first is provision_dhcp_dns.pp
, which contains:
plan renfro::provision_dhcp_dns( TargetSpec $dc, String $hostname, String $domain, String $ip, String $mac, String $scope ) { run_task('renfro::create_dhcp_reservation', $dc, scope => $scope, ip => $ip, mac => $mac, hostname => $hostname) run_task('renfro::create_dns_record', $dc, name => $hostname, ip => $ip, domain => $domain) }
The second is clear_dhcp_dns.pp
, which contains:
plan renfro::clear_dhcp_dns( TargetSpec $dc, String $hostname, String $domain, String $ip ) { run_task('renfro::remove_dhcp_reservation', $dc, ip => $ip) run_task('renfro::remove_dns_record', $dc, name => $hostname, ip => $ip, domain => $domain) }
They can be examined at the command line using bolt plan show
.
Execute the provision_dhcp_dns
plan for the Xubuntu host by running:
bolt plan run renfro::provision_dhcp_dns dc=dc hostname=xubuntu domain=blab.renf.ro ip=192.168.1.4
and verify that the forward and reverse DNS records for xubuntu.blab.renf.ro are correct. Disconnect and reconnect the Xubuntu system’s network interface to verify it gets an IP address of 192.168.1.4.
Execute the clear_dhcp_dns
plan for the Xubuntu host by running:
bolt plan run renfro::clear_dhcp_dns dc=dc hostname=xubuntu domain=blab.renf.ro ip=192.168.1.4
and verify that the forward and reverse DNS records for xubuntu.blab.renf.ro are deleted. Disconnect and reconnect the Xubuntu system’s network interface to verify it gets a new IP between 192.168.1.100 and 192.168.1.199.