Skip to content

Instantly share code, notes, and snippets.

@russianryebread
Last active August 29, 2015 14:22
Show Gist options
  • Save russianryebread/c66449395aa6cff01fea to your computer and use it in GitHub Desktop.
Save russianryebread/c66449395aa6cff01fea to your computer and use it in GitHub Desktop.
Automatic zone replication with BIND9 in Plesk 11.5 + Debian Wheezy + PHP

Automatic zone replication with BIND9 in Plesk 11.5 + Debian Wheezy + PHP

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

Nomenclature

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

Implementation

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.

Master Configuration

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.

Slave Configuration

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

BIND Configuration

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

Generate 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.");
}

Customization

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;

Finalization / Automation

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment