Published on Oct 25 2021 in Backup Directadmin Virtualmin

DirectAdmin and Virtualmin can be used together to form a DNS cluster. Domain created on any of them will create zone file on both and DNS records will be synchronized automatically by BIND.

BIND DNS server newer than 9.7.2 has a useful feature allowing it to manage zones on peer (slave, secondary) DNS server. These are rndc’s addzone and delzone commands. In this real life example we will leverage it to form DNS cluster. We will use domain add/delete hooks provided by both control panels to create zones remotely (on secondary, slave server). For a domain created in Directadmin, a slave zone will be created on Virtualmin and vice versa. Updates to zone records on master/SOA server for the zone will be propagated by BIND automatically to slave server.

Virtualmin/Webmin provide UI way for modifying BIND config so we will partially use it. For Directadmin changes need to be made from command line.

Let’s assume our IPs: DirectAdmin, ns1.domain.com, 10.0.0.2 and Virtualmin, ns2.domain.com, 10.0.0.3.

Allow both hosts in both firewalls (on ns1 and ns2). For more strict rules you can only allow traffic to ports 953 and 53.

BIND named.conf adjustments

Copy key from /etc/rndc.key on ns1 to ns2

ns1# cat /etc/rndc.key
key "rndc-key" {
    algorithm hmac-md5;
    secret "IxB0lIh3DiH/ycIpvK/YlQ==";
};

ns2# cat > /etc/rndc.key <<EOF
key "rndc-key" {
    algorithm hmac-md5;
    secret "IxB0lIh3DiH/ycIpvK/YlQ==";
};
EOF

Do not copy and paste above keys. If the key is not there you can generate it for example with dnssec-keygen -a HMAC-MD5 -b 128 -n HOST rndc-key

On ns1 paste into named.conf

include "/etc/rndc.key";
controls {
    inet * port 953 allow { 127.0.0.1; 10.0.0.3; } keys { "rndc-key"; };
};

In options section add

allow-new-zones yes;
masterfile-format text;
allow-transfer { 10.0.0.3; };

Repeat named.conf modifcations on ns2. Just use the other peer IP (10.0.0.2 instead of 10.0.0.3).

notify is enabled by default so slave (non SOA) servers listed in NS record will be notfied about any changes in the zone. In case you also want other namservers notified use also-notify.

Virtualmin UI specific adjustments

Virtualmin’s default zone template adds restricted allow-transfer directive into each new zone so we need to change it. Go to Virtualmin - System Settings - Server Templates - Additional named.conf directives for new zones and check Directives below… leaving the box empty. Then uncheck also-notify and allow-transfer under Automatically add named.conf directives and save. You may need to cleanup allow-transfer { ... } from your named.conf. But newly added domains will not have it inserted and we already have allow-transfer defined globally.

In Virtualmin - System Settings - Server Templates - Additional manually configured nameservers add our slave and master: ns1.domain.com, ns2.domain.com. Uncheck Add nameserver record for this system as this would add hostname as another NS.

In Virtualmin - System Settings - Server Templates - BIND DNS Domain - Master DNS server hostname set current server’s ns-like hostname. In our case Virtalmin runs ns2 so we save there ns2.domain.com.

Directadmin UI specific adjustments

Login as admin and under Server Manager - Administrator Settings - Server Settings set ns1.domain.com and ns2.domain.com.

Virtualmin hooks

In Virtualmin - System Settings - Virtualmin Configuration - Actions upon server and user creation - Command to run after making changes to a server paste /usr/local/bin/post_virtual_modification.sh and save. Then create the script. Here one script handles creation and deletion of zones.

cat >/usr/local/bin/post_virtual_modification.sh<<'EOF'
#!/bin/bash

export PATH=/usr/bin:/usr/sbin:$PATH

SCRIPT=`basename $0`
LOG=/var/webmin/output/$SCRIPT.log
RNDC_OPTIONS="" # can be -V for verbose log
exec > >(tee -i -a ${LOG})
exec 2>&1

[[ -z "$VIRTUALSERVER_DOM" ]] && { echo "$SCRIPT: VIRTUALSERVER_DOM not set"; exit; }
[[ -z "$VIRTUALSERVER_ACTION" ]] && { echo "$SCRIPT: VIRTUALSERVER_ACTION not set"; exit; }
[[ "$VIRTUALSERVER_ACTION" != "CREATE_DOMAIN" && "$VIRTUALSERVER_ACTION" != "DELETE_DOMAIN" ]] \
&& { echo "$SCRIPT: VIRTUALSERVER_ACTION not in [ CREATE_DOMAIN DELETE_DOMAIN ]"; exit; }

# Log date, command and return code
# error messages will also be printed by VA
if [ "$VIRTUALSERVER_ACTION" == "CREATE_DOMAIN" ]; then
    rndc -s 10.0.0.2 $RNDC_OPTIONS addzone $VIRTUALSERVER_DOM '{ type slave; file "/var/named/'"${VIRTUALSERVER_DOM}"'.db"; masters { 10.0.0.3; }; };'
    RC=$?
    echo `date`" rndc -s 10.0.0.2 $RNDC_OPTIONS addzone $VIRTUALSERVER_DOM '{ type slave; file \"/var/named/${VIRTUALSERVER_DOM}.db\"; masters { 10.0.0.3; }; };' $RC"
