Active Directory Needs Friends!

For those of you who didn’t read my predecessor post on setting up a full-blown Active Directory infrastructure on my home network with home directories, roaming user profiles and group policy using only open source software, take a read through that. This is a follow-on post where I have added a second Active Directory domain controller in a private cloud environment and then bridged that private cloud network to my secure home network using WireGuard.

Bridging The Networks

To start off, since I’m using the bleeding-edge Ubuntu version on my primary domain controller, I set up a virtual server in my cloud provider of choice using 21.10 as well. For the private network, I put it on its own private network that does not collide with my home network (192.168.1.0/24). In this case it is 192.168.2.0/24.

My VPS provider allows me to supply SSH keys at their web console that restricts who can ssh into the remote virtual machine to only those who have the private key that corresponds to the public keys you upload and select. This ensures that I can securely log into the machine as root-level access without fear. The first thing do to, however, when I log into the new server is to update the packages installed on it:

# apt update
# apt upgrade
# reboot

Now for the wireguard setup on the remote virtual machine. For the purposes of this section, we will call it the “server”:

# apt install wireguard wireguard-tools
# wg genkey | sudo tee /etc/wireguard/server_private.key
# wg pubkey | sudo tee /etc/wireguard/server_public.key
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
# sysctl -p
net.ipv4.ip_forwrd=1
net.ipv6.conf.all.forwarding=1
# vim /etc/wireguard/wg0.conf
[Interface]
Address = 10.10.10.1/32
ListenPort = 51820
PrivateKey = *** contents of /etc/wireguard/server_private.key ***
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT

[Peer]
PublicKey = *** contents of /etc/wireguard/server_public.key from remote ***
Endpoint = 1.2.3.4:51820 # IP address of remote
AllowedIPs = 10.10.10.2/32, 192.168.1.0/24

Since my local network is on a residential ISP, I need to use the tools on my ISP’s router to port map the Wireguard port that comes in on the public IP address to the OpenBSD router. Now, we will need to set up the WireGuard configuration on the OpenBSD 7.0 router that I use for my secure network at home (private IP is 192.168.1.1):

# pkg_add wireguard-tools
# sysctl net.inet.ip.forwarding=1
# echo 'net.inet.ip.forwarding=1' | tee -a /etc/sysctl.conf
# mkdir /etc/wireguard
# chmod 700 /etc/wireguard
# openssl rand -base64 32 > /etc/wireguard/server_private.key
# wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key
# vim /etc/hostname.wg0
inet 10.10.10.2 255.255.255.0
!/usr/local/bin/wg setconf wg0 /etc/wireguard/wg0.conf
!route add -inet 192.168.2.0/24 10.10.10.2
# vim /etc/wireguard/wg0.conf
[Interface]
PrivateKey = *** contents of /etc/wireguard/server_private.key ***
ListenPort = 51820

[Peer]
PublicKey = *** contents of /etc/wireguard/server/public.key from remote ***
Endpoint = 2.3.4.5:51820 # public IP address of remote
AllowedIPs = 10.10.10.2/32, 192.168.2.0/24
# vim /etc/pf.conf
... add to end...
pass in on egress proto udp from any to any port 51820 keep state
pass on wg0
pass out on egress inet from (wg0:network) to any nat-to (egress:0)
# pfctl -f /etc/pf.conf
# sh /etc/netstart wg0

Now, run the following command on the remote Linux box to start the Wireguard service:

# systemctl enable wg-quick@wg0.service
# systemctl start wg-quick@wg0.service

At this point, you should be able to check the status of the Wireguard network on both sides with the command wg show and that should show both ends connected. You should be able to ping hosts on the remote network from each end.

So far, the only problem I have found with this setup to bridge the networks, is that my Windows machines that are multi-homed (i.e. one interface – wired ethernet – is connected to my ISP’s network and one – wireless – is connected to my secure network) needs to have a route manually added as follows:

C:\WINDOWS\system32> route add -p 192.168.2.0 MASK 255.255.255.0 192.168.1.1

In this case, the 192.168.2.0/24 network is the remote network and the 192.168.1.1 IP references my OpenBSD 7.0 router.

