Written by ilijamt - Original article
This tutorial is aimed at showing how to set up zone replication from a master to a slave server, without the help of the user input on Debian Wheezy with Plesk 11.5 and PHP
master
Main server, that hosts Plesk and BIND9 DNS server in Master mode
slave
Slave DNS server that hosts BIND9 DNS server in Slave mode
To be able to get a list of available domains on the main server we need mysql-client, on the DNS server, the reason for it is so we can get a list of domains from the primary server, so we can check if they exist or not so we can create them if necesseary.
We need to configure mysql to allow access from the slave server. Connect to the MySQL server on the master and create a user that will be only for viewing the domains records on the server.
Now let’s execute this SQL command to create a user for our case:
CREATE USER 'dns'@'%' identified by 'noh4icaigh2weiGaiph3sahDoeThaequ5HeGha2oeMeezee6re';
GRANT USAGE ON *.* TO 'dns'@'%' with MAX_QUERIES_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
GRANT SELECT ON psa.domains TO 'dns'@'%';
FLUSH PRIVILEGES;
These commands will enable you to query only the domains tables and nothing else, in case someone manages to find out the password they will only be able to query the domains we have. In our case this will allow a connection from anywhere if identified by the password, if you want to be more restrictive you can always specify the IP instead of % in the SQL.
So let’s install the client and some other requirements on the slave server, we can do so really easy by issuing the following command:
apt-get install mysql-client php5-cli php-pear php5-mysql
Before we start we need to make sure we can add new zones in the system without restarting the server, to do so, we need to add to the options part of named.conf, the option to allow new zones, so we edit the file and add the following option in the config file.
allow-new-zones yes;
Now we can use rndc
to create, update, read, delete the zones
Now, on to the next problem, we have the data, now we need to generate the zones when there is something new to add, we can do this by creating a script that is going to run every 15 mins and query the dataset to check if there is new data that we need to input. We can do this simply in PHP:
#!/usr/bin/env php
<?php
define("PSA_USER", 'dns');
define("PSA_PASSWORD", 'noh4icaigh2weiGaiph3sahDoeThaequ5HeGha2oeMeezee6re');
define("PSA_DB", "psa");
define("PSA_HOST", "master");
define("BIND_VIEW", "slave");
define("PSA_SQL", "select name from domains where parentDomainId = 0");
define("LOCAL_DOMAIN_CACHE", "/opt/zones.cache");
define("NS_IPS", "10.0.0.1;");
define("TEMPLATE", "%%domain%% '{type slave; file \"slave/%%domain%%\"; masters { " . NS_IPS . " }; };'");
openlog("slave.configurator.php", LOG_PID | LOG_PERROR, LOG_LOCAL0);
$cache = array();
if (file_exists(LOCAL_DOMAIN_CACHE)) {
$cache = json_decode(file_get_contents(LOCAL_DOMAIN_CACHE), true);
if (is_null($cache)) {
$cache = array();
}
}
$link = mysqli_connect(PSA_HOST, PSA_USER, PSA_PASSWORD, PSA_DB) or die("Error " . mysqli_error($link));
if (!$link) {
$err = mysqli_error($link);
syslog(LOG_ERR, $err);
die("Error: $err");
}
$result = $link->query(PSA_SQL);
$database_domains = array();
while ($domain = mysqli_fetch_assoc($result)) {
array_push($database_domains, $domain['name']);
}
$process = array_diff($database_domains, $cache);
$full = array_unique(array_merge($database_domains, $process));
file_put_contents(LOCAL_DOMAIN_CACHE, json_encode($full));
$format = "Y-m-d H:i:s - ";
syslog(LOG_INFO,
date($format, microtime(true)) . "Total available domains in cache: " . count($cache));
syslog(LOG_INFO,
date($format, microtime(true)) . "Total available domains in database: " . count($database_domains));
syslog(LOG_INFO,
date($format, microtime(true)) . "Difference elements between cache and database: " . count($process) . ", domains: " . (count($process) > 0 ? join(",",
$process) : "None available"));
syslog(LOG_INFO,
date($format, microtime(true)) . "Total domains in system: " . count($full));
if (count($process) > 0) {
foreach ($process as $domain) {
echo date($format, microtime(true)) . "Processing domain: $domain\n";
$template = str_replace("%%domain%%", $domain, TEMPLATE);
syslog(LOG_INFO, "/usr/sbin/rndc addzone $template");
exec("/usr/sbin/rndc addzone $template");
}
exec("/usr/sbin/rndc reload");
} else {
syslog(LOG_INFO, date($format, microtime(true)) . "Nothing to process.");
}
NS_IPS
, is a ;
separated list of all your nameservers, you should put your IP of your master nameserver here, in the demo script I put 10.0.0.1
, but you should replace it with your own master nameserver, NOTICE: the IP should always have a ;
at the end of the IP in the script.
If /usr/sbin/rndc
is not the correct path for rndc
, you should change the script accordingly;
Move the script to wherever you keep config scripts, make the script executable:
chmod +x /opt/slave.configurator.php
Then add it to root's crontab
.
*/15 * * * * /opt/slave.configurator.php