mirror of
https://github.com/opnsense/plugins.git
synced 2026-05-28 04:34:15 -04:00
Merge pull request #91 from fraenki/acme_perms
security/acme-client: bugfix release 1.3
This commit is contained in:
commit
a3520c7c24
9 changed files with 187 additions and 56 deletions
|
|
@ -1,6 +1,5 @@
|
|||
PLUGIN_NAME= acme-client
|
||||
PLUGIN_VERSION= 1.2
|
||||
PLUGIN_REVISION= 1
|
||||
PLUGIN_VERSION= 1.3
|
||||
PLUGIN_COMMENT= Let's Encrypt client
|
||||
PLUGIN_MAINTAINER= opnsense@moov.de
|
||||
|
||||
|
|
|
|||
|
|
@ -24,19 +24,27 @@
|
|||
<help>Pre-defined commands for this restart action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<label>Optional Parameters</label>
|
||||
<label>Required Parameters</label>
|
||||
<type>header</type>
|
||||
<style>method_table method_table_configd</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.configd</id>
|
||||
<label>System Command</label>
|
||||
<type>dropdown</type>
|
||||
<help>Select a pre-defined system command which should be run for this action.</help>
|
||||
<style>table_optional table_optional_configd</style>
|
||||
</field>
|
||||
<field>
|
||||
<label>Required Parameters</label>
|
||||
<type>header</type>
|
||||
<style>method_table method_table_custom</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.custom</id>
|
||||
<label>Custom Command</label>
|
||||
<type>textbox</type>
|
||||
<help>Specify a custom commands which should be run for this action.</help>
|
||||
<style>table_optional table_optional_custom</style>
|
||||
</field>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>HTTP-01/OPNsense</label>
|
||||
<label>OPNsense</label>
|
||||
<type>header</type>
|
||||
<style>method_table method_table_http01</style>
|
||||
<style>table_http table_http_opnsense</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_opn_autodiscovery</id>
|
||||
|
|
@ -61,9 +61,9 @@
|
|||
<hint>Enter IP addresses here. Finish each with TAB.</hint>
|
||||
</field>
|
||||
<field>
|
||||
<label>HTTP-01/HAProxy</label>
|
||||
<label>HAProxy</label>
|
||||
<type>header</type>
|
||||
<style>method_table method_table_http01</style>
|
||||
<style>table_http table_http_haproxy</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_haproxyInject</id>
|
||||
|
|
@ -79,20 +79,6 @@
|
|||
<allownew>true</allownew>
|
||||
<help>Choose the local HAProxy frontends. They will automatically be configured to redirect acme challenges to the internal acme client. The HAProxy service will automatically be restarted if a certificate was renewed.</help>
|
||||
</field>
|
||||
<!--
|
||||
<field>
|
||||
<id>validation.http_relaydInject</id>
|
||||
<label>Loadbalancer Config Injection</label>
|
||||
<type>checkbox</type>
|
||||
<help>Automatically inject config into the local Loadbalancer (relayd) to let it serve acme challanges without service interruption.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_relaydVserver</id>
|
||||
<label>Loadbalancer Virtual Server</label>
|
||||
<type>text</type>
|
||||
<help>Choose the Virtual Server from the relayd Loadbalancer that should be configured to server acme challenges.</help>
|
||||
</field>
|
||||
-->
|
||||
<field>
|
||||
<label>DNS-01</label>
|
||||
<type>header</type>
|
||||
|
|
@ -111,7 +97,7 @@
|
|||
<help><![CDATA[The time in seconds to wait for all the TXT records to take effect after adding them to the DNS API. Defaults to 120 seconds.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Alwaysdata</label>
|
||||
<label>Alwaysdata</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_ad</style>
|
||||
</field>
|
||||
|
|
@ -122,7 +108,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/aliyun</label>
|
||||
<label>aliyun</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_ali</style>
|
||||
</field>
|
||||
|
|
@ -139,7 +125,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/AWS Route53</label>
|
||||
<label>AWS Route53</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_aws</style>
|
||||
</field>
|
||||
|
|
@ -156,7 +142,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Cloudflare</label>
|
||||
<label>Cloudflare</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_cf</style>
|
||||
</field>
|
||||
|
|
@ -173,7 +159,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/CloudXNS</label>
|
||||
<label>CloudXNS</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_cx</style>
|
||||
</field>
|
||||
|
|
@ -190,7 +176,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/cyon</label>
|
||||
<label>cyon</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_cyon</style>
|
||||
<help></help>
|
||||
|
|
@ -208,7 +194,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Domain-Offensive</label>
|
||||
<label>Domain-Offensive</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_do</style>
|
||||
<help></help>
|
||||
|
|
@ -226,7 +212,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/DNSPod</label>
|
||||
<label>DNSPod</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_dp</style>
|
||||
</field>
|
||||
|
|
@ -243,7 +229,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/FreeDNS</label>
|
||||
<label>FreeDNS</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_freedns</style>
|
||||
<help></help>
|
||||
|
|
@ -261,7 +247,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Gandi LiveDNS</label>
|
||||
<label>Gandi LiveDNS</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_gandi_livedns</style>
|
||||
<help></help>
|
||||
|
|
@ -273,7 +259,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/GoDaddy</label>
|
||||
<label>GoDaddy</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_gd</style>
|
||||
</field>
|
||||
|
|
@ -290,7 +276,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/IPSConfig</label>
|
||||
<label>IPSConfig</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_ispconfig</style>
|
||||
</field>
|
||||
|
|
@ -319,7 +305,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/lexicon</label>
|
||||
<label>lexicon</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_lexicon</style>
|
||||
</field>
|
||||
|
|
@ -342,7 +328,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Linode</label>
|
||||
<label>Linode</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_linode</style>
|
||||
<help></help>
|
||||
|
|
@ -354,7 +340,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/LuaDNS</label>
|
||||
<label>LuaDNS</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_lua</style>
|
||||
</field>
|
||||
|
|
@ -371,7 +357,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/DNSMadeEasy</label>
|
||||
<label>DNSMadeEasy</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_me</style>
|
||||
</field>
|
||||
|
|
@ -388,7 +374,7 @@
|
|||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/nsupdate</label>
|
||||
<label>nsupdate</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_nsupdate</style>
|
||||
</field>
|
||||
|
|
@ -405,7 +391,7 @@
|
|||
<help><![CDATA[Requires the the whole key file in a format that is compatible with nsupdate.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/OVH</label>
|
||||
<label>OVH</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_ovh</style>
|
||||
</field>
|
||||
|
|
@ -434,7 +420,7 @@
|
|||
<help><![CDATA[Specify the OVH endpoint, i.e. ovh-eu, ovh-ca, kimsufi-eu, etc. Please refer to the <a href="https://github.com/Neilpang/acme.sh/tree/master/dnsapi">acme.sh documentation</a> for further information.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/PowerDNS</label>
|
||||
<label>PowerDNS</label>
|
||||
<type>header</type>
|
||||
<style>table_dns table_dns_pdns</style>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -30,4 +30,11 @@
|
|||
<help><![CDATA[When using HTTP-01 as validation method, a local webserver is used to provide acme challenge data to the Let's Encrypt servers. This setting allows you to change the local port of this webserver in case it interferes with another local services. Defaults to port 43580.]]></help>
|
||||
<advanced>true</advanced>
|
||||
</field>
|
||||
<field>
|
||||
<id>acmeclient.settings.restartTimeout</id>
|
||||
<label>Restart Timeout</label>
|
||||
<type>text</type>
|
||||
<help><![CDATA[The maximum time in seconds to wait for a restart action to complete. When the timeout is reached the command is forcefully aborted. Defaults to 600 seconds.]]></help>
|
||||
<advanced>true</advanced>
|
||||
</field>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@
|
|||
<MaximumValue>65535</MaximumValue>
|
||||
<Required>Y</Required>
|
||||
</challengePort>
|
||||
<restartTimeout type="IntegerField">
|
||||
<default>600</default>
|
||||
<MinimumValue>10</MinimumValue>
|
||||
<MaximumValue>86400</MaximumValue>
|
||||
<Required>Y</Required>
|
||||
</restartTimeout>
|
||||
<haproxyIntegration type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>N</Required>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,19 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
}
|
||||
);
|
||||
|
||||
// hook into on-show event for dialog to extend layout.
|
||||
$('#DialogAction').on('shown.bs.modal', function (e) {
|
||||
$("#action\\.type").change(function(){
|
||||
var service_id = 'table_optional_' + $(this).val();
|
||||
$(".table_optional").hide();
|
||||
$("."+service_id).show();
|
||||
});
|
||||
$("#action\\.type").change(function(){
|
||||
$(".method_table").hide();
|
||||
$(".method_table_"+$(this).val()).show();
|
||||
});
|
||||
$("#action\\.type").change();
|
||||
})
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -51,16 +51,25 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
// hook into on-show event for dialog to extend layout.
|
||||
$('#DialogValidation').on('shown.bs.modal', function (e) {
|
||||
$("#validation\\.dns_service").change(function(){
|
||||
var service_id = 'table_' + $(this).val() ;
|
||||
var service_id = 'table_' + $(this).val();
|
||||
$(".table_dns").hide();
|
||||
if ($("#validation\\.method").val() == 'dns01') {
|
||||
$("."+service_id).show();
|
||||
}
|
||||
});
|
||||
$("#validation\\.http_service").change(function(){
|
||||
var service_id = 'table_http_' + $(this).val();
|
||||
$(".table_http").hide();
|
||||
if ($("#validation\\.method").val() == 'http01') {
|
||||
$("."+service_id).show();
|
||||
} else {
|
||||
}
|
||||
});
|
||||
$("#validation\\.method").change(function(){
|
||||
$(".method_table").hide();
|
||||
$(".method_table_"+$(this).val()).show();
|
||||
$("#validation\\.dns_service").change();
|
||||
$("#validation\\.http_service").change();
|
||||
});
|
||||
$("#validation\\.method").change();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Based in parts on certs.inc (thus the extended copyright notice).
|
||||
* Based in parts on certs.inc and system_camanager.php (thus the extended copyright notice).
|
||||
*
|
||||
* Copyright (C) 2017 Frank Wall
|
||||
* Copyright (C) 2015 Deciso B.V.
|
||||
|
|
@ -777,12 +777,13 @@ function import_certificate($certObj, $modelObj)
|
|||
|
||||
$cert_id = (string)$certObj->id;
|
||||
$cert_filename = "/var/etc/acme-client/certs/${cert_id}/cert.pem";
|
||||
$cert_chain_filename = "/var/etc/acme-client/certs/${cert_id}/chain.pem";
|
||||
$cert_fullchain_filename = "/var/etc/acme-client/certs/${cert_id}/fullchain.pem";
|
||||
$key_filename = "/var/etc/acme-client/keys/${cert_id}/private.key";
|
||||
|
||||
// Check if certificate files can be found
|
||||
clearstatcache(); // don't let the cache fool us
|
||||
foreach (array($cert_filename, $key_filename, $cert_fullchain_filename) as $file) {
|
||||
foreach (array($cert_filename, $key_filename, $cert_chain_filename, $cert_fullchain_filename) as $file) {
|
||||
if (is_file($file)) {
|
||||
// certificate file found
|
||||
} else {
|
||||
|
|
@ -791,6 +792,63 @@ function import_certificate($certObj, $modelObj)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 1: import CA
|
||||
*/
|
||||
|
||||
// Read contents from CA file
|
||||
$ca_content = @file_get_contents($cert_chain_filename);
|
||||
if ($ca_content != false) {
|
||||
$ca_subject = cert_get_subject($ca_content, false);
|
||||
$ca_serial = cert_get_serial($ca_content, false);
|
||||
$ca_cn = local_cert_get_cn($ca_content, false);
|
||||
$ca_issuer = cert_get_issuer($ca_content, false);
|
||||
$ca_purpose = cert_get_purpose($ca_content, false);
|
||||
} else {
|
||||
log_error("AcmeClient: unable to read CA certificate content from file");
|
||||
return(1);
|
||||
}
|
||||
|
||||
// Prepare CA for import in Cert Manager
|
||||
$ca = array();
|
||||
$ca['crt'] = base64_encode($ca_content);
|
||||
$ca['refid'] = uniqid();
|
||||
$ca_found = false;
|
||||
|
||||
// Check if CA was previously imported
|
||||
$cacnt = 0;
|
||||
foreach ($config['ca'] as $cacrt) {
|
||||
$cacrt_subject = cert_get_subject($cacrt['crt'], true);
|
||||
$cacrt_issuer = cert_get_issuer($cacrt['crt'], true);
|
||||
if (($ca_subject == $cacrt_subject) and ($ca_issuer == $cacrt_issuer)) {
|
||||
// Use old refid instead of generating a new one
|
||||
$ca['refid'] = (string)$cacrt['refid'];
|
||||
$ca_found = true;
|
||||
break;
|
||||
}
|
||||
$cacnt++;
|
||||
}
|
||||
|
||||
// Collect required CA information
|
||||
$ca_cn = local_cert_get_cn($ca_content, false);
|
||||
$ca['descr'] = (string)$ca_cn . ' (Let\'s Encrypt)';
|
||||
|
||||
// Prepare CA for import
|
||||
local_ca_import($ca, $ca_content);
|
||||
|
||||
// Update existing CA?
|
||||
if ($ca_found == true) {
|
||||
$config['ca'][$cacnt] = $ca;
|
||||
} else {
|
||||
// Create new CA item
|
||||
$config['ca'][] = $ca;
|
||||
log_error("AcmeClient: importing Let's Encrypt CA: ${ca_cn}");
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 2: import certificate
|
||||
*/
|
||||
|
||||
// Read contents from certificate file
|
||||
$cert_content = @file_get_contents($cert_filename);
|
||||
if ($cert_content != false) {
|
||||
|
|
@ -809,6 +867,7 @@ function import_certificate($certObj, $modelObj)
|
|||
$cert = array();
|
||||
$cert_refid = uniqid();
|
||||
$cert['refid'] = $cert_refid;
|
||||
$cert['caref'] = (string)$ca['refid'];
|
||||
$import_log_message = 'Imported';
|
||||
$cert_found = false;
|
||||
|
||||
|
|
@ -842,20 +901,13 @@ function import_certificate($certObj, $modelObj)
|
|||
return(1);
|
||||
}
|
||||
|
||||
// Read cert fullchain
|
||||
$cert_fullchain_content = @file_get_contents($cert_fullchain_filename);
|
||||
if ($cert_fullchain_content == false) {
|
||||
log_error("AcmeClient: unable to read full certificate chain from file: ${cert_fullchain_filename}");
|
||||
return(1);
|
||||
}
|
||||
|
||||
// Collect required cert information
|
||||
$cert_cn = local_cert_get_cn($cert_content, false);
|
||||
$cert['descr'] = (string)$cert_cn . ' (Let\'s Encrypt)';
|
||||
$cert['refid'] = $cert_refid;
|
||||
|
||||
// Prepare certificate for import
|
||||
cert_import($cert, $cert_fullchain_content, $key_content);
|
||||
cert_import($cert, $cert_content, $key_content);
|
||||
|
||||
// Update existing certificate?
|
||||
if ($cert_found == true) {
|
||||
|
|
@ -874,6 +926,10 @@ function import_certificate($certObj, $modelObj)
|
|||
$config['cert'][] = $cert;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 3: update configuration
|
||||
*/
|
||||
|
||||
// Write changes to config
|
||||
// TODO: Legacy code, should be replaced with code from OPNsense framework
|
||||
write_config("${import_log_message} Let's Encrypt SSL certificate: ${cert_cn}");
|
||||
|
|
@ -901,6 +957,7 @@ function run_restart_actions($certlist, $modelObj)
|
|||
{
|
||||
global $config;
|
||||
$return = 0;
|
||||
$configObj = Config::getInstance()->object();
|
||||
|
||||
// NOTE: Do NOT run any restart action twice, collect duplicates first.
|
||||
$restart_actions = array();
|
||||
|
|
@ -915,11 +972,11 @@ function run_restart_actions($certlist, $modelObj)
|
|||
continue;
|
||||
}
|
||||
// Extract restart actions
|
||||
$_actions = explode(',', $certObj->restartActions);
|
||||
if (empty($_actions)) {
|
||||
if (empty((string)$certObj->restartActions)) {
|
||||
// No restart actions configured.
|
||||
continue;
|
||||
}
|
||||
$_actions = explode(',', $certObj->restartActions);
|
||||
// Walk through all linked restart actions.
|
||||
foreach ($_actions as $_action) {
|
||||
// Extract restart action
|
||||
|
|
@ -985,8 +1042,12 @@ function run_restart_actions($certlist, $modelObj)
|
|||
$proc_stderr = '';
|
||||
$result = ''; // exit code (or '99' in case of timeout)
|
||||
|
||||
// TODO: Make the timeout configurable.
|
||||
$timeout = '600';
|
||||
// Timeout for custom restart actions.
|
||||
if (!empty((string)$configObj->OPNsense->AcmeClient->settings->restartTimeout)) {
|
||||
$timeout = (string)$configObj->OPNsense->AcmeClient->settings->restartTimeout;
|
||||
} else {
|
||||
$timeout = '600';
|
||||
}
|
||||
$starttime = time();
|
||||
|
||||
$proc_cmd = (string)$action->custom;
|
||||
|
|
@ -1101,6 +1162,48 @@ function local_cert_get_cn($crt, $decode = true)
|
|||
return "";
|
||||
}
|
||||
|
||||
// taken from system_camanager.php
|
||||
function local_ca_import(& $ca, $str, $key="", $serial=0) {
|
||||
global $config;
|
||||
|
||||
$ca['crt'] = base64_encode($str);
|
||||
if (!empty($key)) {
|
||||
$ca['prv'] = base64_encode($key);
|
||||
}
|
||||
if (!empty($serial)) {
|
||||
$ca['serial'] = $serial;
|
||||
}
|
||||
$subject = cert_get_subject($str, false);
|
||||
$issuer = cert_get_issuer($str, false);
|
||||
|
||||
// Find my issuer unless self-signed
|
||||
if($issuer <> $subject) {
|
||||
$issuer_crt =& lookup_ca_by_subject($issuer);
|
||||
if($issuer_crt) {
|
||||
$ca['caref'] = $issuer_crt['refid'];
|
||||
}
|
||||
}
|
||||
|
||||
/* Correct if child certificate was loaded first */
|
||||
if (is_array($config['ca'])) {
|
||||
foreach ($config['ca'] as & $oca) {
|
||||
$issuer = cert_get_issuer($oca['crt']);
|
||||
if($ca['refid']<>$oca['refid'] && $issuer==$subject) {
|
||||
$oca['caref'] = $ca['refid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($config['cert'])) {
|
||||
foreach ($config['cert'] as & $cert) {
|
||||
$issuer = cert_get_issuer($cert['crt']);
|
||||
if($issuer==$subject) {
|
||||
$cert['caref'] = $ca['refid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function base64url_encode($str)
|
||||
{
|
||||
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ ACME_DIRS="/var/etc/acme-client /var/etc/acme-client/certs /var/etc/acme-client/
|
|||
for directory in ${ACME_DIRS}; do
|
||||
mkdir -p ${directory}
|
||||
chown -R root:wheel ${directory}
|
||||
chmod -R 755 ${directory}
|
||||
chmod -R 750 ${directory}
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
Loading…
Reference in a new issue