[enh] ssl class should work now, including system certificates and auto renewal of vhosts etc.

This commit is contained in:
Benjamin Sonntag 2018-06-23 16:28:50 +02:00
parent 8232c1a318
commit ac841451bc
4 changed files with 177 additions and 5 deletions

View File

@ -1415,7 +1415,7 @@ class m_dom {
return false;
}
}
$db->query("UPDATE sub_domaines SET provider=? WHERE id=?",array($provider,$sub_domain_id));
$db->query("UPDATE sub_domaines SET web_action=?, provider=? WHERE id=?",array("UPDATE",$provider,$sub_domain_id));
return true;
}

View File

@ -63,7 +63,7 @@ class m_ssl {
* used by providers to get the certs they should generate
* also used by update_domaines to choose which cert to use for a specific fqdn
*/
function get_fqdn_specials() {
function get_fqdn_specials($deduplicate=true) {
global $L_FQDN;
$specials=array($L_FQDN);
$variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman");
@ -87,14 +87,149 @@ class m_ssl {
}
// -----------------------------------------------------------------
/**
* Crontab launched every minute
* to search for new certificates and launch web_action="UPDATE"
*/
function cron_new_certs() {
global $db,$msg,$dom;
$db->query("SELECT max(id) AS maxid FROM certificates;");
if (!$db->next_record()) {
$msg->raise("ERROR","ssl",_("FATAL: no certificates in certificates table, even the SnakeOil one??"));
return false;
}
$maxid=$db->Record["maxid"];
if ($maxid>$this->last_certificate_id) {
$db->query("SELECT id,fqdn,altnames,sslcrt FROM certificates WHERE id>?",array($this->last_certificate_id));
$certs=array();
// fill an array of fqdn/altnames
while ($db->next_record()) {
if (!$db->Record["sslcrt"]) continue; // skip NOT FINALIZED certificates !!
list($altnames)=explode("\n",$db->Record["altnames"]);
$certs[]=array("id"=>$db->Record["id"],"fqdn"=>$db->Record["fqdn"]);
foreach($altnames as $altname) {
$certs[]=array("id"=>$db->Record["id"],"fqdn"=>$altname);
}
}
// get the list of subdomains-id that match the following FQDN (or wildcard)
$updateids=array();
foreach($certs as $cert) {
$subids=$this->searchSubDomain($cert["fqdn"]);
foreach($subids as $subid) {
$updateids[$subid]=$cert["id"];
}
// if this fqdn match a special domain, update its certificate (and mark service for reloading)
$this->update_specials_match($cert["id"],$cert["fqdn"]);
}
// update those subdomains
$dom->lock();
foreach($updateids as $id => $certid) {
$db->query("UPDATE sub_domaines SET web_action=? WHERE id=?;",array("UPDATE",$id));
$msg->raise("INFO","ssl",sprintf(_("Reloading domain %s as we have new certificate %s"),$id,$certid));
}
$dom->unlock();
$this->last_certificate_id=$maxid;
}
}
function fqdnmatch($cert,$fqdn) {
if ($cert==$fqdn)
return true;
if (substr($cert,0,2)=="*." &&
substr($cert,2)==substr($fqdn,strpos($fqdn,".")+1) )
return true;
return false;
}
// -----------------------------------------------------------------
/**
* update special system certificate that matches the cert fqdn:
*/
function update_specials_match($id,$fqdn) {
global $L_FQDN;
if ($this->fqdnmatch($fqdn,$L_FQDN)) {
// new certificate for the panel
$this->copycert("alternc-panel",$id);
exec("service apache2 reload");
}
$variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman");
foreach($variables as $var) {
$value = variable_get($var,null);
if ($value) {
if ($this->fqdnmatch($fqdn,$value)) {
$this->copycert("alternc-".substr($var,5),$id);
exec("service ".substr($var,5)." reload");
}
}
}
}
// -----------------------------------------------------------------
/**
* copy a certificate (by its ID) to the system files
* set the correct permissions
* try to minimize zero-file-size risk or timing attack
*/
function copycert($target,$id) {
global $db;
$db->query("SELECT * FROM certificate WHERE id=?",array($id));
if (!$db->next_record()) return false;
if (!file_put_contents("/etc/ssl/certs/".$target.".crt.tmp",trim($db->Record["sslcrt"])."\n".trim($db->Record["sslchain"])))
return false;
chown("/etc/ssl/certs/".$target.".crt.tmp","root");
chgrp("/etc/ssl/certs/".$target.".crt.tmp","ssl-cert");
chmod("/etc/ssl/certs/".$target.".crt.tmp",0755);
if (!file_put_contents("/etc/ssl/private/".$target.".key.tmp",$db->Record["sslkey"]))
return false;
chown("/etc/ssl/private/".$target.".key.tmp","root");
chgrp("/etc/ssl/private/".$target.".key.tmp","ssl-cert");
chmod("/etc/ssl/private/".$target.".key.tmp",0750);
rename("/etc/ssl/certs/".$target.".crt.tmp","/etc/ssl/certs/".$target.".crt");
rename("/etc/ssl/private/".$target.".key.tmp","/etc/ssl/private/".$target.".key");
return true;
}
// -----------------------------------------------------------------
/**
* search for a FQDN as a fqdn or a wildcard in all subdomains currently hosted
* return a list of subdomain-id
*/
function searchSubDomain($fqdn) {
global $db;
$db->query("SELECT sd.id FROM sub_domaines sd, domaines_type dt WHERE dt.name=sd.type AND dt.only_dns=0 AND
(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine)=?
OR CONCAT('*.',SUBSTRING(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),
INSTR(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),'.')+1))=?
);",
array($fqdn,$fqdn));
$ids=array();
while ($db->next_record()) {
$ids[]=$db->Record["id"];
}
return $ids;
}
// -----------------------------------------------------------------
/**
* delete old certificates (expired for more than a year)
*/
function delete_old_certificates() {
global $db;
$db->query("SELECT id FROM certificates WHERE status=".self::STATUS_EXPIRED." AND validend<DATE_SUB(NOW(), INTERVAL 12 MONTH) AND validend!='0000-00-00 00:00:00';");
$db->query("SELECT c.id,sd.id AS used FROM certificates c LEFT JOIN sub_domaines sd ON sd.certificate_id=c.id WHERE c.status=".self::STATUS_EXPIRED." AND c.validend<DATE_SUB(NOW(), INTERVAL 12 MONTH) AND c.validend!='0000-00-00 00:00:00';");
while ($db->next_record()) {
if ($db->Record["used"]) {
continue; // this certificate is used (even though it's expired :/ )
}
$CRTDIR = self::KEY_REPOSITORY . "/" . floor($db->Record["id"]/1000);
@unlink($CRTDIR."/".$db->Record["id"].".crt");
@unlink($CRTDIR."/".$db->Record["id"].".key");
@ -383,13 +518,16 @@ class m_ssl {
// Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
if (!$db->query(
"UPDATE certificates SET status=?, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslcrt=?, sslchain=? WHERE id=?;",
"INSERT INTO certificates (status,fqdn,altnames,validstart,validend,sslcrt,sslchain,sslcsr)
SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate WHERE id=?;",
array(self::STATUS_OK, $fqdn, $altnames, $validstart, $validend, $crt, $chain, $certid)
)) {
$msg->raise("ERROR","ssl", _("Can't save the Crt/Chain now. Please try later."));
return false;
}
return $certid;
$newid=$db->lastid();
$db->query("DELETE FROM certificates WHERE id=?;",array($certid));
return $newid;
}

View File

@ -1,4 +1,6 @@
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Create /var/run/ folder : it may be a ramdrive
@reboot root mkdir -p /var/run/alternc && chown alterncpanel:alterncpanel /var/run/alternc
@ -37,3 +39,7 @@
# Calculate the mail accounts size once a week beacause the dovecot plugin to do that is not precise (see ticket AlternC #168)
# Every Sunday at 4am
0 4 * * 0 root /usr/lib/alternc/update_quota_mail.sh -a
# every minute, search for new certificates
* * * * * root /usr/lib/alternc/cron_ssl.sh

28
src/cron_ssl.sh Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/php
<?php
/**
* script called as a every-minute-crontab
* to look for new certificates and reload the associated subdomains
* also delete the old certificates once a day (at 10am)
*/
// Bootstrap
require_once("/usr/share/alternc/panel/class/config_nochk.php");
if (!isset($ssl)) {
echo "OUPS: update_cert.sh launched, but ssl module not installed, exiting\n";
exit();
}
if (posix_getuid()!=0) {
echo "This script MUST be launched as root, it should be able to overwrite files in /etc/ssl/private\n";
exit(-1);
}
if ($date("H:m")=="10:00") {
$ssl->delete_old_certificates();
}
$ssl->cron_new_certs();