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 edc5de0c4..f78611844 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
@@ -372,4 +372,22 @@ class SettingsController extends ApiMutableModelControllerBase
return $result;
}
+
+ /**
+ * Check wether the Google Cloud plugin is installed.
+ * @return array status action
+ */
+ public function getGcloudPluginStatusAction()
+ {
+ $result = array("result" => "0");
+
+ $mdlAcme = $this->getModel();
+
+ // Check if the required plugin is installed
+ if ((string)$mdlAcme->isPluginInstalled('google-cloud-sdk') == "1") {
+ $result['result'] = "1";
+ }
+
+ return $result;
+ }
}
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 1243d46fa..7da76ac8d 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
@@ -282,7 +282,17 @@
Note that this is the account token not the user token.
-
+
+ header
+
+
+
+ validation.dns_doapi_token
+
+ text
+
+
+
header
@@ -391,6 +401,30 @@
text
+
+
+ header
+
+
+
+
+ info
+
+
+ validation.dns_gcloud_key
+
+ textbox
+ Provide a service account key in JSON format for your Google Cloud account.
+
+
+
+ header
+
+
+
+
+ info
+
header
@@ -586,6 +620,27 @@
text
+
+
+ header
+
+
+
+ validation.dns_namecheap_user
+
+ text
+
+
+ validation.dns_namecheap_api
+
+ text
+
+
+ validation.dns_namecheap_sourceip
+
+ text
+ true
+
header
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 ba4e62853..e8cdb2877 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
@@ -342,7 +342,7 @@
ACME DNS API
Alwaysdata.com API
aliyun.com API
- autoDNS (InternetX) API
+ AutoDNS (InterNetX) API
AWS Route 53
Azure DNS API
CloudFlare.com API
@@ -354,7 +354,8 @@
DNSimple API
DNSMadeEasy.com API
DNSPod.cn API
- Domain-Offensive/Resellerinterface/Domainrobot API
+ Domain-Offensive LetsEncrypt API
+ Domain-Offensive Resellerinterface/Domainrobot API
DreamHost DNS API
DuckDNS API
Dyn Managed DNS API
@@ -362,6 +363,7 @@
FreeDNS API
Gandi LiveDNS API
GoDaddy.com API
+ Google Cloud DNS API
GratisDNS.dk
hosting.de API
Hurricane Electric
@@ -374,6 +376,7 @@
Linode API
LuaDNS.com API
Name.com API
+ Namecheap API
Namesilo.com API
NS1.com API
nsupdate (RFC 2136)
@@ -471,6 +474,9 @@
N
+
+ N
+
N
@@ -513,6 +519,9 @@
N
+
+ N
+
N
@@ -597,6 +606,15 @@
N
+
+ N
+
+
+ N
+
+
+ N
+
N
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 7c5d762d8..fe51b231e 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
@@ -56,6 +56,12 @@ POSSIBILITY OF SUCH DAMAGE.
if ($("#validation\\.method").val() == 'dns01') {
$("."+service_id).show();
}
+ // Show a warning if the Google Cloud SDK plugin is missing.
+ ajaxCall(url="/api/acmeclient/settings/getGcloudPluginStatus", sendData={}, callback=function(data,status) {
+ if (data['result'] != 0) {
+ $(".gcloud_plugin_warning").hide();
+ }
+ });
});
$("#validation\\.http_service").change(function(){
var service_id = 'table_http_' + $(this).val();
@@ -72,6 +78,7 @@ POSSIBILITY OF SUCH DAMAGE.
$("#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 95d3eacd2..78f63dbad 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 @@
* Copyright (C) 2008 Shrew Soft Inc.
@@ -206,7 +206,7 @@ function cert_action_validator($opt_cert_id)
// Start acme client to issue or renew certificate
$val_result = run_acme_validation($certObj, $valObj, $acctObj);
if (!$val_result) {
- log_error("AcmeClient: issued/renewed certificate: " . (string)$certObj->name);
+ log_error("AcmeClient: successfully issued/renewed certificate: " . (string)$certObj->name);
// Import certificate to Cert Manager
if (!import_certificate($certObj, $modelObj)) {
// Prepare certificate for automation
@@ -371,6 +371,7 @@ function run_acme_account_registration($acctObj, $certObj, $modelObj)
// serialize to config and save
$modelObj->serializeToConfig();
Config::getInstance()->save();
+ Config::getInstance()->forceReload();
}
}
@@ -405,6 +406,7 @@ function run_acme_account_registration($acctObj, $certObj, $modelObj)
// serialize to config and save
$modelObj->serializeToConfig();
Config::getInstance()->save();
+ Config::getInstance()->forceReload();
}
return;
@@ -417,6 +419,7 @@ function run_acme_validation($certObj, $valObj, $acctObj)
// Required to run pre-defined commands.
$backend = new Backend();
+ $modelObj = new OPNsense\AcmeClient\AcmeClient;
// Collect account information
$account_conf_dir = "/var/etc/acme-client/accounts/" . $acctObj->id;
@@ -567,6 +570,7 @@ function run_acme_validation($certObj, $valObj, $acctObj)
// Prepare DNS-01 hooks
if ($val_method == 'dns01') {
// Some common stuff
+ $val_id = preg_replace("/[^a-zA-Z0-9]/", "", (string)$valObj->id);
$secret_key_filename = "${configdir}/secret.key";
$acme_args[] = '--dnssleep ' . $valObj->dns_sleep;
@@ -626,6 +630,9 @@ function run_acme_validation($certObj, $valObj, $acctObj)
$proc_env['DO_PID'] = (string)$valObj->dns_do_pid;
$proc_env['DO_PW'] = (string)$valObj->dns_do_password;
break;
+ case 'dns_doapi':
+ $proc_env['DO_LETOKEN'] = (string)$valObj->dns_doapi_token;
+ break;
case 'dns_dp':
$proc_env['DP_Id'] = (string)$valObj->dns_dp_id;
$proc_env['DP_Key'] = (string)$valObj->dns_dp_key;
@@ -652,6 +659,42 @@ function run_acme_validation($certObj, $valObj, $acctObj)
case 'dns_gandi_livedns':
$proc_env['GANDI_LIVEDNS_KEY'] = (string)$valObj->dns_gandi_livedns_key;
break;
+ case 'dns_gcloud':
+ # Google Cloud SDK must be installed.
+ if ((string)$modelObj->isPluginInstalled('google-cloud-sdk') != "1") {
+ log_error("AcmeClient: Google Cloud SDK plugin is NOT installed. Please install os-google-cloud-sdk.");
+ return(1);
+ }
+ # We need a valid Google Cloud JSON key.
+ if (!empty((string)$valObj->dns_gcloud_key)) {
+ # Extract the gcloud project from the key data.
+ $_gcloud_data = json_decode((string)$valObj->dns_gcloud_key);
+ $gcloud_project = $_gcloud_data->project_id;
+ $gcloud_account = $_gcloud_data->client_email;
+ if (empty($gcloud_project)) {
+ log_error("AcmeClient: unable to extract project name from Google Cloud DNS JSON key");
+ return(1);
+ } else {
+ log_error("AcmeClient: Google Cloud DNS project name: ${gcloud_project}");
+ }
+ } else {
+ log_error("AcmeClient: no key for Google Cloud DNS was specified");
+ return(1);
+ }
+ # Preparations for gcloud CLI.
+ $gcloud_config = "acme-${val_id}";
+ $gcloud_key_file = "/tmp/acme_" . (string)$valObj->dns_service . "_${val_id}.json";
+ file_put_contents($gcloud_key_file, (string)$valObj->dns_gcloud_key);
+ chmod($gcloud_key_file, 0600);
+ $proc_env['CLOUDSDK_ACTIVE_CONFIG_NAME'] = $gcloud_config;
+ $proc_env['CLOUDSDK_CORE_PROJECT'] = $gcloud_project;
+ # Ensure that a working gcloud config exists.
+ run_shell_command("/usr/local/bin/gcloud config configurations create ${gcloud_config}",$proc_env);
+ run_shell_command("/usr/local/bin/gcloud config configurations activate ${gcloud_config}",$proc_env);
+ run_shell_command("/usr/local/bin/gcloud auth activate-service-account --key-file=${gcloud_key_file}",$proc_env);
+ run_shell_command("/usr/local/bin/gcloud config set account ${gcloud_account}",$proc_env);
+ run_shell_command("/usr/local/bin/gcloud config set project ${gcloud_project}",$proc_env);
+ break;
case 'dns_gd':
$proc_env['GD_Key'] = (string)$valObj->dns_gd_key;
$proc_env['GD_Secret'] = (string)$valObj->dns_gd_secret;
@@ -709,6 +752,16 @@ function run_acme_validation($certObj, $valObj, $acctObj)
$proc_env['ME_Key'] = (string)$valObj->dns_me_key;
$proc_env['ME_Secret'] = (string)$valObj->dns_me_secret;
break;
+ case 'dns_namecheap':
+ $proc_env['NAMECHEAP_USERNAME'] = (string)$valObj->dns_namecheap_user;
+ $proc_env['NAMECHEAP_API_KEY'] = (string)$valObj->dns_namecheap_api;
+ if (!empty((string)$valObj->dns_namecheap_sourceip)) {
+ $proc_env['NAMECHEAP_SOURCEIP'] = (string)$valObj->dns_namecheap_sourceip;
+ } else {
+ // Use a public service to get our source IP for Namecheap API
+ $proc_env['NAMECHEAP_SOURCEIP'] = 'https://ifconfig.co/ip';
+ }
+ break;
case 'dns_namecom':
$proc_env['Namecom_Username'] = (string)$valObj->dns_namecom_user;
$proc_env['Namecom_Token'] = (string)$valObj->dns_namecom_token;
@@ -1069,6 +1122,7 @@ function import_certificate($certObj, $modelObj)
// if node was found, serialize to config and save
$modelObj->serializeToConfig();
Config::getInstance()->save();
+ Config::getInstance()->forceReload();
} else {
log_error("AcmeClient: unable to update LE certificate object");
return(1);
@@ -1203,20 +1257,62 @@ function log_cert_acme_status($certObj, $modelObj, $statusCode)
function dump_postponed_updates()
{
global $postponed_updates;
+
+ $status_descr = [
+ 100 => 'unknown',
+ 200 => 'OK',
+ 250 => 'cert revoked',
+ 300 => 'configuration error',
+ 400 => 'validation failed',
+ 500 => 'internal error',
+ ];
+
$modelObj = new OPNsense\AcmeClient\AcmeClient;
foreach ($postponed_updates as $pupdate) {
- $node = $modelObj->getNodeByReference('certificates.certificate.' . $pupdate['uuid']);
+ $_statusCode = $pupdate['statusCode'];
+ $_uuid = $pupdate['uuid'];
+ $node = $modelObj->getNodeByReference('certificates.certificate.'.$_uuid);
if ($node != null) {
- $node->statusCode = $pupdate['statusCode'];
+ log_error("AcmeClient: storing status '" . $status_descr[$_statusCode] . "' for cert " . (string)$node->name);
+ $node->statusCode = $_statusCode;
$node->statusLastUpdate = $pupdate['statusLastUpdate'];
// serialize to config and save
$modelObj->serializeToConfig();
Config::getInstance()->save();
+ Config::getInstance()->forceReload();
+ } else {
+ log_error(sprintf("AcmeClient: failed to store status '%s' for cert %s: node not found",$status_descr[$_statusCode],$_uuid));
}
}
}
+function run_shell_command($proc_cmd, $proc_env = array())
+{
+ $proc_desc = array( // descriptor array for proc_open()
+ 0 => array("pipe", "r"), // stdin
+ 1 => array("pipe", "w"), // stdout
+ 2 => array("pipe", "w") // stderr
+ );
+ $proc_pipes = array();
+ $proc = proc_open($proc_cmd, $proc_desc, $proc_pipes, null, $proc_env);
+
+ // Make sure the resource could be setup properly
+ if (is_resource($proc)) {
+ // Close all pipes
+ fclose($proc_pipes[0]);
+ fclose($proc_pipes[1]);
+ fclose($proc_pipes[2]);
+ // Get exit code
+ $result = proc_close($proc);
+ log_error(sprintf("AcmeClient: The shell command '%s' returned exit code '%d'", $proc_cmd, $result));
+ return($result);
+ } else {
+ log_error(sprintf("AcmeClient: Unable to prepare shell command '%s'",$proc_cmd));
+ return(1);
+ }
+}
+
// taken from certs.inc
function local_cert_get_subject_array($str_crt, $decode = true)
{