security/acme-client: add support for duplicate common names, fixes #3127

This commit is contained in:
Frank Wall
2024-01-11 00:25:35 +01:00
parent 7f8612f800
commit 7e73d5ab87
10 changed files with 159 additions and 5 deletions
+10
View File
@@ -8,8 +8,18 @@ WWW: https://github.com/acmesh-official/acme.sh
Plugin Changelog
================
4.0
NOTE: This is a new major release with backwards-incompatible changes.
Downgrade to older releases is not supported. Be sure to create a
full backup and include /var/etc/acme-client.
Changed:
* use a dedicated acme.sh runtime directory for every cert (#3127)
Fixed:
* fix sporadic command failure with gcloud DNS API (#3745)
* fix errors when the same Common Name is used multiple times (#3127)
3.20
@@ -224,6 +224,9 @@ class LeAccount extends LeCommon
return false;
}
// Fix account config
$this->fixConfig();
// Update account status.
LeUtils::log_error('account registration successful for ' . $this->config->name);
$this->setStatus(200);
@@ -233,4 +236,34 @@ class LeAccount extends LeCommon
return true;
}
/**
* Remove CERT_HOME property from account config,
* otherwise --cert-home will be ignored by acme.sh.
*/
public function fixConfig()
{
$account_conf_dir = self::ACME_BASE_ACCOUNT_DIR . '/' . (string)$this->config->id . '_' . $this->ca_compat;
$account_conf_file = $account_conf_dir . '/account.conf';
if (is_dir($account_conf_dir)) {
if (is_file($account_conf_file)) {
// Parse config file and remove property
$account_conf = parse_ini_file($account_conf_file);
if (isset($account_conf['CERT_HOME'])) {
unset($account_conf['CERT_HOME']);
}
// Convert array back to ini file format
$new_account_conf = array();
foreach ($account_conf as $key => $value) {
$new_account_conf[] = "${key}='${value}'";
}
// Write changes back to file
file_put_contents($account_conf_file, implode("\n", $new_account_conf) . "\n");
chmod($account_conf_file, 0600);
}
}
}
}
@@ -71,6 +71,7 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon
// Store acme filenames
$this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR);
$this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--certpath %s', sprintf(self::ACME_CERT_FILE, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--keypath %s', sprintf(self::ACME_KEY_FILE, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--capath %s', sprintf(self::ACME_CHAIN_FILE, $this->cert_id));
@@ -95,6 +95,7 @@ class LeCertificate extends LeCommon
// Store acme filenames
$this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR);
$this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->config->id));
$this->acme_args[] = LeUtils::execSafe('--certpath %s', $this->cert_file);
$this->acme_args[] = LeUtils::execSafe('--keypath %s', $this->cert_key_file);
$this->acme_args[] = LeUtils::execSafe('--capath %s', $this->cert_chain_file);
@@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2020-2021 Frank Wall
* Copyright (C) 2020-2024 Frank Wall
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -53,6 +53,7 @@ abstract class LeCommon
// 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_CERT_HOME_DIR = '/var/etc/acme-client/cert-home/%s';
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';
@@ -94,6 +94,7 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon
// Store acme filenames
$this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR);
$this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--certpath %s', sprintf(self::ACME_CERT_FILE, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--keypath %s', sprintf(self::ACME_KEY_FILE, $this->cert_id));
$this->acme_args[] = LeUtils::execSafe('--capath %s', sprintf(self::ACME_CHAIN_FILE, $this->cert_id));
@@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/AcmeClient</mount>
<version>3.5.0</version>
<version>4.0.0</version>
<description>A secure ACME Client plugin</description>
<items>
<settings>
@@ -75,7 +75,7 @@ class M1_6_0 extends BaseModelMigration
}
// Write changes back to file
file_put_contents($account_file, implode("\r\n", $new_account_conf) . "\n");
file_put_contents($account_file, implode("\n", $new_account_conf) . "\n");
chmod($account_file, 0600);
// Finally, rename account directory
@@ -0,0 +1,107 @@
<?php
/**
* Copyright (C) 2024 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\Migrations;
use OPNsense\Base\BaseModelMigration;
class M4_0_0 extends BaseModelMigration
{
public function run($model)
{
$acme_account_dir = '/var/etc/acme-client/accounts/';
$old_acme_home = '/var/etc/acme-client/home/';
$new_acme_home = '/var/etc/acme-client/cert-home/';
// Remove CERT_HOME property from all account configs
if (is_dir($acme_account_dir)) {
$account_files = glob($acme_account_dir . '*/account.conf');
foreach ($account_files as $account_file) {
if (is_file($account_file)) {
// Parse config file and remove property
$account_conf = parse_ini_file($account_file);
if (isset($account_conf['CERT_HOME'])) {
unset($account_conf['CERT_HOME']);
}
// Convert array back to ini file format
$new_account_conf = array();
foreach ($account_conf as $key => $value) {
$new_account_conf[] = "${key}='${value}'";
}
// Write changes back to file
file_put_contents($account_file, implode("\n", $new_account_conf) . "\n");
chmod($account_file, 0600);
}
}
}
// Create new acme home directory
if (!is_dir($new_acme_home)) {
mkdir($new_acme_home, 0750);
}
// Migrate all certificates to new directory
// OLD: /var/etc/acme-client/home/opnsense.example.com
// NEW: /var/etc/acme-client/cert-home/659971be677b69.19708532/opnsense.example.com
foreach ($model->getNodeByReference('certificates.certificate')->iterateItems() as $cert) {
$cert_id = (string)$cert->id;
$cert_name = (string)$cert->name;
$old_cert_home = $old_acme_home . $cert_name;
$new_cert_home = $new_acme_home . $cert_id . '/' . $cert_name;
$old_cert_home_ecc = $old_acme_home . $cert_name . '_ecc';
$new_cert_home_ecc = $new_acme_home . $cert_id . '/' . $cert_name . '_ecc';
$_parent_dir = $new_acme_home . $cert_id;
// Check if cert home directory exists
// Certs that haven't been issued yet don't need to be migrated.
if (is_dir($old_cert_home)) {
// Create parent directory
if (!is_dir($_parent_dir)) {
mkdir($_parent_dir, 0750);
}
// Rename cert home directory
rename($old_cert_home, $new_cert_home);
}
// Migrate ECC certs
if (is_dir($old_cert_home_ecc)) {
// Create parent directory
if (!is_dir($_parent_dir)) {
mkdir($_parent_dir, 0750);
}
// Rename cert home directory
rename($old_cert_home_ecc, $new_cert_home_ecc);
}
}
}
}
@@ -1,9 +1,9 @@
#!/bin/sh
ACME_BASE="/var/etc/acme-client"
ACME_DIRS="/var/etc/acme-client/certs /var/etc/acme-client/keys /var/etc/acme-client/configs /var/etc/acme-client/challenges /var/etc/acme-client/home"
ACME_DIRS="/var/etc/acme-client/certs /var/etc/acme-client/keys /var/etc/acme-client/configs /var/etc/acme-client/challenges /var/etc/acme-client/home /var/etc/acme-client/cert-home"
# Generate required directories and set owner/mode recursively.
# Create required directories and set owner/mode recursively.
for directory in ${ACME_DIRS}; do
mkdir -p ${directory}
chown -R root:wheel ${directory}