security/acme-client: release 2.0

This commit is contained in:
Frank Wall
2020-09-12 22:42:14 +02:00
parent 8daf9a122c
commit 8fe0f3a899
101 changed files with 6363 additions and 1710 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PLUGIN_NAME= acme-client
PLUGIN_VERSION= 1.36
PLUGIN_VERSION= 2.0
PLUGIN_COMMENT= Let's Encrypt client
PLUGIN_MAINTAINER= opnsense@moov.de
PLUGIN_DEPENDS= acme.sh py${PLUGIN_PYTHON}-dns-lexicon
+318
View File
@@ -4,3 +4,321 @@ and smallest and smartest shell script" to automatically issue and renew
the free certificates from Let's Encrypt.
WWW: https://github.com/acmesh-official/acme.sh
Plugin Changelog
================
2.0
Added:
* add new OOP backend to improve reliability and maintainability (#1398)
* add status for accounts to backend and WebGUI
* add button to manually trigger account registration
* add plugin changelog
Fixed:
* fix bug where configuration could get lost (#1526)
* fix Cyon DNS API (password not set)
Changed:
* now an Automation may run multiple times during bulk issue/renewal (previously only once)
* rename "Validation Methods" to "Challenge Types" to adopt official LE wording
* rename Menu entry "Automation" to "Automations"
* specify python version for gcloud SDK
* rephrase several log messages
* add more detailed output when debug logging is enabled
1.36
Added:
* add ability to rerun automations (#1962)
1.35
Added:
* add support for Linode Cloud API (#1940)
* add support for 1984Hosting API (#1945)
Changed:
* remove outdated bundled version of dns_opnsense.sh (#1888)
1.34
Added:
* add support for dnsapi ArvanCloud (#1834)
* add support for dnsapi Hetzner (#1870)
Changed:
* restore proper sorting in DNS API list
1.33
Added:
* add NSUPDATE_ZONE support to nsupdate DNS-01 service (#1851)
1.32
Added:
* add support for Acmeproxy DNS provider (#1838)
Changed:
* improve support for dnsapi Euserv.eu (#1790)
1.31
Added:
* add support for dnsapi SchlundTech (#1728)
* add support for dnsapi Euserv (#1779)
* add support for dnsapi Leaseweb (#1670)
Changed:
* sftp export: make the "fullchain" filename configurable (#1776)
1.30
Changed:
* update acme.sh GitHub link to new repo URL (#1744)
1.29
Added:
* add support for CloudFlare token (#1625)
* add support for MailinaBox DNS API (#1531)
* add support for Plesk XML API (#1567)
* add support for Variomedia DNS API
Fixed:
* fix IPv6 support for "automatic port forward" validation method (#1590)
Changed:
* validate IPv4 and IPv6 addresses before using them for "automatic port forward"
* enable IPv6 support on local ACME webservice (when system.ipv6allow is enabled)
1.28
Changed:
* correct minor spelling error (#1628)
* log filename not compatible with new log view (#1593)
1.27
Added:
* add support for Loopia DNS API (#1529)
* automations can now restart Captive Portal or IPsec service after cert renewal (#1534)
* add support for 60+ DNS APIs through Lexicon (#1524)
Fixed:
* don't break accounts when switching between stg/prod Let's Encrypt environments (#1528)
Changed:
* add py-dns-lexicon as plugin dependency to support it in DNS-01 out-of-the-box
* support acme.sh debug log level 2 and 3 (#1546)
1.26
Added:
* new automation: support cert upload via sftp (#1455)
* add support for OPNsense's BIND plugin (#1491)
* add support for DNS alias mode (#1492, #1301)
Changed:
* add headers for certificate options for the sake of clarity
1.25
Added:
* add support for netcup DNS API (#1350)
Fixed:
* updating an existing cert in Highwinds API failed with a 404 error (wrong HTTP method)
Changed:
* fix "Use of undefined constant" PHP errors
* treat certificate serial number as string not as integer
* move "remove certificate" button to the end of the button list
1.24
Added:
* add support for Domain-Offensive LetsEncrypt API dns_doapi (#1294)
* add support for Namecheap API (dns_namecheap)
* add support for Google Cloud DNS API dns_gcloud (#549)
* run acme.sh --remove when a cert is removed from the GUI (#1380)
* add a new button to remove the private key (#990)
Fixed:
* certificate status not correctly updated (#1307)
Changed:
* add log message when certificate status is updated (refs #1307)
1.23
Fixed:
* renewal interval is ignored (#1221)
1.22
Added:
* support DNS-01 with hosting.de API (#1234)
Changed:
* streamline log messages, use "AcmeClient" instead of "LE"
1.21
Added:
* possible breaking change: the API endpoint to update individual certs/accounts/etc. has been renamed from "set" to "update"
Fixed:
* bulk deleting does not work (#1163)
Changed:
* migrate to mutable controller (required to fix #1163)
1.20
Added:
* new button to reset all acme states, useful after importing a config backup to a new installation (#243)
1.19
Added:
* new automation: automatically upload certificates to Highwinds CDN (proof-of-concept, support for other APIs possible)
Changed:
* rename "Restart Actions" to "Automation" (the old name has always been rather clumsy)
* change "Automation" position in Menu (it's optional, the new position reflects this)
1.18
Added:
* add support for GratisDNS.dk (#1042)
* add support for ACME DNS
1.17
Fixed:
* fix OCSP always enabled (#794)
* fix acme operations when using multiple accounts (#789)
1.16
Added:
* add support for OCSP Must Staple extension
Fixed:
* fix ecc certs renewal bug
1.15
Added:
* add support to multiple dns api providers (#712)
Changed:
* mask passwords by using password fields (#707)
1.14
Added:
* add support for ClouDNS (#574)
1.13
Added:
* update acme.sh to 2.7.5 (#418)
Changed:
* fix missing fields for several DNS providers (#481)
1.12
Added:
* compatibility with HAProxy plugin version 2.0 (refs #330)
Fixed:
* fix missing fields for Hurricane Electric (#334)
1.11
Fixed:
* add missing field for DuckDNS (#287)
1.9
Added:
* update acme.sh to version 2.7.2 (#210)
* add support for new DNS API hooks (#225)
Fixed:
* Rename Certificate "Name" to "Common Name" for better clarity (#214)
* Fix title in "Renew" and "Revoke" dialogs
* Add dependency to BIND to fix nsupdate support
* fix 'Compilation failed: number too big' (#227)
1.8
Added:
* drop bundled acme.sh in favour of the FreeBSD port
Fixed:
* rename validation method "OPNsense Port Forward" to "OPNsense Web Service" to make it more clear that we're using an internal web service
1.7
Fixed:
* fix $backend is not declared (#132)
* fix null exception in api
1.6
Fixed:
* fix broken translation strings
1.5
Fixed:
* try to solve disconnection issue (mostly during auto-renewal) (#109)
* try to fix "Node no longer exists"
1.4
Changed:
* rename label "Validation Method" to "Challenge Type"
1.3
Changed:
* remove support for custom restart actions (#100)
* avoid log message on missing restart action
* simplify JS code
1.2
Fixed:
* properly import CA certificates (#84)
* don't make sensitive data world-readable
Changed:
* hide params for restart actions when not selected
* remove prefixes from validation name
* hide http service entries when not selected
* log acme status for each cert
1.1
Added:
* add HAProxy integration
Fixed:
* avoid API exception when HAProxy integration is incomplete
* avoid error message if no restart action was specified
* do not run restart actions if cert was not changed
Changed:
* add hide() trickery to hide entries when not selected
* relax fields validation (#70)
1.0
Initial release (#6)
@@ -33,6 +33,7 @@ namespace OPNsense\AcmeClient\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Base\UIModelGrid;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\AcmeClient\AcmeClient;
@@ -73,6 +74,29 @@ class AccountsController extends ApiMutableModelControllerBase
public function searchAction()
{
return $this->searchBase('accounts.account', array('enabled', 'name', 'email'), 'name');
return $this->searchBase('accounts.account', array('enabled', 'name', 'email', 'statusCode', 'statusLastUpdate'), 'name');
}
/**
* register account by uuid
* @param $uuid item unique id
* @return array status
*/
public function registerAction($uuid)
{
$result = array("result" => "failed");
if ($this->request->isPost()) {
$mdlAcme = new AcmeClient();
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('accounts.account.' . $uuid);
if ($node != null) {
$backend = new Backend();
$response = $backend->configdRun("acmeclient register-account ${uuid}");
return array("response" => $response);
}
}
}
return $result;
}
}
@@ -69,9 +69,11 @@ class CertificatesController extends ApiMutableModelControllerBase
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('certificates.certificate.' . $uuid);
if ($node != null) {
$cert_id = $node->id;
$backend = new Backend();
$response = $backend->configdRun("acmeclient remove-cert {$cert_id}");
$response = $backend->configdRun("acmeclient remove-cert {$uuid}");
// Give configd some time to start this operation before the
// cert is removed from config.
sleep(2);
}
}
return $this->delBase('certificates.certificate', $uuid);
@@ -101,9 +103,8 @@ class CertificatesController extends ApiMutableModelControllerBase
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('certificates.certificate.' . $uuid);
if ($node != null) {
$cert_id = $node->id;
$backend = new Backend();
$response = $backend->configdRun("acmeclient sign-cert {$cert_id}");
$response = $backend->configdRun("acmeclient sign-cert ${uuid}");
return array("response" => $response);
}
}
@@ -123,9 +124,8 @@ class CertificatesController extends ApiMutableModelControllerBase
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('certificates.certificate.' . $uuid);
if ($node != null) {
$cert_id = $node->id;
$backend = new Backend();
$response = $backend->configdRun("acmeclient remove-key {$cert_id}");
$response = $backend->configdRun("acmeclient remove-key ${uuid}");
}
}
return $result;
@@ -145,9 +145,8 @@ class CertificatesController extends ApiMutableModelControllerBase
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('certificates.certificate.' . $uuid);
if ($node != null) {
$cert_id = $node->id;
$backend = new Backend();
$response = $backend->configdRun("acmeclient revoke-cert {$cert_id}");
$response = $backend->configdRun("acmeclient revoke-cert ${uuid}");
return array("response" => $response);
}
}
@@ -167,9 +166,8 @@ class CertificatesController extends ApiMutableModelControllerBase
if ($uuid != null) {
$node = $mdlAcme->getNodeByReference('certificates.certificate.' . $uuid);
if ($node != null) {
$cert_id = $node->id;
$backend = new Backend();
$response = $backend->configdRun("acmeclient run-automation {$cert_id}");
$response = $backend->configdRun("acmeclient run-automation ${uuid}");
}
}
return $result;
@@ -182,7 +182,6 @@ class ServiceController extends ApiControllerBase
// finally run the syntax check
$response = $backend->configdRun("acmeclient configtest");
return array("result" => $response);
// TODO: We may also want to check for duplicate cert names, etc.
}
/**
@@ -384,7 +384,7 @@ class SettingsController extends ApiMutableModelControllerBase
}
/**
* Check wether the Google Cloud plugin is installed.
* Check whether the Google Cloud plugin is installed.
* @return array status action
*/
public function getGcloudPluginStatusAction()
@@ -402,7 +402,7 @@ class SettingsController extends ApiMutableModelControllerBase
}
/**
* Check wether the BIND plugin is installed.
* Check whether the BIND plugin is installed.
* @return array status action
*/
public function getBindPluginStatusAction()
@@ -42,9 +42,9 @@
</field>
<field>
<id>certificate.validationMethod</id>
<label>Validation Method</label>
<label>Challenge Type</label>
<type>dropdown</type>
<help><![CDATA[Set the Let's Encrypt validation method for this certificate.]]></help>
<help><![CDATA[Set the Let's Encrypt challenge type for this certificate.]]></help>
</field>
<field>
<id>certificate.autoRenewal</id>
@@ -9,19 +9,19 @@
<id>acmeclient.settings.autoRenewal</id>
<label>Auto Renewal</label>
<type>checkbox</type>
<help><![CDATA[Enable automatic renewal for certificates to prevent expiration. This will add a cronjob to the system. You may want to customize the cronjob schedule to your needs, because re-issueing a certificate may lead to a short downtime, depending on the selected validation method and service.]]></help>
<help><![CDATA[Enable automatic renewal for certificates to prevent expiration. This will add a cronjob to the system. You may want to customize the cronjob schedule to your needs, because re-issueing a certificate may lead to a short downtime, depending on the selected challenge type and service.]]></help>
</field>
<field>
<id>acmeclient.settings.environment</id>
<label>Let's Encrypt Environment</label>
<type>dropdown</type>
<help><![CDATA[Choose Let's Encrypts staging environment when using it for the first time or while testing new validation methods. The staging environment offers <a href="https://letsencrypt.org/docs/staging-environment/">relaxed rate limits</a>.<br/><div class="text-info"><b>NOTE:</b>Certificates signed by the staging environment are NOT valid. You need to forcefully re-sign (or delete and re-create) them after switching from staging to production environment.</div>]]></help>
<help><![CDATA[Choose Let's Encrypts staging environment when using it for the first time or while testing new challenge types. The staging environment offers <a href="https://letsencrypt.org/docs/staging-environment/">relaxed rate limits</a>.<br/><div class="text-info"><b>NOTE:</b>Certificates signed by the staging environment are NOT valid. You need to forcefully re-sign (or delete and re-create) them after switching from staging to production environment.</div>]]></help>
</field>
<field>
<id>acmeclient.settings.haproxyIntegration</id>
<label>HAProxy Integration</label>
<type>checkbox</type>
<help><![CDATA[Enable automatic integration with the OPNsense HAProxy plugin. <b>Requires that the OPNsense HAProxy plugin is installed.</b> This will automatically add the required backend, server, action and ACL for you. You just need to select your HAProxy frontend when configuration the certificate or validation method. <div class="text-info"><b>NOTE:</b>This will only work for HTTP-01 validation and HAProxy frontends running in <i>http</i> mode; TCP frontends are not supported.</div>]]></help>
<help><![CDATA[Enable automatic integration with the OPNsense HAProxy plugin. <b>Requires that the OPNsense HAProxy plugin is installed.</b> This will automatically add the required backend, server, action and ACL for you. You just need to select your HAProxy frontend when configuration the certificate or challenge type. <div class="text-info"><b>NOTE:</b>This will only work for HTTP-01 validation and HAProxy frontends running in <i>http</i> mode; TCP frontends are not supported.</div>]]></help>
</field>
<field>
<id>acmeclient.settings.logLevel</id>
@@ -33,7 +33,7 @@
<id>acmeclient.settings.challengePort</id>
<label>Local HTTP Port</label>
<type>text</type>
<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. The local webserver is NOT directly exposed to the outside and should NOT use port 80 or any other well-known port. This setting allows you to change the local port of this webserver in case it interferes with another local service. Defaults to port 43580.]]></help>
<help><![CDATA[When using HTTP-01 as challenge type, a local webserver is used to provide acme challenge data to the Let's Encrypt servers. The local webserver is NOT directly exposed to the outside and should NOT use port 80 or any other well-known port. This setting allows you to change the local port of this webserver in case it interferes with another local service. Defaults to port 43580.]]></help>
<advanced>true</advanced>
</field>
<field>
@@ -0,0 +1,263 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient;
use OPNsense\Core\Config;
/**
* Manage Let's Encrypt accounts with acme.sh
* @package OPNsense\AcmeClient
*/
class LeAccount extends LeCommon
{
public const CONFIG_PATH = 'accounts.account';
/*
* create the object by collecting and storing all required data
* @param $uuid string the UUID of the configuration object
*/
public function __construct(string $uuid)
{
// Store basic information
$this->uuid = $uuid;
// Get config object
$this->loadConfig(self::CONFIG_PATH, $this->uuid);
// Set log level
$this->setLoglevel();
// Set Let's Encrypt environment
$this->setEnvironment();
// Store acme filenames
$this->acme_args[] = '--home ' . self::ACME_HOME_DIR;
}
/**
* generate private key and ACME config for this account
*/
public function generateKey()
{
// Collect account information
$account_conf_dir = self::ACME_BASE_ACCOUNT_DIR . '/' . (string)$this->config->id . '_' . $this->environment;
$account_conf_file = $account_conf_dir . '/account.conf';
$account_key_file = $account_conf_dir . '/account.key';
$account_json_file = $account_conf_dir . '/account.json';
$account_ca_file = $account_conf_dir . '/ca.conf';
$acme_conf = array();
$acme_conf[] = "CERT_HOME='" . self::ACME_HOME_DIR . "'";
$acme_conf[] = "LOG_FILE='" . self::ACME_LOG_FILE . "'";
$acme_conf[] = "ACCOUNT_KEY_PATH='" . $account_key_file . "'";
$acme_conf[] = "ACCOUNT_JSON_PATH='" . $account_json_file . "'";
$acme_conf[] = "CA_CONF='" . $account_ca_file . "'";
if (!empty((string)$this->config->email)) {
$acme_conf[] = "ACCOUNT_EMAIL='" . (string)$this->config->email . "'";
}
// Store some values for later re-use
$this->account_conf_file = $account_conf_file;
// Create account configuration file
if (!is_dir($account_conf_dir)) {
mkdir($account_conf_dir, 0700, true);
}
file_put_contents($account_conf_file, (string)implode("\n", $acme_conf) . "\n");
chmod($account_conf_file, 0600);
// Check if account key already exists both in filesystem and in config
if (!is_file($account_key_file) || empty((string)$this->config->key)) {
LeUtils::log_debug('creating account key for ' . (string)$this->config->name, $this->debug);
// Check if we have an account key in our configuration
if (!empty((string)$this->config->key)) {
LeUtils::log_debug('exporting existing account key to filesystem for ' . (string)$this->config->name, $this->debug);
// Write key to disk
file_put_contents($account_key_file, (string)base64_decode((string)$this->config->key));
chmod($account_key_file, 0600);
return true;
} else {
LeUtils::log_debug('generating a new account key for ' . (string)$this->config->name, $this->debug);
// Preparation to run acme client
$proc_env = $this->acme_env; // env variables for proc_open()
$proc_env['PATH'] = $this::ACME_ENV_PATH;
$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();
// Run acme client to generate a account key
$acmecmd = '/usr/local/sbin/acme.sh '
. '--createAccountKey '
. implode(' ', $this->acme_args) . ' '
. '--accountkeylength ' . self::ACME_ACCOUNT_KEY_LENGTH . ' '
. "--accountconf ${account_conf_file}";
LeUtils::log_debug('running acme.sh command: ' . (string)$acmecmd, $this->debug);
$proc = proc_open($acmecmd, $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);
} else {
LeUtils::log_error('unable to start acme client process');
$this->setStatus(500);
return false;
}
// Check exit code
if ($result) {
LeUtils::log_error('failed to create a new account key for ' . (string)$this->config->name);
$this->setStatus(300);
return false;
}
// Read account key file
$account_key_content = @file_get_contents($account_key_file);
if (empty($account_key_content) || ($account_key_content == false)) {
LeUtils::log_error("unable to read account key from file ${account_key_file}");
$this->setStatus(500);
return false;
}
// Reload to get most recent config
Config::getInstance()->forceReload();
$this->loadConfig(self::CONFIG_PATH, $this->uuid);
// Import account key into config
$this->config->key = base64_encode($account_key_content);
// Serialize to config and save
$this->model->serializeToConfig();
Config::getInstance()->save();
// Refresh config objects
Config::getInstance()->forceReload();
$this->loadConfig(self::CONFIG_PATH, $this->uuid);
if (empty((string)$this->config->key)) {
$this->setStatus(500);
LeUtils::log_error('failed to save account key for ' . (string)$this->config->name);
return false;
}
LeUtils::log_debug('successfully created account key for ' . (string)$this->config->name, $this->debug);
return true;
}
}
return true;
}
/**
* check if account is already registered
* @return bool
*/
public function isRegistered()
{
if (!empty((string)$this->config->statusLastUpdate) and !empty((string)$this->config->key) and ((string)$this->config->statusCode == '200')) {
return true;
}
return false;
}
/**
* register account with Let's Encrypt
* @return bool
*/
public function register()
{
if (!($this->isEnabled())) {
LeUtils::log('ignoring disabled account: ' . (string)$this->config->name);
return false;
}
// Make sure a private already exists
if (!($this->generateKey())) {
LeUtils::log_error('aborting registration due to issues with account key: ' . (string)$this->config->name);
return false;
}
// Check if account is already registered
if (!($this->isRegistered())) {
LeUtils::log_debug('starting account registration for ' . (string)$this->config->name, $this->debug);
// Preparation to run acme client
$proc_env = $this->acme_env; // env variables for proc_open()
$proc_env['PATH'] = $this::ACME_ENV_PATH;
$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();
// Run acme client
$acmecmd = '/usr/local/sbin/acme.sh '
. '--registeraccount '
. implode(' ', $this->acme_args) . ' '
. '--accountconf ' . $this->account_conf_file;
LeUtils::log_debug('running acme.sh command: ' . (string)$acmecmd, $this->debug);
$proc = proc_open($acmecmd, $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);
} else {
LeUtils::log_error('unable to start acme client process');
$this->setStatus(500);
return false;
}
// Check validation result
if ($result) {
LeUtils::log_error('account registration failed for ' . $this->config->name);
$this->setStatus(400);
return false;
}
// Update account status.
LeUtils::log_error('account registration successful for ' . $this->config->name);
$this->setStatus(200);
} else {
LeUtils::log_debug('account already registered: ' . (string)$this->config->name, $this->debug);
}
return true;
}
}
@@ -0,0 +1,97 @@
<?php
/*
* Copyright (C) 2020 Frank Wall (derived from OPNsense\Backup)
* Copyright (C) 2018 Deciso B.V.
* Copyright (C) 2018 Franco Fichtner <franco@opnsense.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\AcmeClient\LeAccount;
use OPNsense\AcmeClient\LeUtils;
/**
* LeAutomation stub file, contains shared logic for all automations.
* @package OPNsense\AcmeClient
*/
abstract class Base extends \OPNsense\AcmeClient\LeCommon
{
public const CONFIG_PATH = 'actions.action';
/**
* Initialize LeAutomation object by adding the required configuration.
* @return boolean
*/
public function init(string $certid, string $accountuuid)
{
// Get config object
$this->loadConfig(self::CONFIG_PATH, $this->uuid);
// Get account object to query ID
$account = new LeAccount($accountuuid);
// Store auxiliary information (required to glue stuff together)
$this->cert_id = $certid;
$this->account_id = (string)$account->id;
$this->account_uuid = (string)$account->uuid;
// Set log level
$this->setLoglevel();
// Set Let's Encrypt environment
$this->setEnvironment();
return true;
}
/**
* run all tasks related to this automation
* @return boolean
*/
public function run()
{
if (!($this->isEnabled())) {
LeUtils::log('ignoring disabled automation: ' . (string)$this->config->name);
return true; // not an error
}
LeUtils::log('running automation: ' . $this->config->name);
$backend = new \OPNsense\Core\Backend();
$response = $backend->configdRun((string)$this->command, $this->command_args);
return true;
}
/**
* get automation type from configuration
* @return string
*/
public function getType()
{
return $this->config->type;
}
}
@@ -0,0 +1,51 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
use OPNsense\AcmeClient\LeUtils;
/**
* Run selected configd command
* @package OPNsense\AcmeClient
*/
class Configd extends Base implements LeAutomationInterface
{
public function prepare()
{
// Make sure a configd command was specified.
if (empty((string)$this->config->configd)) {
LeUtils::log_error('no configd command specified for automation: ' . $this->config->name);
return false;
}
$this->command = (string)$this->config->configd;
return true;
}
}
@@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
/**
* Restart OPNsense WebGUI
* @package OPNsense\AcmeClient
*/
class RestartGui extends Base implements LeAutomationInterface
{
public function prepare()
{
$this->command = 'webgui restart 2';
$this->command_args = true;
return true;
}
}
@@ -0,0 +1,44 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
/**
* Restart local HAProxy service
* @package OPNsense\AcmeClient
*/
class RestartHaproxy extends Base implements LeAutomationInterface
{
public function prepare()
{
$this->command = 'haproxy restart';
return true;
}
}
@@ -0,0 +1,44 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
/**
* Restart local Nginx service
* @package OPNsense\AcmeClient
*/
class RestartNginx extends Base implements LeAutomationInterface
{
public function prepare()
{
$this->command = 'nginx restart';
return true;
}
}
@@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
/**
* Upload certificate to Highwinds CDN API
* @package OPNsense\AcmeClient
*/
class UploadHighwinds extends Base implements LeAutomationInterface
{
public function prepare()
{
$command = 'acmeclient upload_highwinds ' . $this->cert_id . ' ' . $this->config->id;
$this->command = $command;
return true;
}
}
@@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient\LeAutomation;
use OPNsense\AcmeClient\LeAutomationInterface;
/**
* Upload certificate via SFTP to arbitrary hosts
* @package OPNsense\AcmeClient
*/
class UploadSftp extends Base implements LeAutomationInterface
{
public function prepare()
{
$command = 'acmeclient upload-sftp ' . $this->cert_id . ' ' . $this->config->id;
$this->command = $command;
return true;
}
}
@@ -0,0 +1,81 @@
<?php
/**
* Copyright (C) 2020 Frank Wall (derived from OPNsense/Auth)
* Copyright (C) 2018 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient;
use \OPNsense\AcmeClient\AcmeClient;
/**
* Class LeAutomationFactory
* @package OPNsense\AcmeClient
*/
class LeAutomationFactory
{
public const CONFIG_PATH = 'actions.action';
/**
* create an automation object from a UUID
* @param $uuid string UUID of the automation object
* @return LeAutomation object or null if not found
*/
public function getAutomation(string $uuid)
{
// Ensure that the automation can be found in config.
$model = new \OPNsense\AcmeClient\AcmeClient();
$obj = $model->getNodeByReference(self::CONFIG_PATH . '.' . $uuid);
if ($obj == null) {
LeUtils::log_error("automation not found: ${uuid}");
return null;
}
// Convert to PascalCase, required to find the class name.
$auto_name = str_replace(' ', '', ucwords(str_replace(array('-', '_'), ' ', (string)$obj->type)));
// Search class name
foreach (glob(__DIR__ . "/LeAutomation/*.php") as $filename) {
$file_found = basename($filename, '.php');
try {
$reflClass = new \ReflectionClass("OPNsense\\AcmeClient\\LeAutomation\\{$file_found}");
} catch (\ReflectionException $e) {
break;
}
if ($reflClass->implementsInterface('OPNsense\\AcmeClient\\LeAutomationInterface')) {
if ($file_found == $auto_name) {
// Create new object
$objAuto = $reflClass->newInstance();
$objAuto->setUuid($uuid);
return $objAuto;
}
}
}
LeUtils::log_error("automation not supported: " . (string)$obj->type . " (${uuid})");
return null;
}
}
@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2020 Frank Wall (derived from OPNsense\Backup)
* Copyright (C) 2018 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient;
/**
* Interface for Let's Encrypt automations
* @package OPNsense\AcmeClient
*/
interface LeAutomationInterface
{
/**
* add configuration that is required only for this specific automation
* @return bool
*/
public function prepare();
}
@@ -0,0 +1,214 @@
<?php
/*
* Copyright (C) 2020 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\AcmeClient;
use OPNsense\Core\Config;
use OPNsense\AcmeClient\LeUtils;
/**
* Common constants and functions for all Let's Encrypt classes
* @package OPNsense\AcmeClient
*/
abstract class LeCommon
{
// Static acme.sh directories and files
public const ACME_BASE_ACCOUNT_DIR = '/var/etc/acme-client/accounts';
public const ACME_BASE_CERT_DIR = '/var/etc/acme-client/certs';
public const ACME_BASE_CONFIG_DIR = '/var/etc/acme-client/configs';
public const ACME_HOME_DIR = '/var/etc/acme-client/home';
public const ACME_LOG_FILE = '/var/log/acme.sh.log';
// Defaults for acme.sh
public const ACME_ACCOUNT_KEY_LENGTH = 4096;
public const ACME_ENV_PATH = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin';
// Filenames for certs, configs, ...
public const ACME_CERT_DIR = '/var/etc/acme-client/certs/%s/';
public const ACME_CERT_FILE = '/var/etc/acme-client/certs/%s/cert.pem';
public const ACME_CHAIN_FILE = '/var/etc/acme-client/certs/%s/chain.pem';
public const ACME_CONFIG_DIR = '/var/etc/acme-client/configs/%s/';
public const ACME_FULLCHAIN_FILE = '/var/etc/acme-client/certs/%s/fullchain.pem';
public const ACME_KEY_DIR = '/var/etc/acme-client/keys/%s/';
public const ACME_KEY_FILE = '/var/etc/acme-client/keys/%s/private.key';
// Runtime parameters for acme.sh
protected $acme_args = array(); # command line arguments to be passed to acme.sh
protected $acme_env = array(); # environment variables to be used when running acme.sh
protected $acme_keylength; # private key length in acme.sh compatible format
// Certificate details and configuration
protected $cert_id; # AcmeClient certificate object ID
protected $cert_name; # certificate name
protected $cert_altnames; # certificate altNames
protected $cert_aliasmode; # AcmeClient certificate object aliasmode
protected $cert_domainalias; # AcmeClient certificate object domain alias
protected $cert_challengealias; # AcmeClient certificate object challenge alias
protected $cert_keylength; # Private key length
// Account details
protected $account_id; # AcmeClient account object ID
protected $account_uuid; # AcmeClient account object UUID
// Automation details and configuration
protected $command; # configd command to run
protected $command_args; # optional args for configdRun()
// Basic object information
protected $config; # AcmeClient config object
protected $debug; # Debug logging (bool)
protected $environment; # Let's Encrypt environment (uses shortnames)
protected $force; # Force operation
protected $model; # AcmeClient model object
protected $uuid; # AcmeClient config object uuid
protected $validation; # LeValidation object
/**
* get ID from auxiliary configuration object
* @return string
*/
public function getId()
{
return (string)$this->config->id;
}
/**
* get UUID from auxiliary configuration object
* @return string
*/
public function getUuid()
{
return (string)$this->config->uuid;
}
/**
* load config object from configuration
* @return bool
*/
public function loadConfig(string $path, string $uuid)
{
// Get config object
$model = new \OPNsense\AcmeClient\AcmeClient();
$obj = $model->getNodeByReference("${path}.${uuid}");
if ($obj == null) {
LeUtils::log_error("config of type ${path} not found: ${uuid}");
return false;
}
// Store config objects
$this->config = $obj;
$this->model = $model;
return true;
}
/**
* check if object is enabled in configuration
* @return bool
*/
public function isEnabled()
{
return (string)$this->config->enabled == 1 ? true : false;
}
/**
* set Let's Encrypt environment for acme.sh
*/
public function setEnvironment()
{
$this->environment = (string)$this->model->getNodeByReference('settings.environment');
$this->acme_args[] = $this->environment == 'stg' ? '--staging' : null;
}
/**
* set log level for acme.sh and configure optional debug logging
*/
public function setLoglevel()
{
$loglevel = (string)$this->model->getNodeByReference('settings.logLevel');
switch ($loglevel) {
case 'extended':
$this->acme_args[] = '--log-level 2';
$this->debug = false;
break;
case 'debug':
$this->acme_args[] = '--debug';
$this->debug = true;
break;
case 'debug2':
$this->acme_args[] = '--debug 2';
$this->debug = true;
break;
case 'debug3':
$this->acme_args[] = '--debug 3';
$this->debug = true;
break;
default:
$this->acme_args[] = '--log-level 1';
$this->debug = false;
break;
}
}
/**
* update status information to reflect the result of the last operation
* Supported status codes are:
* 100 pending
* 200 cert issued / acct registered
* 250 cert revoked / acct deactivated
* 300 configuration error
* 400 issue/renew/registration failed
* 500 internal error (code issues, bad luck, unexpected errors, ...)
* Feel free to add more status codes to support new use-cases.
* @return bool
*/
public function setStatus(int $statusCode)
{
// Update attributes.
$this->config->statusCode = $statusCode;
$this->config->statusLastUpdate = time();
// Serialize to config and save
Config::getInstance()->unlock();
$this->model->serializeToConfig();
Config::getInstance()->save();
// Reload to get most recent config
Config::getInstance()->forceReload();
$this->loadConfig($this::CONFIG_PATH, $this->uuid);
return true;
}
/**
* set UUID of auxiliary configuration object
*/
public function setUuid(string $uuid)
{
$this->uuid = $uuid;
}
}

Some files were not shown because too many files have changed in this diff Show More