security/acme-client: make it possible to use multiple CAs

This commit is contained in:
Frank Wall
2021-08-08 00:02:18 +02:00
parent 6ebbaf7555
commit be9aa41ae1
11 changed files with 60 additions and 35 deletions
+6 -2
View File
@@ -11,15 +11,19 @@ Plugin Changelog
3.0
Added:
* add support for new ACME CA's: buypass, buypass_test, sslcom, zerossl (#2361)
* add support for new ACME CAs: buypass, buypass_test, sslcom, zerossl (#2361)
* add CA setting to accounts, make it possible to use multiple CAs
* add introduction pages and an option to hide them
* add tooltips for account command buttons (#2188)
* add support for custom ACME EAB kid/hmac when registering accounts
Fixed:
* properly set/get the UUID of LE objects
Changed:
* rename plugin from "Let's Encrypt client" to "ACME Client" (#2361)
* change the suffix for imports to the certificate storage to "ACME Client" (#2361)
* rename "Let's Encrypt Environment" to "ACME CA" (#2361)
* rename "Let's Encrypt Environment" to "ACME CA" and move to account settings (#2361)
* preserve old LE accounts/certs by adding a compatibility layer (#2361)
* update tooltip style for 21.7 (#2188)
* show more options in list view for challenge types and automations
@@ -17,12 +17,22 @@
<type>text</type>
<help>Description for this account.</help>
</field>
<field>
<label>NOTE: Settings below must not be changed after account registration.</label>
<type>header</type>
</field>
<field>
<id>account.email</id>
<label>E-Mail Address</label>
<type>text</type>
<help>Optional e-mail address for this account.</help>
</field>
<field>
<id>account.ca</id>
<label>ACME CA</label>
<type>dropdown</type>
<help><![CDATA[The ACME CA that should be used for this account and all associated certificates. Note that some of them offer paid services and may require a subscription. Check the <a href="https://github.com/acmesh-official/acme.sh/wiki/Server" target="_blank">acme.sh documentation</a> for a list of supported CAs.]]></help>
</field>
<field>
<label>Optional EAB Credentials</label>
<type>header</type>
@@ -11,12 +11,6 @@
<type>checkbox</type>
<help><![CDATA[Enable automatic renewal for certificates to prevent expiration. This will add a cron job to the system. You may want to customize the cron job 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.ca</id>
<label>ACME CA</label>
<type>dropdown</type>
<help><![CDATA[The ACME CA that should be used to issue or renew certificates. Note that some of them offer paid services and may require a subscription. Check the <a href="https://github.com/acmesh-official/acme.sh/wiki/Server" target="_blank">acme.sh documentation</a> for a list of supported CAs.]]></help>
</field>
<field>
<id>acmeclient.settings.haproxyIntegration</id>
<label>HAProxy Integration</label>
@@ -54,7 +54,7 @@ class LeAccount extends LeCommon
$this->setLoglevel();
// Set ACME CA
$this->setCa();
$this->setCa($uuid);
// Store acme filenames
$this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR);
@@ -213,7 +213,7 @@ class LeAccount extends LeCommon
LeUtils::log_debug('starting account registration for ' . (string)$this->config->name, $this->debug);
// Check if ACME External Account Binding (EAB) is enabled
if (!empty((string)$this->config->eab_kid) && !empty((string)$this->config->eab_hmac) {
if (!empty((string)$this->config->eab_kid) && !empty((string)$this->config->eab_hmac)) {
LeUtils::log_debug('enabling ACME EAB for this account', $this->debug);
$this->acme_args[] = LeUtils::execSafe('--eab-kid %s', $this->config->eab_kid);
$this->acme_args[] = LeUtils::execSafe('--eab-hmac-key %s', $this->config->eab_hmac);
@@ -64,7 +64,7 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon
$this->setLoglevel();
// Set ACME CA
$this->setCa();
$this->setCa($accountuuid);
return true;
}
@@ -75,7 +75,7 @@ class LeCertificate extends LeCommon
$this->setLoglevel();
// Set ACME CA
$this->setCa();
$this->setCa((string)$this->config->account);
// Handle special key types
if ($this->config->keyLength == 'key_ec256' || $this->config->keyLength == 'key_ec384') {
@@ -351,6 +351,7 @@ class LeCertificate extends LeCommon
return false;
}
LeUtils::log("${acme_action} certificate: " . (string)$this->config->name);
LeUtils::log('using CA: ' . $this->ca);
// Ensure that account is registered.
if (!($this->setAccount())) {
@@ -105,7 +105,7 @@ abstract class LeCommon
*/
public function getUuid()
{
return (string)$this->config->uuid;
return (string)$this->uuid;
}
/**
@@ -124,6 +124,7 @@ abstract class LeCommon
// Store config objects
$this->config = $obj;
$this->model = $model;
$this->uuid = $uuid;
return true;
}
@@ -139,16 +140,28 @@ abstract class LeCommon
/**
* set ACME CA for acme.sh
*/
public function setCa()
public function setCa(string $uuid)
{
$this->ca = (string)$this->model->getNodeByReference('settings.ca');
$this->acme_args[] = LeUtils::execSafe('--server %s', $this->ca);
// Get account config object
$model = new \OPNsense\AcmeClient\AcmeClient();
$obj = $model->getNodeByReference("accounts.account.${uuid}");
if (empty($obj) || $obj == null) {
LeUtils::log_error("unable to set CA, account not found: ${uuid}");
return false;
}
// Extract ACME CA from account config
$acme_ca = (string)$obj->ca;
$this->ca = $acme_ca;
// Add CA to acme arguments
$this->acme_args[] = LeUtils::execSafe('--server %s', $acme_ca);
// Evaluate how the CA should be represented in filenames.
// This is a compatibility layer. It ensures that old files that
// were generated for the Let's Encrypt Production/Staging CA
// can still be used.
switch ($this->ca) {
switch ($acme_ca) {
case 'letsencrypt':
$ca_compat = 'prod';
break;
@@ -156,7 +169,7 @@ abstract class LeCommon
$ca_compat = 'stg';
break;
default:
$ca_compat = $this->ca;
$ca_compat = $acme_ca;
break;
}
$this->ca_compat = $ca_compat;
@@ -74,7 +74,7 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon
$this->setLoglevel();
// Set ACME CA
$this->setCa();
$this->setCa($accountuuid);
// Store acme hook
switch ((string)$this->config->method) {
@@ -34,18 +34,6 @@
<stg>Staging Environment</stg>
</OptionValues>
</environment>
<ca type="OptionField">
<Required>Y</Required>
<default></default>
<OptionValues>
<buypass>Buypass</buypass>
<buypass_test>Buypass Test CA</buypass_test>
<letsencrypt>Let's Encrypt [default]</letsencrypt>
<letsencrypt_test>Let's Encrypt Test CA</letsencrypt_test>
<sslcom>SSL.com</sslcom>
<zerossl>ZeroSSL</zerossl>
</OptionValues>
</ca>
<challengePort type="IntegerField">
<default>43580</default>
<MinimumValue>1024</MinimumValue>
@@ -148,6 +136,18 @@
<email type="EmailField">
<Required>N</Required>
</email>
<ca type="OptionField">
<Required>Y</Required>
<default>letsencrypt</default>
<OptionValues>
<buypass>Buypass</buypass>
<buypass_test>Buypass Test CA</buypass_test>
<letsencrypt>Let's Encrypt [default]</letsencrypt>
<letsencrypt_test>Let's Encrypt Test CA</letsencrypt_test>
<sslcom>SSL.com</sslcom>
<zerossl>ZeroSSL</zerossl>
</OptionValues>
</ca>
<eab_kid type="TextField">
<Required>N</Required>
<mask>/^.{1,8192}$/u</mask>
@@ -46,9 +46,12 @@ class M3_0_0 extends BaseModelMigration
$new_ca = 'letsencrypt_test';
break;
}
// Set new CA
$model->settings->ca = $new_ca;
$model->settings->environment = null; // clear old value
// Search accounts
foreach ($model->getNodeByReference('accounts.account')->iterateItems() as $account) {
// Set CA
$account->ca = $new_ca;
}
}
}
@@ -260,7 +260,7 @@ POSSIBILITY OF SUCH DAMAGE.
<p>{{ lang._("Setting up this plugin for the first time involves the following steps") }}</p>
<ul>
<li>{{ lang._('%sEnable%s the plugin: When enabling this plugin in the %ssettings%s, a lightweight service is started and cron jobs are added for automatic tasks.') | format('<b>', '</b>', '<a href="/ui/acmeclient#settings">', '</a>') }}</li>
<li>{{ lang._('Create an %saccount%s: An %saccount%s is required and will be automatically at the chosen CA.') | format('<b>', '</b>', '<a href="/ui/acmeclient/accounts">', '</a>') }}</li>
<li>{{ lang._('Create an %saccount%s: An %saccount%s is required. It determines which CA will be used for all associated certificates.') | format('<b>', '</b>', '<a href="/ui/acmeclient/accounts">', '</a>') }}</li>
<li>{{ lang._('Set up a %schallenge type%s: Choose the %schallenge type%s that works best for you and if necessary, add the credentials for your DNS provider.') | format('<b>', '</b>', '<a href="/ui/acmeclient/validations">', '</a>') }}</li>
<li>{{ lang._('Add %sautomations%s: This is optional, but recommended when using short-lived certificates. %sAutomations%s allow to automatically run tasks when a certificate was created or renewed.') | format('<b>', '</b>', '<a href="/ui/acmeclient/actions">', '</a>') }}</li>
<li>{{ lang._('Create %scertificates%s: Finally create the %scertificates%s and let the CA complete the validation process.') | format('<b>', '</b>', '<a href="/ui/acmeclient/certificates">', '</a>') }}</li>