Remote Samba Active Directory Server

Now that we have a remote network that is securely bridged to our local private network on which the current Samba Active Directory infrastructure is running, it is time to create the VPC virtual server that will be running our Active Directory remote server. My particular VPC service allows me to create a server that is on the same private network as my remote “router” that is running Wireguard, so I create such a server and call it AD2.ad.example.com (put in your own AD domain name there).

First things first, the remote AD server must have a route to the Wireguard network. This is not a necessary step on the home network side because the Wireguard server is running on the OpenBSD 7.0 router and by definition is the default route for the servers on that network. This is not the case for the servers on the private network at the VPC. To do this, we simply need to add a persistent route. So as to not mess things up with the default network configuration on the remote host, I decided to create a (yuck) SystemD (blech) service:

# apt update
# apt upgrade
# apt install network-tools
# vim /usr/sbin/MY-NETWORK.sh
#! /bin/sh
/usr/sbin/route add -net 192.168.1.0/24 gw 192.168.2.2 eth1
# chmod +x /usr/sbin/route
# vim /etc/systemd/system/MY-NETWORK.service
[Unit]
Description=Route to Wireguard server
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
User=root
ExecStart=/usr/sbin/MY-NETWORK.sh

[Install]
WantedBy=multi-user.target
# systemctl daemon-reload
# systemctl enable MY-NETWORK.service
# systemctl start MY-NETWORK.service

At this point, you should be able to ping the domain controller on the remote (home) network and from that domain controller, you should be able to ping the new host.

Now we need to do the standard networking configuration ‘stuff’ that Samba likes. First, edit the /etc/hosts file to remove the “127.0.1.1 DC2.ad.example.com DC2” line and replace it with one tying it to the static private IP address that has been assigned to this virtual host. In this case, “192.168.2.3 DC2.ad.example.com DC2”.

Here we need to add the necessary packages to host an Active Directory domain controller:

# apt install acl attr samba samba-dsdb-modules samba-vfs-modules winbind libpam-winbind libnss-winbind libpam-krb5 krb5-config krb5-user dnsutils net-tools smbclient

Next, disable systemd’s resolver and add the remote AD server as the DNS name server and also add the Active Directory domain:

# systemctl stop systemd-resolved
# systemctl disable systemd-resolved
# unlink /etc/resolv.conf
# vim /etc/resolv.conf
nameserver 192.168.1.2
search ad.example.com

Now, go ahead and reboot the remote machine and when you log back into it, test to see if DNS is working properly:

# nslookup DC1.ad.example.com
Server:     192.168.1.2
Name:      DC1.ad.example.com
# nslookup 192.168.1.2
2.1.168.192.in-addr.arpa    name = DC1.ad.example.com
# host -t SRV _ldap._tcp.ad.example.com
_ldap._tcp.ad.example.com has SRV record 0 100 389 dc1.ad.example.com

Rename the /etc/krb5.conf file and the /etc/samba/smb.conf file like you did when you created the domain controller on your local network. Then, create a new /etc/krb5.conf file:

[libdefaults]
    default_realm = AD.EXAMPLE.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true

At this point, we need to set up an NTP server and sync it to the one at our original Active Directory domain controller:

# apt install chrony ntpdate
# ntpdate 192.168.1.2
# echo "server 192.168.1.2 minpoll 0 maxpoll 5 maxdelay .05" > /etc/chrony/chrony.conf
# systemctl enable chrony
# systemctl start chrony

Now we need to authenticate against Kerberos and get a ticket:

# kinit administrator
... provide your AD\Administrator password ...
# klist

At this point, it’s time to join the domain as a new domain controller:

# samba-tool domain join ad.example.com DC -U"AD\administrator"

After the tool finishes (it produces a lot of output), you need to copy the generated Kerberos configuration file to the /etc directory:

# cp /var/lib/samba/private/krb5.conf /etc/krb5.conf

You need to manually create the systemd service and set things up so that everything fires up when you reboot the server:

