One of possible slave DNS setups can be achieved using Virtuialmin hook for create/delete account and BIND notifications when only DNS records change. The two servers can then be used as ns1/ns2 for domains hosted on the first one where Virtualmin is running. The other one is DNS-only server.
Prepare 2 Centos 8 servers. Full hostnames: srv7.temporary-domain.net
10.10.10.80 and srv9.temporary-domain.net
10.10.10.90. srv7 will host Virtualmin (and master DNS server managed by VA), srv9 will be slave DNS updated via SSH/script (when new zone is created) or via DNS NOTIFY when zone is updated.
ns1.temporary-domain.net
should be pointed with ‘A’ DNS record to IP of srv7.
ns2.temporary-domain.net
should be pointed with ‘A’ DNS record to IP of srv9.
ns1.temporary-domain.net
should be registered as nameserver with IP of srv7.
ns2.temporary-domain.net
should be registered as nameserver with IP of srv9.
These ns1/ns2 can be used at the registrar as nameservers for domains hosted on srv7.
srv7# hostnamectl set-hostname srv7.temporary-domain.net
In real setup install Virtualmin on srv7 - this will install BIND but for now we will use generic bind.
srv7# yum -y install bind bind-utils
srv7# systemctl enable --now named
srv7# systemctl status named
In VA set nameservers to ns1.temporary-domain.net
and ns2.temporary-domain.net
. This can be done in post installation wizard or later. DNS zones created by Virtualmin will include these nameservers in NS records. Generate root’s key on master. We will copy it’s public part to srv9 later.
srv7# ssh-keygen -P "" -f ~/.ssh/id_rsa -C "id_rsa_default"
Install bind on slave
srv9# hostnamectl set-hostname srv9.temporary-domain.net
srv9# yum -y install bind bind-utils
srv9# systemctl enable --now named
srv9# systemctl status named
This dnssync
user will have rights to update DNS configs
srv9# adduser dnssync
srv9# PASS=`openssl rand -base64 9`; echo $PASS && echo $PASS | passwd --stdin dnssync
srv9# sed -i -r 's/^(PasswordAuthentication).*/\1 yes/' /etc/ssh/sshd_config
srv9# systemctl restart sshd
srv7# ssh-copy-id [email protected]
srv9# sed -i -r 's/^(PasswordAuthentication).*/\1 no/' /etc/ssh/sshd_config
srv9# systemctl restart sshd
Make named listen on all interfaces
srv7# sed -i -r 's/(listen-on ).*/\1 port 53 { any; };/' /etc/named.conf
srv7# sed -i -r 's/(listen-on-v6 ).*/\1 port 53 { any; };/' /etc/named.conf
srv7# sed -i -r 's/(allow-query ).*/\1 { any; };/' /etc/named.conf
Adjust master named.conf and use SRV9 IP
srv7# sed -i -r 's|(options \{)|\1\n\tallow-transfer { 10.10.10.90; }; // transfer to slave\n|' /etc/named.conf
srv7# sed -i -r 's|(options \{)|\1\n\talso-notify { 10.10.10.90; }; // notify\n|' /etc/named.conf
The above commands modifying named.conf may differ slightly when VA is installed as VA may insert its own versions of the directives.
Here you need to use SRV7 IP in from=
srv9# sed -i -e 's|^|from="10.10.10.80",command="/usr/bin/sudo -E /usr/local/sbin/zone_updater.sh" |' /home/dnssync/.ssh/authorized_keys
Make named listen on all interfaces
srv9# sed -i -r 's/(listen-on ).*/\1 port 53 { any; };/' /etc/named.conf
srv9# sed -i -r 's/(listen-on-v6 ).*/\1 port 53 { any; };/' /etc/named.conf
srv9# sed -i -r 's/(allow-query ).*/\1 { any; };/' /etc/named.conf
srv9# sed -i -r 's|(options \{)|\1\n\tmasterfile-format text;\n|' /etc/named.conf
Allow user dnssync
to run updater with root
rights
srv9# cat >/etc/sudoers.d/dnssync<<'EOF'
dnssync ALL = (root) NOPASSWD:SETENV:/usr/local/sbin/zone_updater.sh *
EOF
Zone updater script for slave
srv9# cat >/usr/local/sbin/zone_updater.sh<<'EOF'
#!/bin/bash
IFS=' ' read SUDOCOM ENVSWITCH COM DOMAIN IP ACTION <<< $SSH_ORIGINAL_COMMAND
LOG=/var/log/zone_updater.log
echo `date`" $SSH_ORIGINAL_COMMAND" >> $LOG
echo $DOMAIN | grep -qP '(?=^.{5,254}$)(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)'
if [ $? -ne 0 ]; then
echo "$DOMAIN is not a valid domain" >> $LOG
exit 1
fi
if [ "$ACTION" != "add" -a "$ACTION" != "del" ]; then
echo "$ACTION is not a valid action" >> $LOG
exit 1
fi
if [ "$ACTION" == "add" ]; then
# update zone file no matter if the zone is active or not
cat - > /var/named/${DOMAIN}.hosts
chown named: /var/named/${DOMAIN}.hosts
chmod 644 /var/named/${DOMAIN}.hosts
# activate zone if missing
grep -q -P "zone \"${DOMAIN}\" \{" /etc/named.conf
if [ $? -ne 0 ]; then
echo "Adding zone $DOMAIN to named.conf" >> $LOG
echo -e "zone \"$DOMAIN\" { type slave; masters { $IP; }; file \"/var/named/$DOMAIN.hosts\"; };" >> /etc/named.conf
fi
fi
if [ "$ACTION" == "del" ]; then
[ -f /var/named/${DOMAIN}.hosts ] && rm -f /var/named/${DOMAIN}.hosts
echo "Removing zone $DOMAIN from named.conf" >> $LOG
perl -i -0777 -spe '{$_ =~ s/^zone "$d" \{.*?\};.*?\};\n//ms}' -- -d=$DOMAIN /etc/named.conf
fi
rndc reload
echo "rndc reload RC = $?" >> $LOG
EOF
srv7# chmod +x /usr/local/sbin/zone_updater.sh
DNS sync script
srv7# cat>/usr/local/sbin/dns_sync.pl<<'EOF'
#!/usr/bin/perl -w
use strict;
use 5.010;
unless ($ARGV[0] and $ARGV[0] =~ /(?=^.{5,254}$)(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)/
and $ARGV[1] and ($ARGV[1] eq 'add' or $ARGV[1] eq 'del')) {
say "Usage: $0 domain.com add|del";
say "This script sync master zone to slave server for domain.com";
exit 1;
}
my $domain = $ARGV[0];
my $hostname = `hostname -f`; chomp $hostname;
my $ip = `hostname -I`; $ip =~ s/\s+$//; # chomp $ip;
my $action = $ARGV[1];
my $config;
open F, "/etc/webmin/virtual-server/config" or die "$!";
{ local $/; $config = <F>; }
my ($master) = $config =~ /^bind_master=(.*?)$/ms;
my (@slaves) = $config =~ /^dns_ns=(.*?)$/msg;
say "master = $master, slaves = ".join(" ",@slaves);
# create
if (-f "/var/named/$domain.hosts" && $action eq 'add') {
for my $ns (@slaves) {
system("cat /var/named/$domain.hosts | \
ssh -p2017 -v dnssync\@$ns \"/usr/bin/sudo -E /usr/local/sbin/zone_updater.sh ${domain} ${ip} ${action}\" 2>/dev/null;");
}
}
# delete
if ($action eq 'del') {
for my $ns (@slaves) {
system("cat /dev/null | \
ssh -p2017 -v dnssync\@$ns \"/usr/bin/sudo -E /usr/local/sbin/zone_updater.sh ${domain} ${ip} ${action}\" 2>/dev/null;");
}
}
EOF
srv7# chmod +x /usr/local/sbin/dns_sync.pl
Virtualmin hook
Settings -> Virtualmin Config -> Actions upon user and server creation -> Command to run after making changes to a server: /usr/local/bin/post_virtual_modification.sh
It will be called at new domain creation and will call /usr/local/bin/dns_sync.pl This hook does not run for DNS modification so we need to use other method: DNS notify (master notifies slave automatically).
srv7# cat >/usr/local/bin/post_virtual_modification.sh<<'EOF'
#!/bin/bash
LOG=/var/log/`basename $0`.log
if [ $VIRTUALSERVER_ACTION == "CREATE_DOMAIN" ]; then
echo `date`" /usr/local/sbin/dns_sync.pl $VIRTUALSERVER_DOM add" >> $LOG
/usr/local/sbin/dns_sync.pl $VIRTUALSERVER_DOM add
elif [ $VIRTUALSERVER_ACTION == "DELETE_DOMAIN" ]; then
echo `date`" /usr/local/sbin/dns_sync.pl $VIRTUALSERVER_DOM del" >> $LOG
/usr/local/sbin/dns_sync.pl $VIRTUALSERVER_DOM del
fi
EOF
srv7# chmod +x /usr/local/bin/post_virtual_modification.sh