diff --git a/security/acme-client/Makefile b/security/acme-client/Makefile
index dea8204ec..543f2f717 100644
--- a/security/acme-client/Makefile
+++ b/security/acme-client/Makefile
@@ -1,5 +1,5 @@
PLUGIN_NAME= acme-client
-PLUGIN_VERSION= 1.1
+PLUGIN_VERSION= 1.2
PLUGIN_COMMENT= Let's Encrypt client
PLUGIN_MAINTAINER= opnsense@moov.de
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/CertificatesController.php b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/CertificatesController.php
index fcba67fa2..913afdc4c 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/CertificatesController.php
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/CertificatesController.php
@@ -203,7 +203,7 @@ class CertificatesController extends ApiControllerBase
$grid = new UIModelGrid($mdlAcme->certificates->certificate);
return $grid->fetchBindRequest(
$this->request,
- array("enabled", "name", "altNames", "description"),
+ array("enabled", "name", "altNames", "description", "lastUpdate", "statusCode", "statusLastUpdate"),
"name"
);
}
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/SettingsController.php b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/SettingsController.php
index ea5083510..52e60e9b7 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/SettingsController.php
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/Api/SettingsController.php
@@ -125,7 +125,7 @@ class SettingsController extends ApiMutableModelControllerBase
$mdlAcme = $this->getModel();
// Check if the required plugin is installed
- if ((string)$mdlAcme->isPluginInstalled('os-haproxy') != "1") {
+ if ((string)$mdlAcme->isPluginInstalled('haproxy') != "1") {
$this->getLogger()->error("LE check: HAProxy plugin is NOT installed, skipping integration");
return($result);
}
@@ -322,6 +322,11 @@ class SettingsController extends ApiMutableModelControllerBase
(string)$validation->method == "http01" and
(string)$validation->http_service == "haproxy") {
//$this->getLogger()->error("LE HAProxy DEBUG: checking validation method: " . (string)$validation->name);
+ // Check if HAProxy frontends were specified.
+ if (empty((string)$validation->http_haproxyFrontends)) {
+ // Skip item, no HAProxy frontends were specified.
+ continue;
+ }
$_frontends = explode(',', $validation->http_haproxyFrontends);
// Walk through all linked frontends.
foreach ($_frontends as $_frontend) {
diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogCertificate.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogCertificate.xml
index da015154c..2056ed88f 100644
--- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogCertificate.xml
+++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogCertificate.xml
@@ -28,9 +28,9 @@
certificate.account
-
+
dropdown
-
+
certificate.validationMethod
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 81f703af4..4109e2c0d 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
@@ -26,6 +26,7 @@
header
+
validation.http_service
@@ -36,6 +37,7 @@
header
+
validation.http_opn_autodiscovery
@@ -61,6 +63,7 @@
header
+
validation.http_haproxyInject
@@ -93,6 +96,7 @@
header
+
validation.dns_service
@@ -109,6 +113,7 @@
header
+
validation.dns_ad_key
@@ -119,6 +124,7 @@
header
+
validation.dns_ali_key
@@ -135,6 +141,7 @@
header
+
validation.dns_aws_id
@@ -151,6 +158,7 @@
header
+
validation.dns_cf_email
@@ -167,6 +175,7 @@
header
+
validation.dns_cx_key
@@ -183,6 +192,7 @@
header
+
validation.dns_dp_id
@@ -199,6 +209,7 @@
header
+
validation.dns_gd_key
@@ -215,6 +226,7 @@
header
+
validation.dns_ispconfig_user
@@ -243,6 +255,7 @@
header
+
validation.dns_lexicon_provider
@@ -265,6 +278,7 @@
header
+
validation.dns_lua_email
@@ -281,6 +295,7 @@
header
+
validation.dns_me_key
@@ -297,6 +312,7 @@
header
+
validation.dns_nsupdate_server
@@ -313,6 +329,7 @@
header
+
validation.dns_ovh_app_key
@@ -341,6 +358,7 @@
header
+
validation.dns_pdns_url
diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.php b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.php
index 09a07e485..70427ab5c 100644
--- a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.php
+++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.php
@@ -96,45 +96,7 @@ class AcmeClient extends BaseModel
*/
public function isPluginInstalled($name)
{
- // NOTE: Based on infoAction() from Core/Api/FirmwareController.php
- // FIXME: Should be replaced by a Core function sooner or later.
-
$backend = new Backend();
- $keys = array('name', 'version', 'comment', 'flatsize', 'locked', 'license');
- $plugins = array();
-
- // Only check local package data for performance reasons
- $current = $backend->configdRun("firmware local");
- $current = explode("\n", trim($current));
-
- foreach ($current as $line) {
- /* package infos are flat lists with 3 pipes as delimiter */
- $expanded = explode('|||', $line);
- $translated = array();
- $index = 0;
- if (count($expanded) != count($keys)) {
- continue;
- }
- foreach ($keys as $key) {
- $translated[$key] = $expanded[$index++];
- }
-
- /* mark local packages as "installed" */
- $translated['installed'] = "1";
-
- /* figure out local and remote plugins */
- $plugin = explode('-', $translated['name']);
- if (count($plugin)) {
- if ($plugin[0] == 'os' || $plugin[0] == 'ospriv') {
- $plugins[$translated['name']] = $translated;
- }
- }
- }
-
- if (isset($plugins[$name]) and $plugins[$name]['installed'] == "1") {
- return 1; // TRUE, is installed
- } else {
- return 0; // FALSE, is not installed
- }
+ return trim($backend->configdRun('firmware plugin ' . escapeshellarg($name)));
}
}
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 dd011e54a..50f6823de 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
@@ -224,6 +224,18 @@
N
+
+
+ N
+
+
+ 100
+ 1000
+
+
+
+ N
+
diff --git a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/certificates.volt b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/certificates.volt
index 8f3ac324f..051f3e7af 100644
--- a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/certificates.volt
+++ b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/certificates.volt
@@ -69,6 +69,52 @@ POSSIBILITY OF SUCH DAMAGE.
} else {
return "";
}
+ },
+ "certdate": function (column, row) {
+ if (row.lastUpdate == "" || row.lastUpdate == undefined) {
+ return "{{ lang._('pending') }}";
+ } else {
+ var certdate = new Date(row.lastUpdate*1000);
+ return certdate.toLocaleString();
+ }
+ },
+ "acmestatus": function (column, row) {
+ if (row.statusCode == "" || row.statusCode == undefined) {
+ // fallback to lastUpdate value (unset if cert was never issued/imported)
+ if (row.lastUpdate == "" || row.lastUpdate == undefined) {
+ return "{{ lang._('unknown') }}";
+ } else {
+ return "{{ lang._('OK') }}";
+ }
+ } else if (row.statusCode == "100") {
+ return "{{ lang._('unknown') }}";
+ } else if (row.statusCode == "200") {
+ return "{{ lang._('OK') }}";
+ } else if (row.statusCode == "250") {
+ return "{{ lang._('cert revoked') }}";
+ } else if (row.statusCode == "300") {
+ return "{{ lang._('configuration error') }}";
+ } else if (row.statusCode == "400") {
+ return "{{ lang._('validation failed') }}";
+ } else if (row.statusCode == "500") {
+ return "{{ lang._('internal error') }}";
+ } else {
+ return "{{ lang._('unknown') }}";
+ }
+ },
+ "acmestatusdate": function (column, row) {
+ if (row.statusLastUpdate == "" || row.statusCode == undefined) {
+ // fallback to lastUpdate value
+ if (row.lastUpdate == "" || row.lastUpdate == undefined) {
+ return "{{ lang._('unknown') }}";
+ } else {
+ var legacydate = new Date(row.lastUpdate*1000);
+ return legacydate.toLocaleString();
+ }
+ } else {
+ var statusdate = new Date(row.statusLastUpdate*1000);
+ return statusdate.toLocaleString();
+ }
}
},
};
@@ -339,6 +385,9 @@ POSSIBILITY OF SUCH DAMAGE.
| {{ lang._('Certificate Name') }} |
{{ lang._('Multi-Domain (SAN)') }} |
{{ lang._('Description') }} |
+ {{ lang._('Issue/Renewal Date') }} |
+ {{ lang._('Last Acme Status') }} |
+ {{ lang._('Last Acme Run') }} |
{{ lang._('Commands') }} |
{{ lang._('ID') }} |
diff --git a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/settings.volt b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/settings.volt
index a1eca2613..96660070d 100644
--- a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/settings.volt
+++ b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/settings.volt
@@ -175,7 +175,7 @@ POSSIBILITY OF SUCH DAMAGE.
});
} else {
BootstrapDialog.show({
- type: BootstrapDialog.TYPE_WARNING,
+ type: BootstrapDialog.TYPE_INFO,
title: "{{ lang._('acme-client config test result') }}",
message: "{{ lang._('Your acme-client config contains no errors.') }}",
draggable: true
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 260d583f3..b192c9477 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
@@ -48,6 +48,22 @@ 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() ;
+ $(".table_dns").hide();
+ if ($("#validation\\.method").val() == 'dns01') {
+ $("."+service_id).show();
+ }
+ });
+ $("#validation\\.method").change(function(){
+ $(".method_table").hide();
+ $(".method_table_"+$(this).val()).show();
+ $("#validation\\.dns_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 f6c417c4e..058004bd4 100755
--- a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php
+++ b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/certhelper.php
@@ -158,6 +158,7 @@ function cert_action_validator($opt_cert_id)
} else {
//echo "DEBUG: account registration failed\n";
log_error("AcmeClient: account registration failed");
+ log_cert_acme_status($certObj, $modelObj, '400');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -166,6 +167,7 @@ function cert_action_validator($opt_cert_id)
} else {
//echo "DEBUG: account not found\n";
log_error("AcmeClient: account not found");
+ log_cert_acme_status($certObj, $modelObj, '300');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -193,10 +195,12 @@ function cert_action_validator($opt_cert_id)
// Start acme client to revoke the certificate
$rev_result = revoke_cert($certObj, $valObj, $acctObj);
if (!$rev_result) {
+ log_cert_acme_status($certObj, $modelObj, '250');
return(0); // Success!
} else {
// Revocation failure
log_error("AcmeClient: revocation for certificate failed");
+ log_cert_acme_status($certObj, $modelObj, '400');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -215,8 +219,10 @@ function cert_action_validator($opt_cert_id)
//echo "DEBUG: cert import done\n";
// Prepare certificate for restart action
$restart_certs[] = $certObj;
+ log_cert_acme_status($certObj, $modelObj, '200');
} else {
log_error("AcmeClient: unable to import certificate: " . (string)$certObj->name);
+ log_cert_acme_status($certObj, $modelObj, '500');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -227,6 +233,7 @@ function cert_action_validator($opt_cert_id)
} else {
// validation failure
log_error("AcmeClient: validation for certificate failed: " . (string)$certObj->name);
+ log_cert_acme_status($certObj, $modelObj, '400');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -234,6 +241,7 @@ function cert_action_validator($opt_cert_id)
}
} else {
log_error("AcmeClient: invalid validation method specified: " . (string)$valObj->method);
+ log_cert_acme_status($certObj, $modelObj, '300');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -241,6 +249,7 @@ function cert_action_validator($opt_cert_id)
}
} else {
log_error("AcmeClient: validation method not found for cert " . $certObj->name);
+ log_cert_acme_status($certObj, $modelObj, '300');
if (isset($options["A"])) {
continue; // skip to next item
}
@@ -887,8 +896,11 @@ function run_restart_actions($certlist, $modelObj)
}
// Extract restart actions
$_actions = explode(',', $certObj->restartActions);
+ if (empty($_actions)) {
+ // No restart actions configured.
+ continue;
+ }
// Walk through all linked restart actions.
- $_actions = explode(',', $certObj->restartActions);
foreach ($_actions as $_action) {
// Extract restart action
$action = $modelObj->getByActionID($_action);
@@ -1007,6 +1019,32 @@ function run_restart_actions($certlist, $modelObj)
return($return);
}
+/* Update certificate object to log the status of the current acme run.
+ * Supported status codes are:
+ * 100 pending
+ * 200 issue/renew OK
+ * 250 certificate revoked
+ * 300 configuration error (validation method, account, ...)
+ * 400 issue/renew failed
+ * 500 internal error (code issues, bad luck, unexpected errors, ...)
+ * Feel free to add more status codes to make it more useful.
+*/
+function log_cert_acme_status($certObj, $modelObj, $statusCode)
+{
+ $uuid = $certObj->attributes()->uuid;
+ $node = $modelObj->getNodeByReference('certificates.certificate.' . $uuid);
+ if ($node != null) {
+ $node->statusCode = $statusCode;
+ $node->statusLastUpdate = time();
+ // serialize to config and save
+ $modelObj->serializeToConfig();
+ Config::getInstance()->save();
+ } else {
+ log_error("AcmeClient: unable to update acme status for certificate " . (string)$certObj->name);
+ return(1);
+ }
+}
+
// taken from certs.inc
function local_cert_get_subject_array($str_crt, $decode = true)
{