elif [ "$VIRTUALSERVER_ACTION" == "DELETE_DOMAIN" ]; then
    # &>/dev/null to squash: zone 'z1.com' will be deleted.The following files were in use and may now be removed: /var/named/z1.com.db
    rndc -s 10.0.0.2 $RNDC_OPTIONS delzone $VIRTUALSERVER_DOM &>/dev/null
    RC=$?
    echo `date`" rndc -s 10.0.0.2 $RNDC_OPTIONS delzone $VIRTUALSERVER_DOM $RC"
fi
EOF

Directamin Hooks

Here we need 2 scripts.

mkdir -p /usr/local/directadmin/scripts/custom/domain_create_post
cat >/usr/local/directadmin/scripts/custom/create_slave_zone.sh<<'EOF'
#!/bin/bash

export PATH=/usr/bin:/usr/sbin:$PATH
SCRIPT=`basename $0`
LOG=/var/log/directadmin/$SCRIPT.log
RNDC_OPTIONS="" # can be -V for verbose log

[[ -z "$domain" ]] && { echo "$SCRIPT: domain not set"; exit; }

# Log date, command and return code. Error messages are also printed by DA
rndc -s 10.0.0.3 $RNDC_OPTIONS addzone $domain '{ type slave; file "/var/named/'"${domain}"'.hosts"; masters { 10.0.0.2; }; };'
RC=$?
echo `date`" rndc -s 10.0.0.3 $RNDC_OPTIONS addzone $domain '{ type slave; file \"/var/named/${domain}.hosts\"; masters { 10.0.0.2; }; };' $RC" >> $LOG

# this will be displayed by DA
[ $RC -eq 0 ] && echo "Successfully added $domain to slave DNS" || echo "Error ($RC) while adding $domain to slave DNS"
EOF
chmod +x /usr/local/directadmin/scripts/custom/domain_create_post/create_slave_zone.sh
chown diradmin: /usr/local/directadmin/scripts/custom/domain_create_post/create_slave_zone.sh

mkdir -p /usr/local/directadmin/scripts/custom/domain_destroy_post

cat >/usr/local/directadmin/scripts/custom/domain_destroy_post/delete_slave_zone.sh<<'EOF'
#!/bin/bash

export PATH=/usr/bin:/usr/sbin:$PATH
SCRIPT=`basename $0`
LOG=/var/log/directadmin/$SCRIPT.log

RNDC_OPTIONS="" # can be -V for verbose log

[[ -z "$domain" ]] && { echo "$SCRIPT: domain not set"; exit; }

# Log date, command and return code
# error messages are also printed by DA
# &>/dev/null to squash: zone 'z1.com' will be deleted.The following files were in use and may now be removed: /var/named/z1.com.db
rndc -s 10.0.0.3 $RNDC_OPTIONS delzone $domain &>/dev/null
RC=$?
echo `date`" rndc -s 10.0.0.3 $RNDC_OPTIONS delzone $domain $RC" >> $LOG

[ $RC -eq 0 ] && echo "Successfully deleted $domain from slave DNS" || echo "Error ($RC) while deleting $domain from slave DNS"
EOF
chmod +x /usr/local/directadmin/scripts/custom/domain_create_post/delete_zone.sh
chown diradmin: /usr/local/directadmin/scripts/custom/domain_destory_post/delete_slave_zone.sh

Similar hooks can be added for Domain Pointers (Aliases):

mkdir -p /usr/local/directadmin/scripts/custom/{domain_pointer_create_post,domain_pointer_destroy_post}
cat >/usr/local/directadmin/scripts/custom/domain_pointer_create_post/create_slave_zone.sh<<'EOF'
#!/bin/bash
/usr/local/directadmin/scripts/custom/domain_create_post/create_slave_zone.sh
EOF
cat >/usr/local/directadmin/scripts/custom/domain_pointer_destroy_post/delete_slave_zone.sh<<'EOF'
#!/bin/bash
/usr/local/directadmin/scripts/custom/domain_destroy_post/delete_slave_zone.sh
EOF

Notes:

To avoid the WARNING: key file (/etc/rndc.key) exists, but using default configuration file (/etc/rndc.conf) remove /etc/rndc.conf. It is not needed as /etc/rndc.key will be used by default. If you need to have rndc.conf where you include rndc.key them move rndc.key out of /etc to avoid the above warning.

if you get rndc: connect failed: 10.0.0.2#953: host unreachable and there is no iptables (iptables -L) then check for firewalld and remove it (systemctl disable --now firewalld && systemctl mask --now firewalld) or allow traffic to ports 953 and 53.

Recent versions of BIND also contain an other synchronzation mechanism called Catalog Zones.

Directadmin places zones in /var/named/${DOMAIN}.db while Virtualminin uses /var/named/${DOMAIN}.hosts. The hook scripts are aware of it.

Above setup can be installed on any BIND equipped server, with or without any control panel. You just need to ensure domain addition or deletion will call respective hook.