Commit a8b20050 authored by helmo's avatar helmo

Issue #2466989: Spin off DNS extension

Code moving to https://www.drupal.org/project/hosting_dns
parent b5851a3b
......@@ -8,7 +8,7 @@
dh $@
override_dh_install:
cp -r "$(CURDIR)/db/" "$(CURDIR)/dns/" "$(CURDIR)/http/" "$(CURDIR)/"*.make "$(CURDIR)/platform/" "$(CURDIR)/Provision/" "$(CURDIR)/Symfony/" "$(CURDIR)"/*.inc "$(CURDIR)"/*.php "$(CURDIR)"/*.info "$(CURDIR)/debian/aegir3-provision/usr/share/drush/commands/provision/"
cp -r "$(CURDIR)/db/" "$(CURDIR)/http/" "$(CURDIR)/"*.make "$(CURDIR)/platform/" "$(CURDIR)/Provision/" "$(CURDIR)/Symfony/" "$(CURDIR)"/*.inc "$(CURDIR)"/*.php "$(CURDIR)"/*.info "$(CURDIR)/debian/aegir3-provision/usr/share/drush/commands/provision/"
# We need this nasty hack, because we added directories.
# TODO: this is really lame, there must be a better way to do this?
......
Here is a list of pointers on how to setup DNS in aegir. It's still very crude
and needs a lot of love. It may also not be the right place for this as it
mixes todos, test procedures and install procedures.
Master server configuration
===========================
First, install bind, allow aegir to sudo rndc reload. Make sure the bind user
can read hostmaster files (adduser bind hostmaster).
Then add the DNS service in the frontend. This should trigger a server
verification and configure the DNS service in the backend. Creating a site
should then create a zone and a record and reload bind.
If you're having trouble with this, you can try to test the backend.
Testing the backend
-------------------
drush provision-save @server_master --dns_service_type=bind
drush @server_master provision-zone create foobar.com
drush @server_master provision-zone rr-add foobar.com www A 1.2.3.4
You should end up with a zonefile like:
; Bind zonefile
; File managed by Aegir
; Changes here may be lost by user configurations, tread carefully
$TTL
@ IN SOA foobar.com. hostmaster.foobar.com. ( 2010061700 21600 3600 604800 86400 )
www IN A 1.2.3.4
With this command:
provision-zone create-host www.foobar.com
should another similar record to the same zonefile.
Slave server configuration
==========================
A slave server requires the following steps:
1. install bind, sudo, rsync on the server
2. create an aegir user on the server the usual way (including SSH key exchange)
3. create the server in the frontend with the bind_slave service
4. configure the master server to use those slaves:
drush provision-save @server_master --slave_servers=@server_slaveservername
Then running the tests describe in the master configuration should create a
config file in /var/aegir/config/bind_slave.conf that you need to include in
your bind configuration. The config file should look something like this:
zone "foobar.com" { type slave; file "/var/hostmaster/config/server_ns4koumbitnet/bind_slave/zone.d/foobar.com.zone"; masters { 1.2.3.4; }; allow-query { any; }; };
Caveats
=======
1. there's duplicate storage altogether: we could parse zonefiles and
use that as storage, not PHP files, but that would make switching
engines more difficult and would require writing parsers for all
engines. such a parser was originally written for bind but was dropped
in one of the numerous rewrites of the DNS code.
2. changing the master/slave relationship doesn't change the zonefiles
unless every zonefile is verified again.
Todo and bugs
=============
Those should be reported in the regular trackers from now on.
<?php
/**
* Base config class for all dns config files.
*/
class Provision_Config_Dns extends Provision_Config {
public $mode = 0777;
function write() {
parent::write();
$this->data['server']->sync($this->filename());
}
function unlink() {
$result = parent::unlink();
$this->data['server']->sync($this->filename());
return $result;
}
}
<?php
class Provision_Config_Dns_Host extends Provision_Config_Dns {
public $template = 'host.tpl.php';
public $description = 'Host-wide DNS configuration';
function filename() {
return "{$this->data['server']->dns_hostd_path}/{$this->uri}.hosts";
}
}
<?php
/**
* Base config class for the server level config.
*/
class Provision_Config_Dns_Server extends Provision_Config_Dns {
public $template = 'server.tpl.php';
public $description = 'Server-wide DNS configuration';
public $data_store_class = 'Provision_Config_Dns_Server_Store';
function filename() {
if (isset($this->data['application_name'])) {
$file = $this->data['application_name'] . '.conf';
return $this->data['server']->config_path . '/' . $file;
}
else {
return FALSE;
}
}
function write() {
// lock the store until we are done generating our config.
$this->store->lock();
parent::write();
$this->store->write();
$this->store->close();
if (isset($this->data['application_name'])) {
$file = $this->data['application_name'] . '.conf';
// We link the app_name.conf file on the remote server to the right version.
$cmd = sprintf('ln -sf %s %s',
escapeshellarg($this->data['server']->config_path . '/' . $file),
escapeshellarg($this->data['server']->aegir_root . '/config/' . $file)
);
if ($this->data['server']->shell_exec($cmd)) {
drush_log(dt("Created symlink for %file on %server", array(
'%file' => $file,
'%server' => $this->data['server']->remote_host,
)));
};
}
}
}
<?php
// The data store for the server configuration
// contains a list of zones we manage.
class Provision_Config_Dns_Server_Store extends Provision_Config_Data_Store {
function filename() {
return $this->data['server']->dns_data_path . '/zones.master.inc';
}
}
<?php
/**
* Representation of a DNS zonefile
*
* This is the internal representation of a zonefile. It can be
* extended by other subclasses to implement various engines, but it
* has its own internal storage (through
* Provision_Config_Dns_Zone_Store).
*
* It assumes a certain structure in the records of the store.
*
* @example
*
* <code>
* $zonefile = array('www' => array('a' => array('1.2.3.3', '1.2.3.4')),
* '@' => array('SOA' => array('hostmaster' => 'localhost', 'email' => 'admin.localhost', 'serial' => '2010082301', 'refresh' => 21600 ... )'),
* 'A' => array('1.2.3.3'),
* 'MX' => array('mail.localhost'),
* 'NS' => array('localhost', 'ns2.localhost'),
* )
* );
* </code>
*
* The zonefile's serial number is incremented automaticall when the
* file is written (in process()). Note how the structure of the SOA
* record is different from the others. First, it is a key-value
* map. Second, it represents only one DNS record (whereas the other
* entries represent as many entries as there are entries in the
* array.
*
* To edit those records, some care need to be taken. Look at the
* implementation of rr-add, rr-delete and rr-modify for examples of
* how it should properly be done, in drush_dns_provision_zone().
*
* @see drush_dns_provision_zone()
* @see increment_serial()
* @see Provision_Config_Dns_Zone_Store
*/
class Provision_Config_Dns_Zone extends Provision_Config_Dns {
public $template = 'zone.tpl.php';
public $description = 'Zone-wide DNS configuration';
public $data_store_class = 'Provision_Config_Dns_Zone_Store';
function filename() {
return "{$this->data['server']->dns_zoned_path}/{$this->data['name']}.zone";
}
function process() {
parent::process();
$records = $this->store->merged_records();
$this->data['dns_email'] = str_replace('@', '.', $this->data['server']->admin_email);
// increment the serial.
$serial = (isset($records['@']['SOA']['serial']) ? $records['@']['SOA']['serial'] : NULL);
$this->store->records['@']['SOA']['serial'] = $records['serial'] = Provision_Service_dns::increment_serial($serial);
$this->data['records'] = $records;
}
function write() {
// lock the store until we are done generating our config.
$this->store->lock();
if ($this->is_empty()) {
$this->unlink();
} else {
parent::write();
$this->store->write();
}
$this->store->close();
}
/**
* this destroys this zonefile, without any checks
*
* It actually removes the zonefile, the internal storage and the
* record in the server config.
*/
function unlink() {
// remove the zonefile
if (parent::unlink()) {
// remove the master record
// XXX: need to do this for slaves too
$this->server->service('dns')->config('server')->record_del($zone)->write();
// remove the zonefile storage
$this->store->unlink();
}
$this->store->unlock();
}
/**
* test to see if the
*/
function is_empty() {
$records = $this->store->merged_records();
// if there is any record that is not SOA or NS, this is
// considered empty
if (empty($records)) {
return TRUE;
}
foreach ($records as $name => $record) {
if ($name != '@') {
return FALSE;
} else {
foreach ($record as $type => $destination) {
if ($type != 'SOA' && $type != 'NS' && !is_null($destination)) {
return FALSE;
}
}
}
}
return TRUE;
}
}
<?php
class Provision_Config_Dns_Zone_Store extends Provision_Config_Data_Store {
function filename() {
return "{$this->data['server']->dns_data_path}/{$this->data['name']}.zone.inc";
}
}
<?php
class Provision_Config_Dnsmasq_Host extends Provision_Config_Dns_Host {
}
<?php
class Provision_Config_Dnsmasq_Server extends Provision_Config_Dns_Server {
}
<?php
class Provision_Config_Dnsmasq_Zone extends Provision_Config_Dns_Zone {
}
<?php
foreach ($ip_addresses as $server => $ip) {
print "{$ip}\t {$this->uri}\n";
}
?>
<?php
foreach ($records as $key => $name) {
printf("conf-file=%s/%s.zone\n", $dns_zoned_path, $name);
}
?>
<?php
foreach ($hosts as $host => $info) {
print "addn-hosts={$dns_hostd_path}/{$host}.hosts\n";
}
?>
<?php
class Provision_Config_Bind_Server extends Provision_Config_Dns_Server {
/**
* pre-render the slave servers IP addresses
*
* This is done so we can configure the allow-transfer ACL.
*/
function process() {
parent::process();
$slaves = array();
if (!is_array($this->server->slave_servers)) {
$this->server->slave_servers = array($this->server->slave_servers);
}
foreach ($this->server->slave_servers as $slave) {
$slaves = array_merge($slaves, d($slave)->ip_addresses);
}
$this->data['server']->slave_servers_ips = $slaves;
}
}
<?php
class Provision_Config_Bind_slave extends Provision_Config_Dns_Server {
public $template = 'slave.tpl.php';
function process() {
parent::process();
if ($this->context->type == 'server') {
$ips = $this->context->ip_addresses;
}
else {
$ips = $this->context->server->ip_addresses;
}
$this->data['master_ip_list'] = implode(';', $ips);
}
}
<?php
class Provision_Config_Bind_Zone extends Provision_Config_Dns_Zone {
/**
* this renders the slave servers names (as their alias is stored)
*/
function process() {
parent::process();
$slaves = array();
if (!is_array($this->server->slave_servers)) {
$this->server->slave_servers = array($this->server->slave_servers);
}
foreach ($this->server->slave_servers as $slave) {
$slaves[] = d($slave)->remote_host;
}
$this->data['server']->slave_servers_names = $slaves;
}
}
<?php
$slave_acl = "";
if (is_array($server->slave_servers_ips)) {
$slaves = implode(";", $server->slave_servers_ips);
if (!empty($slaves)) {
$slave_acl = "allow-transfer { $slaves; };\n";
}
}
foreach ($records as $key => $name) {
printf('zone "%s" { type master; file "%s/%s.zone"; allow-query { any; }; %s };' . "\n", $name, $dns_zoned_path, $name, $slave_acl);
}
?>
<?php
foreach ($records as $zone => $master) {
printf('zone "%s" { type slave; file "%s/%s.zone"; masters { %s; }; allow-query { any; }; };' . "\n", $zone, $dns_zoned_path, $zone, $master_ip_list);
}
?>
; Bind zonefile
; File managed by Aegir
; Changes here may be lost by user configurations, tread carefully
$TTL <?php print $server->dns_ttl; ?>
<?php
print("@ IN SOA $server->remote_host $dns_email (
" . $records['serial'] . " ; serial
$server->dns_refresh; refresh
$server->dns_retry ; retry
$server->dns_expire ; expire
$server->dns_negativettl ; minimum
)\n");
if (!empty($server->dns_default_mx)) {
if ($server->dns_default_mx[strlen($server->dns_default_mx)-1] != '.') {
$server->dns_default_mx .= '.';
}
print "@\tIN\tMX\t10\t" . $server->dns_default_mx . "\n";
}
print "@\tIN\tNS\t" . $server->remote_host;
if ($server->remote_host[strlen($server->remote_host)-1] != '.') {
print '.';
}
print " ; primary DNS\n";
if (is_array($server->slave_servers_names)) {
foreach ($server->slave_servers_names as $slave) {
if ($slave[strlen($slave)-1] != '.') {
$slave .= '.';
}
print "@\tIN\tNS\t" . $slave . " ; slave DNS\n";
}
}
foreach ($records['@'] as $type => $destinations) {
if ($type != 'SOA' && $type != 'NS') {
foreach ($destinations as $destination) {
print "@\tIN\t$type\t$destination\n";
}
}
}
foreach ($records as $name => $record) {
if ($name != '@') {
foreach ($record as $type => $destinations) {
foreach ($destinations as $destination) {
print "$name\tIN\t$type\t$destination\n";
}
}
}
}
foreach ($hosts as $host => $info) {
foreach ($info['A'] as $ip) {
print "{$info['sub']} IN A {$ip}\n";
}
}
This diff is collapsed.
<?php
/**
* Implementation of the DNS service through BIND9
*
* A lot of this is inspired by the Apache implementation of the HTTP service.
*/
class Provision_Service_dns_bind extends Provision_Service_dns {
protected $application_name = 'bind';
protected $has_restart_cmd = TRUE;
private $zone_cache = array();
static function bind_default_restart_cmd() {
return "rndc reload";
}
function default_restart_cmd() {
return Provision_Service_dns_bind::bind_default_restart_cmd();
}
function init_server() {
parent::init_server();
$this->configs['server'][] = 'Provision_Config_Bind_Server';
$this->configs['zone'][] = 'Provision_Config_Bind_Zone';
}
function parse_configs() {
$status = $this->restart();
return $status && parent::parse_configs();
}
}
<?php
/**
* Implementation of a slave DNS service through BIND9
*
* A lot of this is inspired by the BIND implementation of the DNS service and
* the cluster HTTP service.
*/
class Provision_Service_dns_bind_slave extends Provision_Service_dns {
protected $application_name = 'bind';
protected $has_restart_cmd = TRUE;
function default_restart_cmd() {
return Provision_Service_dns_bind::bind_default_restart_cmd();
}
function init_server() {
parent::init_server();
$this->configs['server'][] = 'Provision_Config_Bind_slave';
}
function parse_configs() {
$this->restart();
}
function verify_server_cmd() {
if (!is_null($this->application_name)) {
provision_file()->create_dir($this->server->dns_zoned_path, dt("DNS slave zone configuration"), 0775);
$this->sync($this->server->dns_zoned_path, array(
'exclude' => $this->server->dns_zoned_path . '/*', // Make sure remote directory is created
));
$this->create_config('server');
}
}
/**
* Create the zonefile record on the slave server
*
* Contrarily to the parent class implementation, this *only* creates the
* bind config (managed through the Provision_Config_Bind_Slave class), and no
* zonefile, because the zonefile should be managed by bind itself through
* regular zone transfers.
*
* Note that this function shouldn't be called directly through the API, but
* only from the master server's create_zone() function.
*
* @arg $zone string the zonefile name to create
*
* @see Provision_Service_dns::create_zone()
*/
function create_zone($zone = null) {
if (is_null($zone) && ($this->context->type == 'site')) {
$host = $this->context->uri;
$zone = $this->context->dns_zone;
$sub = $this->context->dns_zone_subdomain;
}
if (empty($zone)) {
return drush_set_error('DRUSH_DNS_NO_ZONE', "Could not determine the zone to create");
}
drush_log(dt("recording zone in slave configuration"));
$status = $this->config('server')->record_set($zone, $zone)->write();
return $status;
}
/**
* This completely drops a zone, without any checks.
*/
function delete_zone($zone) {
return $this->config('server')->record_del($zone, $zone)->write();
}
}
<?php
class Provision_Service_dns_dnsmasq extends Provision_Service_dns {
protected $application_name = 'dnsmasq';
protected $has_restart_cmd = TRUE;
function default_restart_cmd() {
return 'sudo /etc/init.d/dnsmasq restart';
}
function init_server() {
parent::init_server();
$this->configs['server'][] = 'Provision_Config_Dnsmasq_Server';
$this->configs['zone'][] = 'Provision_Config_Dnsmasq_Zone';
$this->configs['host'][] = 'Provision_Config_Dnsmasq_Host';
}
function parse_configs() {
$this->restart();
}
function create_host($host = NULL) {
parent::create_host($host);
$this->create_config('host');
}
}
name = Provision: BIND
description = Provides provisioning requirements for Bind servers
package = Provision
dependencies[] = provision
core = 6.x
name = Provision: BIND slaves
description = Provides provisioning requirements for Bind slave servers
package = Provision
dependencies[] = provision
core = 6.x
<?php
// $Id: delete.provision.inc,v 1.3 2009/05/07 22:04:30 adrian Exp $
/**
* @file
* Provision hooks for the delete command
**/
function drush_dns_post_provision_delete() {
if (d()->type == 'site') {
d()->service('dns')->delete_host();
d()->service('dns')->parse_configs();
}
}
<?php
// $Id: dns.drush.inc,v 1.4 2009/03/20 16:13:24 adrian Exp $
/**
* @file
* DNS provisioning module.
*
* The goal of this module is to manage DNS zonefiles and Resource Records
* (RRs), for sites that are about to be created. It uses the provision API to
* tie into the right places in the site creation work flow.
*/
//include_once(dirname(__FILE__) . '/../provision.service.inc');
/**
* Register our directory as a place to find provision classes.
*/
function dns_provision_register_autoload() {
static $loaded = FALSE;
if (!$loaded) {
$loaded = TRUE;
provision_autoload_register_prefix('Provision_', dirname(__FILE__));
}
}
/**
* Implements hook_drush_init().
*/
function dns_drush_init() {
dns_provision_register_autoload();
}
/**
* Implementation of hook_drush_command().
*/
function dns_drush_command() {
$items['provision-zone'] = array(
'arguments' => array('operation' => dt('The operation to perform on a zone (verify, delete, rr-add, rr-delete)')),
'description' => dt('Manipulate a zonefile'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
);
return $items;
}
function drush_dns_provision_zone($action, $zone, $name = null, $type = null, $destination = null) {
switch ($action) {
case 'create-host':
$status = d()->service('dns')->create_host($zone);
break;
case 'delete-host':
$status = d()->service('dns')->delete_host($zone);
break;
case 'verify':
case 'create':
$status = d()->service('dns')->create_zone($zone);
break;
case 'delete':
$status = d()->service('dns')->delete_zone($zone);
break;
case 'rr-add':
$record = d()->service('dns')->config('zone', $zone)->record_get($name);
if (!is_array($record[$type])) {
$record[$type] = array();
}
$record[$type] = array_merge($record[$type], array($destination));
$status = d()->service('dns')->config('zone', $zone)->record_set($name, $record)->write();
break;
case 'rr-modify':
$status = d()->service('dns')->config('zone', $zone)->record_set($name, array($type => array($destination)))->write();
break;
case 'rr-delete':
if ($type) {
$status = d()->service('dns')->config('zone', $zone)->record_set($name, array($type => NULL))->write();
} else {
$status = d()->service('dns')->config('zone', $zone)->record_set($name, NULL)->write();
}
break