WIP : DNSSEC Support

This commit is contained in:
Kienan Stewart 2018-12-22 14:54:52 -05:00
parent db916ace66
commit a9a5b2d1de
10 changed files with 757 additions and 37 deletions

View File

@ -105,7 +105,6 @@ if ($r['dns_action']=='UPDATE') {?>
include_once("foot.php");
die();
}
if (! empty($r['dns_result']) && $r['dns_result'] != '0') {
if ($r['dns_result'] == 1) $r['dns_result'] =_("DNS zone is locked, changes will be ignored");
echo '<p class="alert alert-warning">'; __($r['dns_result']); echo '</p>';
@ -123,6 +122,9 @@ if (! empty($r['dns_result']) && $r['dns_result'] != '0') {
<?php if ( $r["dns"] ) { ?>
<li class="view"><a href="#tabsdom-view" onClick="update_dns_content();"><?php __("View");?></a></li>
<?php } //if gesdns ?>
<?php if ($r['dnssec']): ?>
<li class="dnssec"><a href="#tabsdom-dnssec"><?php __('DnsSec');?></a></li>
<?php endif; ?>
<li class="delete"><a href="#tabsdom-delete"><?php __("Delete");?></a></li>
</ul>
@ -322,6 +324,27 @@ if (!$r['noerase']) {
</tr>
</table>
<p class="alert alert-warning"> <?php __("Warning: If you set this to 'no', all your email accounts and aliases on this domain will be immediately deleted."); ?></p>
<div class="dnssec-configuration">
<span><?php __("Enable DnsSec for this domain?"); ?></span>
<span><input type="checkbox" name="dnssec" value="1" <?php if($r['dnssec']) { echo "checked"; } ?>/></span>
<div>
<p class="alert alert-warning"><?php __("If configuring DnsSec for the first time, ensure that your registrar can configure DS records for your domain. If enabling DnsSec on a subdomain, DS records will need to be added to the parent domain instead.");?>
<br><br>
<?php __("If disabling DnsSec make sure to remove DS records recorded with your registrar or in the parent domain.");?>
<br><br>
<?php __("Initial KSK/ZSK configuration is determined by the AlternC settings. Once enabled, keys may be managed in the \"DnsSec\" tab");?>
<br><br>
<?php $dnssec_defaults = system_bind::default_dnssec_configuration(); ?>
<?php __("Key-siging keys will be {$dnssec_defaults['ksk']['algorithm']} of length {$dnssec_defaults['ksk']['keysize']}. Zone-signing keys will be {$dnssec_defaults['zsk']['algorithm']} of length {$dnssec_defaults['zsk']['keysize']}."); ?>
</p>
</div>
</div>
<?php if ($admin->enabled || intval($oldid)): ?>
<div class="ignore-dns-checks">
<span><?php __('Ignore whois check?'); ?></span>
<span><input type="checkbox" name="force" value="1"/></span>
</div>
<?php endif; ?>
<input type="submit" class="inb ok" name="submit" value="<?php __("Submit the changes"); ?>" />
</form>
@ -350,6 +373,111 @@ if (!$r['noerase']) {
</div>
<?php } // if dns ?>
<?php if ($r['dnssec']): ?>
<div id="tabsdom-dnssec">
<div>
<h3><?php __('DS Entries'); ?></h3>
<p><?php __('Here are the current DS entries for your zone. To finish activating DNSSEC on your domain, they must be uploaded to your registrar, or added as DS entries on the parent zone file.');?></p>
<pre><?php echo system_bind::dnssec_ds_entries(); ?></pre>
</div>
<div>
<h3><?php __('Existing DnsSec Keys'); ?></h3>
<form action="dom_editdnsseckeys.php" id="fdnsseckeys" name="fdsnsseckeys" method="post">
<!-- First pass: don't enforce workflow, just give options -->
<input type="hidden" name="domain" value="<?php echo urlencode($r['name']); ?>"/>
<?php $keys = system_bind::list_keys($r['name']); ?>
<?php if (!empty($keys)): ?>
<table>
<?php $attr_list = array('Created', 'Publish', 'Activate',
'Revoke', 'Inactive', 'Delete'); ?>
<tr>
<th><?php __('Key'); ?></th>
<?php foreach ($attr_list as $attr): ?>
<th><?php __($attr); ?></th>
<?php endforeach; ?>
</tr>
<?php foreach ($keys as $key): ?>
<?php $key_data = system_bind::get_key_metadata($key); ?>
<?php $key_base = basename($key); ?>
<td><?php print($key_base); ?></td>
<?php foreach ($attr_list as $attr): ?>
<td>
<!-- @TODO: Is there a nice datetime edit element? -->
<input type="datetime-local"
name="key[<?php echo $key_base; ?>][<?php echo $attr; ?>]"
value="<?php echo strftime("%Y-%m-%dT%H:%M", $key_data[$attr]); ?>"
/>
</td>
<?php endforeach; ?>
<?php endforeach; ?>
</table>
<button type="submit" class="inb" value="<?php __('Submit'); ?>"/>
<?php else: ?>
<p><?php __('No keys found for the domain'); ?></p>
<?php endif; ?>
</form>
</div>
<div>
<h3><?php __('Key generation settings for your domain'); ?></h3>
<?php $key_conf = $dom->get_dnssec_key_generation_parametrs; $valid_conf = system_bind::$dns_key_limits; ?>
<form action="dom_editdnssec.php" method="post" id="fdnssec" name="fdnssec">
<?php csrf_get(); ?>
<input type="hidden" name="domain" value="<?php echo urlencode($r['name']); ?>"/>
<?php $keys = array(
'ksk' => array(
'title' => _('Key-Signing Key Paramaters'),
'validation_type' => 'KSK/ZSK'),
'zsk' => array(
'title' => _('Zone-Signing Key Parameters'),
'validation_type' => 'KSK/ZSK')
); ?>
<?php foreach ($keys as $key_type => $attrs): ?>
<h4><?php echo $attrs['key_title']; ?></h4>
<label>
<?php __('Algorithm'); ?>
<select name="<?php echo $key_type; ?>_algorithm">
<?php foreach($valid_conf[$attrs['validation_type']] as $algo => $v): ?>
<?php $selected = ($algo == $key_config[$key_type]['algorithm']); ?>
<option
value="<?php echo $algo; ?>"
<?php echo $selected ? "selected" : "";?>
>
<?php echo $algo; ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label>
<?php __('Key Size'); ?>
<select name=<?php echo $key_type; ?>_keysize">
<?php // @TODO Change key sizes available when algorithm selected changes ?>
<?php foreach(array(512, 1024, 2048) as $keysize): ?>
<?php $selected = ($keysize == $key_config[$key_type]['keysize']); ?>
<option
value="<?php echo $keysize; ?>"
<?php echo $selected ? "selected" : ""; ?>
>
<?php echo $keysize; ?>
</option>
<?php endforeach; ?>
</select>
</label>
<?php endforeach; ?>
<label><?php __('Force key re-generation'); ?>
<input type="checkbox" name="regen-keys">
</label>
<p class="alert alert-warning">
<?php __('When creating new keys you must manually managed key-rollover.'); ?>
<?php __('All keys are added to the zone.'); ?>
</p>
<button type="submit" class="inb" value="<?php __('Submit'); ?>"/>
<!-- @TODO: Implement form submission handling for KSK/ZSK algo change + key regen -->
</form>
</div>
</div>
<?php endif; ?>
<?php
if (!$r['noerase']) {
?>

View File

@ -31,19 +31,32 @@ $fields = array (
"dns" => array ("post", "integer", 1),
"email" => array ("post", "integer", 1),
"ttl" => array ("post", "integer", 86400),
'dnssec' => array ('post', 'boolean', FALSE),
'force' => array ('post', 'boolean', FALSE),
);
getFields($fields);
$dom->lock();
$r = $dom->get_domain_all($domain);
if ($r["dns"] == $dns && $r["mail"] == $email && $r["zonettl"] == $ttl) {
if ($r["dns"] == $dns && $r["mail"] == $email && $r["zonettl"] == $ttl && $r['dnssec'] == $dnssec) {
$msg->raise("INFO", "dom", _("No change has been requested..."));
} else if ($dom->edit_domain($domain,$dns,$email,0,$ttl)) {
} else if ($dom->edit_domain($domain,$dns,$email,$force,$ttl, $dnssec)) {
$msg->raise("INFO", "dom", _("The domain %s has been changed."),$domain);
$t = time();
// TODO: we assume the cron job is at every 5 minutes
$msg->raise("INFO", "dom", _("The modifications will take effect at %s. Server time is %s."), array(date('H:i:s', ($t-($t%300)+300)), date('H:i:s', $t)));
// Reload the domain information to see if DnsSec changes were done.
$dnssec_change_requested = $r['dnssec'] != $dnssec;
$r = $dom->get_domain_all($domain);
if ($dnssec_change_requested && ($r['dnssec'] == $dnssec)) {
if ($r['dnssec']) {
$msg->raise('INFO', 'dom', _('You have enabled DnsSec: Once the keys are generated and the zone signed you must get the DS entries from the DnsSec tab and upload them to your registrar, or enter them in to the parent zone.'));
}
else {
$msg->raise('INFO', 'dom', _('You have disabled DnsSec: Make sure to remove DS entries from your registrar or the parent zone.'));
}
}
}
$dom->unlock();

View File

@ -37,8 +37,49 @@ class system_bind {
var $cache_get_persistent = array();
var $cache_zone_file = array();
var $cache_domain_summary = array();
// @Note: The trailing slash is required on this variable since other code
// assumes it is present when generating file paths.
var $zone_file_directory = '/var/lib/alternc/bind/zones/';
static public $DNSSEC_KEY_BASEDIR = "/var/lib/alternc/bind/keys";
static public $DNSSEC_SETFILE_BASEDIR = "/var/lib/alternc/bind/setfiles";
static public $dnssec_key_limits = array(
'KSK/ZSK' => array(
// RSAMD5 should never be used, though it could be valid for dnssec-keygen.
// RSASHA1 should never be used, thought it could be valid.
'NSEC3RSASHA1' => array(
'min' => 512,
'max' => 2048,
),
'NSEC3DSA' => array(
'min' => 512,
'max' => 1024,
'other' => '%64', // Exact multiple of 64.
),
'RSASHA256' => array(
'min' => 512,
'max' => 2048,
),
'RSASHA512' => array(
'min' => 512,
'max' => 2048,
),
'ECDSAP256SHA256' => array(
// Keysize parameter is ignored.
'min' => 0,
'max' => 10000,
),
'ECDSAP384SHA384' => array(
// Keysize parameter is ignored.
'min' => 0,
'max' => 10000,
),
),
'TSIG/TKEY' => array(
// No supported algorithms presently.
),
);
/**
* Return the part of the conf we got from the database
@ -83,10 +124,30 @@ class system_bind {
* Return full path of the zone configuration file
*
* @param string $domain
*
* @param boolean $signed
* Whether to return the signed or unsigned zone file path. Ignored
* if force is FALSE.
*
* @param boolean $force
* If FALSE, the zone file path returned will be in accordance with the
* configuration for the domain in the database. If forced, the domain
* configuratoin will be ignored and the path returned according to the
* signed parameter.
*
* @return string
*/
function get_zone_file_uri($domain) {
return $this->zone_file_directory.$domain;
function get_zone_file_uri($domain, $signed = FALSE, $force = FALSE) {
if (!$force) {
$d = $this->get_domain_summary($domain);
$signed = $d['dnssec'];
}
if (!$signed) {
return $this->zone_file_directory.$domain;
}
else {
return "{$this->zone_file_directory}{$domain}.signed";
}
}
@ -285,6 +346,86 @@ class system_bind {
}
/**
* Outputs the include statements for a zone's DnsSec keys if enabled.
*
* @param string $domain
* @returns string
* Include statements for all the keys found or an empty ztring.
*/
function dnssec_entry($domain) {
$includes = '';
$key_directory = system_bind::$DNSSEC_KEY_BASEDIR . "/{$domain}";
$dnssec_enabled = $this->dnssec_is_enabled($domain);
if ($dnssec_enabled && is_dir($key_directory)) {
foreach (glob("{$key_directory}/K{$domain}*.key") as $f) {
if (!is_file($f)) {
continue;
}
$includes .= "\$INCLUDE {$f}\n";
}
}
return $includes;
}
/**
* Checks if dnssec is enabled for a domain.
*
* @param $domain
* A string for which domain to check.
*
* @returns boolean
* True/False if DnsSec is enabled for the domain.
*/
function dnssec_is_enabled(String $domain) {
$d = $this->get_domain_summary($domain);
$enabled = false;
if ($d && $d['dnssec']) {
$enabled = true;
}
return $enabled;
}
/**
* Signs a zone.
*/
function dnssec_sign_zone(String $domain) {
global $msg;
if (!$domain) {
return;
}
// Keys should already exist.
$key_dir = system_bind::$DNSSEC_KEY_BASEDIR . "/{$domain}";
$setfile_dir = system_bind::$DNSSEC_SETFILE_BASEDIR . "/{$domain}";
if (!is_dir($setfile_dir)) {
mkdir($setfile_dir, '0750', TRUE);
}
$return_code = -1;
$output = array();
$salt = _salt16hex();
if (!$salt) {
$salt = '-';
}
// The unsigned_zone_file should be an absolute path to the zone file.
// The $INCLUDE statements in the the zone file _MUST_ use absolute
// paths, otherwise, the CWD of the dnssec-signzone command must be
// changed.
$unsigned_zone_file = $this->get_zone_file_uri($domain, FALSE, TRUE);
// @TODO Should '-Q' and '-R' be used to allow for key roll overs?
$command = sprintf('/usr/sbin/dnssec-signzone -u -d %s -K %s -3 %s -A -N INCREMENT -o %s %s',
escapeshellarg($setfile_dir), escapeshellarg($key_dir),
escapeshellarg($salt), escapeshellarg($domain),
escapeshellarg($unsigned_zone_file));
$last_line = exec(escapeshellcmd($command), $output, $return_code);
$msg->log('system_bind', 'dnssec_sign_zone',
sprintf('Signed zone %s using command "%s". Return code %d; Last line of output: %s',
$domain, $command, $return_code, $last_line)
);
return $return_code;
}
/**
*
@ -351,6 +492,7 @@ class system_bind {
$zone.= $this->dkim_entry($domain);
$zone.= $this->mail_autoconfig_entry($domain);
$zone .= $this->dnssec_entry($domain);
$zone.="\n;;; END ALTERNC AUTOGENERATE CONFIGURATION\n";
$zone.=$this->get_persistent($domain);
@ -423,11 +565,16 @@ class system_bind {
}
// Save file, and apply chmod/chown
$file=$this->get_zone_file_uri($domain);
// Always want to save the unsigned version.
$file=$this->get_zone_file_uri($domain, FALSE, TRUE);
file_put_contents($file, $this->get_zone($domain));
chown($file, 'bind');
chmod($file, 0640);
$d = $this->get_domain_summary($domain);
if ($d['dnssec']) {
$this->dnssec_sign_zone($domain);
}
// @TODO: What should happen if zone signing fails?
$dom->set_dns_action($domain, 'OK');
return true; // fixme add tests
}
@ -440,9 +587,24 @@ class system_bind {
* @return boolean
*/
function delete_zone($domain) {
$file=$this->get_zone_file_uri($domain);
if (file_exists($file)) {
unlink($file);
// get_zone_file_uri() is used since it's result won't be cached.
$files = array(
$this->get_zone_file_uri($domain, FALSE, TRUE),
$this->get_zone_file_uri($domain, TRUE, TRUE),
);
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
}
}
$dnssec_directories = array(
system_bind::$DNSSEC_KEY_BASEDIR . "/{$domain}",
system_bind::$DNSSEC_SETFILE_BASEDIR . "/{$domain}",
);
foreach ($dnssec_directories as $dir) {
if ($domain && is_dir($dir)) {
rmdir($dir);
}
}
$this->dkim_delete($domain);
return true;
@ -521,5 +683,192 @@ class system_bind {
}
} /* Class system_bind */
/**
* Returns the current default configuration for dnssec.
*
* @returqns array
* An array with two indexes 'ksk' (keysigning key), 'zsk' (zonesigning key).
* Each key type is also an array with the properities 'algorithm', and 'size'.
* Algorithm should match the named parameters for dnssec-keygen, and size should
* be an integer.
*/
public static function default_dnssec_configuration() {
$data = array(
'ksk' => array(
'algorithm' => variable_get('ksk_algorithm', 'RSASHA512', ''),
'keysize' => variable_get('ksk_keysize', 2048, ''),
),
'zsk' => array(
'algorithm' => variable_get('zsk_algorithm', 'RSASHA512', ''),
'keysize' => variable_get('zsk_keysize', 2048, ''),
),
);
return $data;
}
/**
* Returns the DS entries for a domain.
*
* @returns string
* The output of dnssec-dsfromkey.
*/
public static function dnssec_ds_entries($domain) {
return shell_exec("/usr/sbin/dnssec-dsfromkey -A -f {$this->zone_file_directory}/{$domain}");
}
/**
* Creates new key for a domain.
*
* @param $domain string
* The domain to create the key for.
*
* @param $key_type string
* One of 'ksk' or 'zsk' for key-sigining keys or zone-sigining keys.
*
* @param $algorithm string
* The name of algorithm as recognized by dnssec-genkey.
*
* @param $length int
* The length of the key in bytes. Note: for elliptic curve keys this
* parameter is ignored.
*
* @returns string|bool
* Returns the full path to the keyfile created on success, otherwise FALSE
* is returned on a failure.
*/
public static function dnssec_create_key($domain, $key_type, $algorithm, $length) {
global $msg;
$valid = system_bind::validate_key_parameters($key_type, $algorithm, $length);
if (!$valid) {
$msg->raise('ERROR', 'system_bind', _("Key generation parameters for {$domain} are invalid. Please check the domain and server default configuration. Type: {$key_type}; Algorithm: {$algorithm}; Key size: {$length}"));
return FALSE;
}
$ksk = '';
if ($key_type == 'ksk') {
$ksk = ' -f KSK ';
}
$output = array();
$return_code = -1;
$key_dir = system_bind::$DNSSEC_KEY_BASEDIR . '/' . $domain;
// @TODO: This fails because the keys directory is owned and only readable by root.
// @TODO: Furthermore, this should probably be called during update, when the scripts
// aren't running as the alterncpanel user.
// @TODO: Similar problems probably exist for the set files.
if (!is_dir($key_dir)) {
if (!mkdir($key_dir, 0750, TRUE)) {
$msg->raise('ERROR', 'system_bind', _('Unable to create key storage directory'));
$msg->log('system_bind', 'dnssec_create_key', "Unable to create directory '{$key_dir}'");
return FALSE;
}
}
$command = sprintf("/usr/sbin/dnssec-keygen -q{$ksk} -a %s -b %s -n ZONE -K %s %s",
escapeshellarg($algorithm), escapeshellarg($length),
escapeshellarg($key_dir), escapeshellarg($domain));
$file_name = exec(escapeshellcmd($command), $output, $return_code);
$msg->log('system_bind', 'dnssec_create_key',
sprintf('Executed command "%s". Return code %d ; Last line of output: %s',
$command, $return_code, $file_name)
);
if ($return_code == 0) {
return "{$key_dir}/{$file_name}.key";
}
else {
$output = implode("\n", $output);
$msg->log('system_bind', 'dnssec_create_key', "Full output: {$output}");
}
return FALSE;
}
public static function validate_key_parameters($key_type, $algorithm, $length) {
global $msg;
if (in_array($key_type, array('ksk', 'zsk'))) {
if (!in_array($algorithm,array_keys(system_bind::$dnssec_key_limits['KSK/ZSK']))) {
// Unsupported algorithm.
$msg->debug('system_bind', 'validate_key_parameters',
"Key validation failed: unknown algorithm for ksk/zsk {$algorithm}");
return FALSE;
}
$min = system_bind::$dnssec_key_limits['KSK/ZSK'][$algorithm]['min'];
$max = system_bind::$dnssec_key_limits['KSK/ZSK'][$algorithm]['max'];
if ($length >= $min && $length <= $max) {
if (isset(system_bind::$dnssec_key_limits['KSK/ZSK'][$algorithm]['other'])) {
// Only have one "other" check for the moment. Could do more
// flexible parsing here.
$other = system_bind::$dnssec_key_limits['KSK/ZSK'][$algorithm]['other'];
if ($other == '%64') {
return $length % 64 == 0;
}
$msg->debug('system_bind', 'validate_key_parameters',
"Key validation failed: unknown other condition '{$other}'");
}
else {
return TRUE;
}
}
else {
$msg->debug('system_bind', 'validate_key_parameters',
"Key validation failed: keysize ({$length} outside of bounds {$min} and {$max}");
}
}
else {
$msg->debug('system_bind', 'validate_key_parameters',
"Key validation failed: unknown key type '{$key_type}'");
}
return FALSE;
}
/**
* Lists all the existing keys for a given domain.
*
* @param $domain
* The domain name
*
* @returns array
* An array, possibly empty, of all existing keys for the domain.
* The array elements are full paths to the keys.
*/
public static function list_keys($domain) {
$r = array();
if (!$domain) {
return $r;
}
$key_dir = system_bind::$DNSSEC_KEY_BASEDIR . '/' . $domain;
if (!is_dir($key_dir)) {
return $r;
}
$r = glob("{$key_dir}/K{$domain}*.key");
return $r;
}
/**
* Gets the timing metadata for a given key.
*
* @param $key_file
* Full path to the key file.
*
* @returns array
* An array the metadata properties indexed by name: Created, Publish
* Activate, Revoke, Inactive, Delete with the data being NULL (for unset)
* or a time in seconds since the UNIX epoch.
*/
public static function get_key_metadata($key_file) {
global $msg;
$data = array();
if (!is_file($key_file)) {
return $data;
}
$output = shell_exec(escapeshellcmd("LANG=C /usr/sbin/dnssec-settime -u -p all {$key_file}"));
$lines = explode(PHP_EOL, $output);
foreach ($lines as $line) {
$r = explode(": ", $line);
if ($r && length($r) == 2) {
$data[$r[0]] = $r[1];
}
else {
$msg->log('system_bind', 'get_key_metadata', "Warning: output line for dnssec-settime for key {$key_file} does not have expected form: \"{$line}\"");
}
}
return $data;
}
} /* Class system_bind */

View File

@ -1213,26 +1213,58 @@ function csrf_check($token=null) {
function _sha512cr($password, $salt = NULL) {
if (!$salt) {
// Aim to have a 16 character salt for SHA-512 crypt.
// @see https://secure.php.net/manual/en/function.crypt.php
if (function_exists('random_bytes')) {
// PHP >= 7.0
$salt = base64_encode(random_bytes(12));
}
else if (function_exists('mcrypt_create_iv')) {
$salt = base64_encode(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
}
else if (function_exists('openssl_random_pseudo_bytes')) {
$salt = base64_encode(openssl_random_pseudo_bytes(12));
}
if (!$salt) {
throw Exception('Unable to generate salt');
}
$salt = _salt16();
}
$salt = '$6$rounds=20000$' . $salt;
$hash = crypt($password, $salt);
return $hash;
}
/**
* Generate a 16 character base64 encoded salt.
*
* @returns string
*/
function _salt16() {
$salt = '';
// @see https://secure.php.net/manual/en/function.crypt.php
if (function_exists('random_bytes')) {
// PHP >= 7.0
$salt = base64_encode(random_bytes(12));
}
else if (function_exists('mcrypt_create_iv')) {
$salt = base64_encode(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
}
else if (function_exists('openssl_random_pseudo_bytes')) {
$salt = base64_encode(openssl_random_pseudo_bytes(12));
}
if (!$salt) {
throw Exception('Unable to generate salt');
}
return $salt;
}
/**
* Generate a 16 character hex ewncoded salt.
*/
function _salt16hex() {
$salt = '';
if (function_exists('random_bytes')) {
// PHP >= 7.0
$salt = random_bytes(8);
}
else if (function_exists('mcrypt_create_iv')) {
$salt = mcrypt_create_iv(8, MCRYPT_DEV_URANDOM);
}
else if (function_exists('openssl_random_pseudo_bytes')) {
$salt = openssl_random_pseudo_bytes(8);
}
if (!$salt) {
throw Exception('Unable to generate salt');
}
return bin2hex($salt);
}
/**
* Create a password hash for use with dovecot.
*/

View File

@ -1064,6 +1064,11 @@ class m_dom {
$r["mail"] = $db->Record["gesmx"];
$r["zonettl"] = $db->Record["zonettl"];
$r['noerase'] = $db->Record['noerase'];
$r['dnssec'] = $db->Record['dnssec'];
$r['ksk_algorithm'] = $db->Record['ksk_algorithm'];
$r['ksk_keysize'] = $db->Record['ksk_keysize'];
$r['zsk_algorithm'] = $db->Record['zsk_algorithm'];
$r['zsk_keysize'] = $db->Record['zsk_keysize'];
$db->free();
$db->query("SELECT COUNT(*) AS cnt FROM sub_domaines WHERE compte= ? AND domaine= ?;", array($cuid, $dom));
$db->next_record();
@ -1437,12 +1442,14 @@ class m_dom {
* @param boolean $dns Vaut 1 ou 0 pour héberger ou pas le DNS du domaine
* @param boolean $gesmx Héberge-t-on le emails du domaines sur ce serveur ?
* @param boolean $force Faut-il passer les checks DNS ou MX ? (admin only)
* @param boolean $dnssec
* Whether or not DnsSec should be enabled for the domain.
* @return boolean appelle $mail->add_dom ou $ma->del_dom si besoin, en
* fonction du champs MX. Retourne FALSE si une erreur s'est produite,
* TRUE sinon.
*
*/
function edit_domain($dom, $dns, $gesmx, $force = false, $ttl = 86400) {
function edit_domain($dom, $dns, $gesmx, $force = false, $ttl = 86400, $dnssec = FALSE) {
global $db, $msg, $hooks;
$msg->log("dom", "edit_domain", $dom . "/" . $dns . "/" . $gesmx);
// Locked ?
@ -1486,7 +1493,7 @@ class m_dom {
$dns = "0";
}
// On vérifie que des modifications ont bien eu lieu :)
if ($r["dns"] == $dns && $r["mail"] == $gesmx && $r["zonettl"] == $ttl) {
if ($r["dns"] == $dns && $r["mail"] == $gesmx && $r["zonettl"] == $ttl && $r['dnssec'] == $dnssec) {
$msg->raise("INFO", "dom", _("No change has been requested..."));
return true;
}
@ -1504,6 +1511,30 @@ class m_dom {
}
}
$dnssec_action = 'OK';
if ($dns == "1") {
// Activating or de-activating DnsSec
if ($r['dnssec'] != $dnssec) {
if ($dnssec == 1) {
$dnssec_action = 'CREATE';
//$r = $this->generate_dnssec_keys($dom);
//$msg->raise('ERROR', 'dom', _('Failed to generate on or more of the zone DnsSec keys. DnsSec has been disabled on this domain. Please contact the administrator.'));
// Disable DnsSec since there were generation errors.
//$dnssec = FALSE;
}
else {
// @TODO: Should the existing keys and setfiles be removed?
// That might be a headache for folks who make a mistake in
// the interface, necessitating that they go and update DS
// records at their registrar or in parents zones.
}
}
}
else if ($dns == "0" && $r['dns'] == 1 ) {
// We should de-activate DnsSec for domains where DNS is not managed.
$dnssec = 0;
}
if ($gesmx && !$r["mail"]) {
$hooks->invoke("hook_dom_add_mx_domain", array($r["id"]));
}
@ -1512,12 +1543,72 @@ class m_dom {
$hooks->invoke("hook_dom_del_mx_domain", array($r["id"]));
}
$db->query("UPDATE domaines SET gesdns= ?, gesmx= ?, zonettl= ? WHERE domaine= ?", array($dns, $gesmx, $ttl, $dom));
$db->query("UPDATE domaines SET gesdns= ?, gesmx= ?, zonettl= ?, dnssec= ?, dnssec_action= ? WHERE domaine= ?", array($dns, $gesmx, $ttl, $dnssec, $dnssec_action, $dom));
$this->set_dns_action($dom, 'UPDATE');
return true;
}
/**
* Generate DnsSec keys for a domain given the domain's current settings.
*
* @param $dom
* The domain.
*
* @returns bool
* TRUE on success, FALSE on failure.
*/
public function generate_dnssec_keys($dom) {
global $msg;
$key_configuration = $this->get_dnssec_key_generation_parameters($dom);
$generation = TRUE;
foreach ($key_configuration as $key_type => $key_parameters) {
// The path to the key is ignored on success.
$rval = system_bind::dnssec_create_key($dom, $key_type,
$key_parameters['algorithm'],
$key_parameters['keysize']);
if ($rval === FALSE) {
$generation = FALSE;
}
}
return $generation;
}
/**
* Get current DnsSec key generation parameters for a domain.
*
* @param $dom
* The domain.
*
* @returns array
* An array indexed by key type (eg. 'ksk'), each containing two keys:
* algorithm and keysize.
*/
public function get_dnssec_key_generation_parameters($dom) {
global $db;
$db->query('select ksk_algorithm, ksk_keysize, zsk_algorithm, zsk_keysize from domaines where domaine = ?', array($dom));
if (!$db->next_record()) {
$msg->raise('ALERT', 'dom', 'Unable to get dnssec key parameters for domain ' . $dom);
return FALSE;
}
$domain_key_configuration = array(
'ksk' => array(
'algorithm' => $db->f('ksk_algorithm'),
'keysize' => $db->f('ksk_keysize'),
),
'zsk' => array(
'algorithm' => $db->f('zsk_algorithm'),
'keysize' => $db->f('zsk_keysize'),
),
);
// Remove any NULL values and merge. It's possible that this creates
// configurations that are not allowed (eg. DSA key with a large key
// size). There should be checking during the actual creation, but also
// when per-domain configuration for KSK/ZSK parameters is saved.
$key_configuration = system_bind::default_dnssec_configuration() +
array_filter($domain_key_configuration);
return $key_configuration;
}
/* Slave dns ip managment */
@ -1643,13 +1734,14 @@ class m_dom {
function get_domain_all_summary() {
global $db;
$res = array();
$db->query("SELECT domaine, gesdns, gesmx, dns_action, zonettl FROM domaines ORDER BY domaine");
$db->query("SELECT domaine, gesdns, gesmx, dns_action, zonettl, dnssec FROM domaines ORDER BY domaine");
while ($db->next_record()) {
$res[$db->f("domaine")] = array(
"gesdns" => $db->f("gesdns"),
"gesmx" => $db->f("gesmx"),
"dns_action" => $db->f("dns_action"),
"zonettl" => $db->f("zonettl"),
"dnssec" => $db->f('dnssec'),
);
}
return $res;

View File

@ -20,6 +20,15 @@ options {
allow-query { "internal"; };
allow-transfer { "allslaves"; };
recursion no;
# DnsSec configuration. This is close to defaults for bind9.
# To fully enable certain types of validation, managed-keys or trusted-keys
# should be added.
dnssec-enable yes;
dnssec-validation yes;
# Look-aside servers are no longer operational.
# Alternatively specific domains could be configured here.
dnssec-lookaside no;
};
acl "internal" {

View File

@ -116,6 +116,12 @@ CREATE TABLE IF NOT EXISTS domaines (
dns_action enum ('OK','UPDATE','DELETE') NOT NULL default 'UPDATE',
dns_result varchar(255) not null default '',
zonettl int(10) unsigned NOT NULL default '86400',
dnssec BOOLEAN NOT NULL DEFAULT FALSE,
dnssec_action ENUM ('OK', 'CREATE') NOT NULL DEFAULT 'OK',
ksk_algorithm ENUM ('NSEC3RSASHA1', 'NSEC3DSA', 'RSASHA256', 'RSASHA512', 'ECDSAP256SHA256', 'ECDSAP384SHA384') DEFAULT NULL,
ksk_keysize INT DEFAULT NULL,
zsk_algorithm ENUM ('NSEC3RSASHA1', 'NSEC3DSA', 'RSASHA256', 'RSASHA512', 'ECDSAP256SHA256', 'ECDSAP384SHA384') DEFAULT NULL,
zsk_keysize INT DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY (domaine)
) ENGINE=InnoDB;

View File

@ -0,0 +1,6 @@
ALTER TABLE domaines ADD `dnssec` BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE domaines ADD `dnssec_action` ENUM ('OK', 'CREATE') NOT NULL DEFAULT 'OK';
ALTER TABLE domaines ADD `ksk_algorithm` ENUM ('NSEC3RSASHA1', 'NSEC3DSA', 'RSASHA256', 'RSASHA512', 'ECDSAP256SHA256', 'ECDSAP384SHA384') DEFAULT NULL;
ALTER TABLE domaines ADD `ksk_keysize` INT DEFAULT NULL;
ALTER TABLE domaines ADD `zsk_algorithm` ENUM ('NSEC3RSASHA1', 'NSEC3DSA', 'RSASHA256', 'RSASHA512', 'ECDSAP256SHA256', 'ECDSAP384SHA384') DEFAULT NULL;
ALTER TABLE domaines add `zsk_keysize` INT DEFAULT NULL;

View File

@ -50,18 +50,54 @@ dns_get_zonettl() {
echo $zonettl
}
dns_sec_salt() {
openssl rand -hex 8 | tr -d "\n"
}
dns_sec_is_enabled() {
local domain="$1"
$MYSQL_DO "select dnssec FROM domaines where domaine='$domain';"
}
dns_sec_needs_keys() {
local domain="$1"
local r=$($MYSQL_DO "select dnssec_action FROM domaines where domaine='$domain';")
if [[ $r == "CREATE" ]]; then
echo "1"
else
echo "0"
fi
}
dns_sec_generate_keys() {
local domain="$1"
/usr/lib/alternc/generate_dnssec_keys.php "$domain"
r=$?
$MYSQL_DO "update domaines set dnssec_action = 'OK' where domaine='$domain';"
return $r
}
dns_chmod() {
local domain=$1
chgrp bind $(dns_zone_file $domain)
chmod 640 $(dns_zone_file $domain)
zone_file=$(dns_zone_file $domain)
chgrp bind $zone_file
chmod 640 $zone_file
if [ $(dns_sec_is_enabled $domain) -eq "1" ] ; then
chgrp bind "${zone_file}.signed"
chmod 640 "${zone_file}.signed"
fi
return 0
}
dns_named_conf() {
local domain=$1
if [ ! -f "$(dns_zone_file $domain)" ] ; then
echo Error : no file $(dns_zone_file $domain)
zone_file=$(dns_zone_file $domain)
if [ $(dns_sec_is_enabled $domain) -eq "1" ] ; then
zone_file="${zone_file}.signed"
fi
if [ ! -f "$zonefile" ] ; then
echo Error : no file "$zone_file"
return 1
fi
@ -70,7 +106,7 @@ dns_named_conf() {
if [ $? -ne 0 ] ; then
local tempo=$(cat "$NAMED_TEMPLATE")
tempo=${tempo/@@DOMAINE@@/$domain}
tempo=${tempo/@@ZONE_FILE@@/$(dns_zone_file $domain)}
tempo=${tempo/@@ZONE_FILE@@/$zone_file}
echo $tempo >> "$NAMED_CONF"
# Kindly ask Bind to reload its configuration
# (the zone file is already created and populated)
@ -86,7 +122,13 @@ dns_delete() {
# Delete the zone file
if [ -w "$(dns_zone_file $domain)" ] ; then
rm -f "$(dns_zone_file $domain)"
rm -f "$(dns_zone_file $domain)"
rm -f "$(dns_zone_file $domain).signed"
fi
# Delete the zones keys, if they exist.
if [[ ! -z "$domain" && -d "/var/lib/alternc/bind/keys/$domain" ]] ; then
rm -rf "/var/lib/alternc/bind/keys/$domain"
rm -rf "/var/lib/alternc/bind/setfiles/$domain"
fi
local reg_domain=${domain/./\\.}
@ -112,7 +154,7 @@ dns_regenerate() {
local domain=$1
local manual_tag=";;; END ALTERNC AUTOGENERATE CONFIGURATION"
local zone_file=$(dns_zone_file $domain)
local dnssec=$(dns_sec_is_enabled $domain)
# Check if locked
dns_is_locked "$domain"
if [ $? -eq 0 ]; then
@ -175,6 +217,21 @@ dns_regenerate() {
fi
##### OpenDKIM signature management - END
# Generate key files if needed.
dnssec_create_keys=$(dns_sec_needs_keys $domain)
if [[ "$dnssec_create_keys" -eq "1" ]] ; then
if ! dns_sec_generate_keys $domain; then
dnssec="0"
$MYSQL_DO "update domaines set dnssec = 0 where domaine='$domain';"
fi
fi
# Include key files for the zones if DnsSec is enabled for the zone.
if [[ "$dnssec" -eq "1" ]] ; then
for i in /var/lib/alternc/bind/keys/"$domain"/K"$domain"*.key ; do
file="$( echo -e "$file" ; echo "\$INCLUDE $i")"
done
fi
# Replace the vars by their values
# Here we can add dynamic value for the default MX
file=$( echo -e "$file" | sed -e "
@ -218,6 +275,13 @@ dns_regenerate() {
# Hook it !
run-parts --arg=dns_reload_zone --arg="$domain" /usr/lib/alternc/reload.d
# Sign it if DnsSec is enabled for the domain.
if [[ "$dnssec" -eq "1" ]] ; then
key_dir="/var/lib/alternc/bind/keys/$domain"
set_dir="/var/lib/alternc/bind/setfiles/$domain"
dnssec-signzone -u -3 "$(dns_sec_salt)" -d "$set_dir" -K "$key_dir" -A -N INCREMENT -o "$domain" "$zone_file"
fi
# ask bind to reload the zone
$RNDC reload $domain
}

21
src/generate_dnssec_keys.php Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/php -q
<?php
require_once('/usr/share/alternc/panel/class/config_nochk.php');
ini_set('display_errors', 1);
$dom = new m_dom();
if (isset($argv[1])) {
$domain = $argv[1];
if (!$domain) {
exit -1;
}
}
else {
exit -2;
}
if ($dom->generate_dnssec_keys($domain)) {
exit;
}
exit -3;