# systemctl mask smbd nmbd winbind
# systemctl disable smbd nmbd winbind
# systemctl stop smbd nmbd winbind
# systemctl unmask samba-ad-dc
# vim /etc/systemd/system/samba-ad-dc.service
[Unit]
Description=Samba Active Directory Domain Controller
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
ExecStart=/usr/sbin/samba -D
PIDFILE=/run/samba/samba.pid
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
# systemctl daemon-reload
# systemctl enable samba-ad-dc
# systemctl start samba-ad-dc

OK. At this point we have a Samba Active Directory domain controller running. We need to get SysVol replication going now to ensure that the two controllers are bidirectionally synchronized.

Bidirectional SysVol Replication

To get the SysVol replication going bidirectionally, I followed the guide here. First, you need some tools installed on both DCs:

# apt install rsync unison

Generate an ssh key on both domain controllers:

# ssh-keygen -t rsa

Now, copy the /root/.ssh/id_rsa.pub contents from one server into the /root/.ssh/authorized_keys file on the other and vice-versa. Verify that you can log in without passwords from one server to the other. If you are prompted for a password, then edit your /etc/ssh/sshd_config file and add the line “PasswordAuthentication no” and then restart the ssh service. Now you should be able to log in just using public keys and no password from one server to the other and back.

Now, copy the /root/.ssh/id_rsa.pub contents from one server into the /root/.ssh/authorized_keys file on the other and vice-versa. Verify that you can log in without passwords from one server to the other. If you are prompted for a password, then edit your /etc/ssh/sshd_config file and add the line “PasswordAuthentication no” and then restart the ssh service. Now you should be able to log in just using public keys and no password from one server to the other and back.

On your new remote DC (DC2 in my example), do the following to ensure that your incoming ssh connection isn’t rate limited:

# mkdir /root/.ssh/ctl
cat << EOF > /root/.ssh/ctl/config
Host *
ControlMaster auto
ControlPath ~/.ssh/ctl/%h_%p_%r
ControlPersist 1
EOF

Now, to be able to log what happens during the sync on the local DC (DC1 in my example), do the following to create the appropriate log files:

# touch /var/log/sysvol-sync.log
# chmod 640 /var/log/sysvol-sync.log

Now, do the following on the local DC (DC1 in my example):

install -o root -g root -m 0750 -d /root/.unison
cat << EOF > /root/.unison/default.prf
# Unison preferences file
# Roots of the synchronization
#
# copymax & maxthreads params were set to 1 for easier troubleshooting.
# Have to experiment to see if they can be increased again.
root = /var/lib/samba
# Note that 2 x / behind DC2, it is required
root = ssh://root@DC2//var/lib/samba 
# 
# Paths to synchronize
path = sysvol
#
#ignore = Path stats    ## ignores /var/www/stats
auto=true
batch=true
perms=0
rsync=true
maxthreads=1
retry=3
confirmbigdeletes=false
servercmd=/usr/bin/unison
copythreshold=0
copyprog = /usr/bin/rsync -XAavz --rsh='ssh -p 22' --inplace --compress
copyprogrest = /usr/bin/rsync -XAavz --rsh='ssh -p 22' --partial --inplace --compress
copyquoterem = true
copymax = 1
logfile = /var/log/sysvol-sync.log
EOF

Now, run the following command on your local DC (DC1 in my example):

# /usr/bin/rsync -XAavz --log-file /var/log/sysvol-sync.log --delete-after -f"+ */" -f"- *"  /var/lib/samba/sysvol root@DC2:/var/lib/samba  &&  /usr/bin/unison

This should synchronize the two sysvols. If you followed my previous how-to and set up Group Policy, this can take some time as there are a lot of files involved that are stored on the SysVol. After it is complete, you can verify this by doing the following on your remote DC (DC2 in my example):

# ls /var/lib/samba/sysvol/ad.example.com

You should see the same file structure under that directory on both servers. This will copy everything including your group policy stuff as well.

Now that you have done the initial sync, just add the following to your crontab on the local DC (DC1 in my example):

# crontab -e
*/5 * * * * /usr/bin/unison -silent

You should monitor /var/log/sysvol-sync.log on your local DC (DC1 in my example) to ensure that everything is synchronizing and staying that way over time.

Hope this little “how-to” helps folks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s