diff --git a/security/acme-client/Makefile b/security/acme-client/Makefile
index 8d1ada4d0..972358733 100644
--- a/security/acme-client/Makefile
+++ b/security/acme-client/Makefile
@@ -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
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml
index 51254439c..4dcfa42b4 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml
@@ -24,19 +24,27 @@
Pre-defined commands for this restart action.
-
+
header
+
action.configd
dropdown
Select a pre-defined system command which should be run for this action.
+
+
+
+
+ header
+
action.custom
textbox
Specify a custom commands which should be run for this action.
+
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml
index f9dd582a2..30bceb613 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml
@@ -35,9 +35,9 @@
-
+
header
-
+
validation.http_opn_autodiscovery
@@ -61,9 +61,9 @@
Enter IP addresses here. Finish each with TAB.
-
+
header
-
+
validation.http_haproxyInject
@@ -79,20 +79,6 @@
true
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.
-
header
@@ -111,7 +97,7 @@
-
+
header
@@ -122,7 +108,7 @@
-
+
header
@@ -139,7 +125,7 @@
-
+
header
@@ -156,7 +142,7 @@
-
+
header
@@ -173,7 +159,7 @@
-
+
header
@@ -190,7 +176,7 @@
-
+
header
@@ -208,7 +194,7 @@
-
+
header
@@ -226,7 +212,7 @@
-
+
header
@@ -243,7 +229,7 @@
-
+
header
@@ -261,7 +247,7 @@
-
+
header
@@ -273,7 +259,7 @@
-
+
header
@@ -290,7 +276,7 @@
-
+
header
@@ -319,7 +305,7 @@
-
+
header
@@ -342,7 +328,7 @@
-
+
header
@@ -354,7 +340,7 @@
-
+
header
@@ -371,7 +357,7 @@
-
+
header
@@ -388,7 +374,7 @@
-
+
header
@@ -405,7 +391,7 @@
-
+
header
@@ -434,7 +420,7 @@
acme.sh documentation for further information.]]>
-
+
header
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml
index 65afd3a4f..8f5debb9d 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml
@@ -30,4 +30,11 @@
true
+
+ acmeclient.settings.restartTimeout
+
+ text
+
+ true
+
diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml
index 5edec0877..65e463196 100644
--- a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml
+++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml
@@ -43,6 +43,12 @@
65535
Y
+
+ 600
+ 10
+ 86400
+ Y
+
0
N
diff --git a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/actions.volt b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/actions.volt
index fbb8f54c5..2eef0c115 100644
--- a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/actions.volt
+++ b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/actions.volt
@@ -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();
+ })
});
diff --git a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt
index b192c9477..d42a0f1ce 100644
--- a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt
+++ b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt
@@ -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();
})
diff --git a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php
index 3acb96297..c1fbd1767 100755
--- a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php
+++ b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php
@@ -2,7 +2,7 @@
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), '+/', '-_'), '=');
diff --git a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh
index 8215a8262..583e3f9d6 100755
--- a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh
+++ b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh
@@ -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