diff --git a/www/caddy/pkg-descr b/www/caddy/pkg-descr index 46c80403a..c86f8a3f8 100644 --- a/www/caddy/pkg-descr +++ b/www/caddy/pkg-descr @@ -17,6 +17,7 @@ Plugin Changelog * Add: basic_auth per handler (opnsense/plugins/issues/4619) * Change: the ACL has been changed to "page-caddy" in "System: Access: Privileges". Privilege must be reassigned if used. (opnsense/plugins/issues/4623) +* Change: standalone certificate widget has been removed, use system default certificate widget instead. (opnsense/plugins/pull/4637) 1.8.4 diff --git a/www/caddy/src/etc/ssl/ext_sources/caddy.conf b/www/caddy/src/etc/ssl/ext_sources/caddy.conf new file mode 100644 index 000000000..08b1368b5 --- /dev/null +++ b/www/caddy/src/etc/ssl/ext_sources/caddy.conf @@ -0,0 +1,4 @@ +[location] +base=/var/db/caddy/data/caddy/certificates +pattern=.*\.crt$ +description=Caddy diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/DiagnosticsController.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/DiagnosticsController.php index edcb55389..e7ba77be4 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/DiagnosticsController.php +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/DiagnosticsController.php @@ -82,22 +82,4 @@ class DiagnosticsController extends ApiMutableModelControllerBase return ["status" => "success", "content" => $responseArray['content']]; } - /** - * Fetch the hostnames, validity and expiration dates of automatic certificates as JSON. Consumed by Caddy widget. - */ - public function certificateAction() - { - $backend = new Backend(); - $response = $backend->configdRun('caddy certificate'); - - // Decode JSON to PHP array - $responseArray = json_decode($response, true); - - if (isset($responseArray['error'])) { - return ["status" => "failed", "message" => $responseArray['message']]; - } - - // Return the response as an array which gets automatically encoded to JSON - return ["status" => "success", "content" => $responseArray]; - } } diff --git a/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py b/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py index 9a15eb01f..27f933d99 100755 --- a/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py +++ b/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py @@ -30,8 +30,6 @@ import sys import json import os import subprocess -import asyncio -from datetime import datetime # Function to show the Caddy configuration from a JSON file @@ -69,88 +67,11 @@ def show_caddyfile(): print(json.dumps({"error": "General Error", "message": str(e)})) -# Function to extract certificate information using openssl command -async def extract_certificate_info(cert_path): - try: - # Execute the openssl command to get the expiration date with a timeout - result = await asyncio.wait_for( - asyncio.create_subprocess_exec( - 'openssl', 'x509', '-in', cert_path, '-noout', '-enddate', - stdout=subprocess.PIPE, stderr=subprocess.PIPE), - timeout=10) # Make sure tasks are cleaned up if they hang - - stdout, stderr = await result.communicate() - - # Check for errors in the execution - if result.returncode != 0: - error_message = stderr.decode().strip() - raise RuntimeError(f"Subprocess failed with error: {error_message}") - - # Decode output and process the information - expiration_date_str = stdout.decode().strip().split('=')[1] - - # Convert expiration date string to datetime object - expiration_date = datetime.strptime(expiration_date_str, "%b %d %H:%M:%S %Y GMT") - - # Determine the current date - now = datetime.now() - - # Calculate remaining days until expiration - remaining_days = (expiration_date - now).days - remaining_days = max(remaining_days, 0) # Ensure non-negative days - - # Extract the hostname from the filename - hostname = os.path.basename(cert_path).replace('.crt', '').lower() - if hostname.startswith("wildcard_"): - hostname = hostname.replace("wildcard_", "*", 1) - - return {'hostname': hostname, 'expiration_date': expiration_date_str, 'remaining_days': remaining_days} - except asyncio.TimeoutError as e: - # Handle timeout specific errors - raise RuntimeError(f"Timeout occurred while processing {cert_path}: {str(e)}") - except Exception as e: - raise RuntimeError(f"Error extracting certificate info for {cert_path}: {str(e)}") - - -# Function to find certificates and create tasks to extract info -async def find_certificates(base_dir): - tasks = [] - for root, dirs, files in os.walk(base_dir): - # Skip any directories named 'temp' - dirs[:] = [d for d in dirs if d != 'temp'] - for file in files: - if file.endswith('.crt'): - cert_path = os.path.join(root, file) - task = asyncio.create_task(extract_certificate_info(cert_path)) - tasks.append(task) - - if not tasks: - raise RuntimeError("No certificates were found in the specified directory.") - - results = await asyncio.gather(*tasks, return_exceptions=True) - return [result for result in results if not isinstance(result, Exception)] - - -# Function to show certificates, processing all found in the given directory -async def show_certificates(): - # Function to show certificates, processing all found in the given directory - base_dir = '/var/db/caddy/data/caddy/certificates' - try: - certificates_data = await find_certificates(base_dir) - if certificates_data: - print(json.dumps(certificates_data)) - else: - raise RuntimeError("No valid certificate data found.") - except Exception as e: - print(json.dumps({"error": "General Error", "message": str(e)})) - - # Action handler def perform_action(cmd_action): actions = { "config": show_caddy_config, - "caddyfile": show_caddyfile, - "certificate": lambda: asyncio.run(show_certificates()) + "caddyfile": show_caddyfile } action_func = actions.get(cmd_action) diff --git a/www/caddy/src/opnsense/service/conf/actions.d/actions_caddy.conf b/www/caddy/src/opnsense/service/conf/actions.d/actions_caddy.conf index be0997b93..0d768ce42 100644 --- a/www/caddy/src/opnsense/service/conf/actions.d/actions_caddy.conf +++ b/www/caddy/src/opnsense/service/conf/actions.d/actions_caddy.conf @@ -47,9 +47,3 @@ command:/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py caddyfil parameters: type:script_output message:Request Caddyfile - -[certificate] -command:/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_diagnostics.py certificate -parameters: -type:script_output -message:Check validity of automatic certificates diff --git a/www/caddy/src/opnsense/www/js/widgets/CaddyCertificate.js b/www/caddy/src/opnsense/www/js/widgets/CaddyCertificate.js deleted file mode 100644 index d3108ac4b..000000000 --- a/www/caddy/src/opnsense/www/js/widgets/CaddyCertificate.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024 Cedrik Pischem - * 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. - */ - -export default class CaddyCertificate extends BaseTableWidget { - constructor() { - super(); - this.tickTimeout = 30; - } - - getGridOptions() { - return { - // trigger overflow-y:scroll after 650px height - sizeToContent: 650 - }; - } - - getMarkup() { - const $container = $('
'); - const $caddyCertificateTable = this.createTable('caddyCertificateTable', { - headerPosition: 'none' - }); - - $container.append($caddyCertificateTable); - return $container; - } - - async onWidgetTick() { - const proxyData = await this.ajaxCall(`/api/caddy/reverse_proxy/${'get'}`); - if (!proxyData.caddy.general || proxyData.caddy.general.enabled === "0") { - this.displayError(`${this.translations.unconfigured}`); - return; - } - - const domains = Object.values(proxyData.caddy.reverseproxy?.reverse || []) - .map(proxy => proxy.FromDomain) - .filter(Boolean); - - const certificates = (await this.ajaxCall(`/api/caddy/diagnostics/${'certificate'}`)).content || []; - - // Display certificate if hostname in config and CN of stored cert on disk match - const matchingCertificates = certificates.filter(cert => domains.includes(cert.hostname)); - - if (matchingCertificates.length === 0) { - this.displayError(`${this.translations.nocerts}`); - return; - } - - this.clearError(); - this.processCertificates(matchingCertificates); - } - - displayError(message) { - const $error = $(``); - $('#caddyCertificateTable').empty().append($error); - } - - clearError() { - $('#caddyCertificateTable .error-message').remove(); - } - - processCertificates(certificates) { - $('.caddy-certificate-tooltip').tooltip('hide'); - - const rows = certificates.map(certificate => { - const colorClass = certificate.remaining_days === 0 - ? 'text-danger' - : certificate.remaining_days < 14 - ? 'text-warning' - : 'text-success'; - - const statusText = certificate.remaining_days === 0 - ? this.translations.expired - : this.translations.valid; - - const row = ` -