security/acme-client: add introduction pages

This commit is contained in:
Frank Wall
2021-08-05 23:03:21 +02:00
parent 8bc24bc9ad
commit bc0b544b02
13 changed files with 164 additions and 42 deletions
+1
View File
@@ -12,6 +12,7 @@ Plugin Changelog
Added:
* add support for new ACME CA's: buypass, buypass_test, sslcom, zerossl (#2361)
* add introduction pages and an option to hide them
Changed:
* rename plugin from "Let's Encrypt client" to "ACME Client" (#2361)
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2017 Frank Wall
* Copyright (C) 2017-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -41,6 +41,9 @@ class AccountsController extends \OPNsense\Base\IndexController
{
// include form definitions
$this->view->formDialogAccount = $this->getForm("dialogAccount");
// set additional view parameters
$mdlAcme = new \OPNsense\AcmeClient\AcmeClient();
$this->view->showIntro = (string)$mdlAcme ->settings->showIntro;
// choose template
$this->view->pick('OPNsense/AcmeClient/accounts');
}
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2017 Frank Wall
* Copyright (C) 2017-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -41,6 +41,9 @@ class ActionsController extends \OPNsense\Base\IndexController
{
// include form definitions
$this->view->formDialogAction = $this->getForm("dialogAction");
// set additional view parameters
$mdlAcme = new \OPNsense\AcmeClient\AcmeClient();
$this->view->showIntro = (string)$mdlAcme ->settings->showIntro;
// choose template
$this->view->pick('OPNsense/AcmeClient/actions');
}
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2017 Frank Wall
* Copyright (C) 2017-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -41,6 +41,9 @@ class CertificatesController extends \OPNsense\Base\IndexController
{
// include form definitions
$this->view->formDialogCertificate = $this->getForm("dialogCertificate");
// set additional view parameters
$mdlAcme = new \OPNsense\AcmeClient\AcmeClient();
$this->view->showIntro = (string)$mdlAcme ->settings->showIntro;
// choose template
$this->view->pick('OPNsense/AcmeClient/certificates');
}
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2017 Frank Wall
* Copyright (C) 2017-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -45,6 +45,9 @@ class IndexController extends \OPNsense\Base\IndexController
{
// include form definitions
$this->view->settingsForm = $this->getForm("settings");
// set additional view parameters
$mdlAcme = new \OPNsense\AcmeClient\AcmeClient();
$this->view->showIntro = (string)$mdlAcme ->settings->showIntro;
// pick the template to serve
$this->view->pick('OPNsense/AcmeClient/settings');
}
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2017 Frank Wall
* Copyright (C) 2017-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -41,6 +41,9 @@ class ValidationsController extends \OPNsense\Base\IndexController
{
// include form definitions
$this->view->formDialogValidation = $this->getForm("dialogValidation");
// set additional view parameters
$mdlAcme = new \OPNsense\AcmeClient\AcmeClient();
$this->view->showIntro = (string)$mdlAcme ->settings->showIntro;
// choose template
$this->view->pick('OPNsense/AcmeClient/validations');
}
@@ -29,6 +29,12 @@
<type>dropdown</type>
<help><![CDATA[Specifies the log level for acme.sh, default is "normal". All other log levels add information for debug purposes, but be aware that this will break the log formatting in the GUI. Levels "debug 2" and "debug 3" log successively deeper log messages from the acme.sh including messages from DNS-01 DNSAPI scripts.]]></help>
</field>
<field>
<id>acmeclient.settings.showIntro</id>
<label>Show introduction pages</label>
<type>checkbox</type>
<help><![CDATA[Uncheck to hide all additional introduction pages.]]></help>
</field>
<field>
<id>acmeclient.settings.challengePort</id>
<label>Local HTTP Port</label>
@@ -121,6 +121,10 @@
<debug3>debug 3</debug3>
</OptionValues>
</logLevel>
<showIntro type="BooleanField">
<Required>Y</Required>
<default>1</default>
</showIntro>
</settings>
<accounts>
<account type="ArrayField">
@@ -321,12 +321,28 @@ POSSIBILITY OF SUCH DAMAGE.
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#accounts">{{ lang._('Accounts') }}</a></li>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li {% if showIntro|default('0')=='1' %}class="active"{% endif %}><a data-toggle="tab" id="accounts-introduction" href="#subtab_accounts-introduction"><b>{{ lang._('Introduction') }}</b></a></li>
<li {% if showIntro|default('0')=='0' %}class="active"{% endif %}><a data-toggle="tab" id="accounts-tab" href="#accounts"><b>{{ lang._('Accounts') }}</b></a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="accounts" class="tab-pane fade in active">
<div class="content-box tab-content">
<div id="subtab_accounts-introduction" class="tab-pane fade {% if showIntro|default('0')=='1' %}in active{% endif %}">
<div class="col-md-12">
<h1>{{ lang._('Accounts') }}</h1>
<p>{{ lang._('In order to create certificates, an account is required. Also the following information should be considered:') }}</p>
<ul>
<li>{{ lang._('The account will be %sregistered automatically%s at the chosen CA. The CA will then associate new certificates to the selected account.') | format('<b>', '</b>') }}</li>
<li>{{ lang._('Usually CAs will let you know if something went wrong and a certificate is about to expire, therefore a %svalid e-mail address%s should be provided.') | format('<b>', '</b>') }}</li>
<li>{{ lang._('For certain use-cases it can be useful to register %smultiple accounts%s, but the policy of the CA should be respected with this regard.') | format('<b>', '</b>') }}</li>
</ul>
<p>{{ lang._('When requesting support from a CA the account ID may be required, %sthis documentation%s contains information how to get the internal account ID from the log files.') | format('<a href="https://letsencrypt.org/docs/account-id/">', '</a>') }}</p>
</div>
</div>
<div id="accounts" class="tab-pane fade {% if showIntro|default('0')=='0' %}in active{% endif %}">
<table id="grid-accounts" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogAccount">
<thead>
<tr>
@@ -351,7 +367,9 @@ POSSIBILITY OF SUCH DAMAGE.
</tr>
</tfoot>
</table>
</div>
</div>
{# include dialogs #}
@@ -1,6 +1,6 @@
{#
Copyright (C) 2017-2019 Frank Wall
Copyright (C) 2017-2021 Frank Wall
OPNsense® is Copyright © 2014-2015 by Deciso B.V.
All rights reserved.
@@ -196,12 +196,27 @@ POSSIBILITY OF SUCH DAMAGE.
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#actions">{{ lang._('Automation') }}</a></li>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li {% if showIntro|default('0')=='1' %}class="active"{% endif %}><a data-toggle="tab" id="actions-introduction" href="#subtab_actions-introduction"><b>{{ lang._('Introduction') }}</b></a></li>
<li {% if showIntro|default('0')=='0' %}class="active"{% endif %}><a data-toggle="tab" id="actions-tab" href="#actions"><b>{{ lang._('Automations') }}</b></a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="actions" class="tab-pane fade in active">
<div class="content-box tab-content">
<div id="subtab_actions-introduction" class="tab-pane fade {% if showIntro|default('0')=='1' %}in active{% endif %}">
<div class="col-md-12">
<h1>{{ lang._('Automations') }}</h1>
<p>{{ lang._('Automations are a completely optional feature, but they can make life much easier, especially when using short-lived certificates. Typically use-case include:') }}</p>
<ul>
<li>{{ lang._("%sRestart a service%s when a certificate was renewed to ensure that the newest certificate is being used. This is especially useful when using an ACME certificate for the OPNsense WebGUI or in combination with the HAProxy or NGINX plugins. Any OPNsense core service or plugin service may be restarted.") | format('<b>', '</b>') }}</li>
<li>{{ lang._("Copy a certificate to one or more other hosts using the %sSFTP/SSH protocol%s. This way OPNsense can be used as a central authority for ACME certificates and secrets for DNS providers can be kept on a secure device.") | format('<b>', '</b>') }}</li>
<li>{{ lang._("Deploy a certificate to an external service, for example a %sCDN%s provider.") | format('<b>', '</b>') }}</li>
</ul>
<p>{{ lang._("This plugin can theoretically utilize most of %sacme.sh's webhooks%s. However, not all webhooks are currently implemented. Feel free to submit a %sfeature request%s if support for a acme.sh webhook should be added to the plugin.") | format('<a href="https://github.com/acmesh-official/acme.sh/wiki/deployhooks">', '</a>', '<a href="https://github.com/opnsense/plugins/issues">', '</a>') }}</p>
</div>
</div>
<div id="actions" class="tab-pane fade {% if showIntro|default('0')=='0' %}in active{% endif %}">
<table id="grid-actions" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogAction">
<thead>
<tr>
@@ -225,6 +240,7 @@ POSSIBILITY OF SUCH DAMAGE.
</tfoot>
</table>
</div>
</div>
{# include dialogs #}
@@ -2,7 +2,7 @@
(Partially duplicates code from opnsense_bootgrid_plugin.js.)
Copyright (C) 2017-2020 Frank Wall
Copyright (C) 2017-2021 Frank Wall
Copyright (C) 2015 Deciso B.V.
OPNsense® is Copyright © 2014-2015 by Deciso B.V.
All rights reserved.
@@ -431,12 +431,28 @@ POSSIBILITY OF SUCH DAMAGE.
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#certificates">{{ lang._('Certificates') }}</a></li>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li {% if showIntro|default('0')=='1' %}class="active"{% endif %}><a data-toggle="tab" id="certificates-introduction" href="#subtab_certificates-introduction"><b>{{ lang._('Introduction') }}</b></a></li>
<li {% if showIntro|default('0')=='0' %}class="active"{% endif %}><a data-toggle="tab" id="certificates-tab" href="#certificates"><b>{{ lang._('Certificates') }}</b></a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="certificates" class="tab-pane fade in active">
<div class="content-box tab-content">
<div id="subtab_certificates-introduction" class="tab-pane fade {% if showIntro|default('0')=='1' %}in active{% endif %}">
<div class="col-md-12">
<h1>{{ lang._('Certificates') }}</h1>
<p>{{ lang._('This plugin supports an unlimited number of certificates. However, the CA may restrict the number of certificates per week or implement other rate-limits. Retrying a failed validation many times in a row may also cause further attempts to fail due to rate-limits. The CA documentation should contain further information.') }}</p>
<p>{{ lang._('The following principles apply when managing certificates with this plugin:') }}</p>
<ul>
<li>{{ lang._('Certificates must be %svalidated%s by the CA before they can be used. This process runs in the background and may take several minutes to complete. The progress can be monitored by using the %slog files%s.') | format('<b>', '</b>', '<a href="/ui/acmeclient/logs">', '</a>') }}</li>
<li>{{ lang._('Certificates are stored in the %sOPNsense certificate storage%s. When a CA has completed the validation of a certificate request, the resulting certificate is then automatically imported into the OPNsense certificate storage. The same applies when renewing certificates, the existing entry in the OPNsense certificate storage will automatically be updated.') | format('<a href="/system_certmanager.php">', '</a>') }}</li>
<li>{{ lang._('When removing a certificate from the plugin, the certificate in the %sOPNsense certificate storage%s is %sNOT removed%s, because it may still be used by a core application or another plugin. Obsolete certificates should be manually removed from the OPNsense certificate storage. Note that when creating a new certificate with the same name, a new certificated will be imported into the OPNsense certificate storage (instead of updating the existing entry).') | format('<a href="/system_certmanager.php">', '</a>', '<b>', '</b>') }}</li>
</ul>
<p>{{ lang._('When experiencing issues, try setting the log level to "debug" in %ssettings%s.') | format('<a href="/ui/acmeclient#settings">', '</a>') }}</p>
</div>
</div>
<div id="certificates" class="tab-pane fade {% if showIntro|default('0')=='0' %}in active{% endif %}">
<table id="grid-certificates" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogCertificate">
<thead>
<tr>
@@ -463,15 +479,15 @@ POSSIBILITY OF SUCH DAMAGE.
</tr>
</tfoot>
</table>
</div>
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="signallcertsAct" type="button"><b>{{ lang._('Issue/Renew Certificates Now') }}</b><i id="signallcertsAct_progress" class=""></i></button>
<br/>
</div>
<div class="col-md-12">
{{ lang._('Use the Issue/Renew button to let the acme client automatically issue any new certificate and renew existing certificates (only if required). If you want to only issue/renew or revoke a single certificate, use the buttons in the Commands column. This will forcefully issue/renew the certificate, even if it is not required.') }} <b>{{ lang._('The process may take some time and thus will run in the background, you will not get any notification in the GUI. Use the log file to monitor the progress and to see error messages.') }}</b>
<br/><br/>
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="signallcertsAct" type="button"><b>{{ lang._('Issue/Renew Certificates Now') }}</b><i id="signallcertsAct_progress" class=""></i></button>
<br/>
</div>
<div class="col-md-12">
{{ lang._('Use the Issue/Renew button to let the acme client automatically issue any new certificate and renew existing certificates (only if required). If you want to only issue/renew or revoke a single certificate, use the buttons in the Commands column. This will forcefully issue/renew the certificate, even if it is not required.') }} <b>{{ lang._('The process may take some time and thus will run in the background, you will not get any notification in the GUI. Use the log file to monitor the progress and to see error messages.') }}</b>
<br/><br/>
</div>
</div>
</div>
@@ -226,17 +226,51 @@ POSSIBILITY OF SUCH DAMAGE.
});
});
// update history on tab state and implement navigation
if(window.location.hash != "") {
$('a[href="' + window.location.hash + '"]').click()
}
$('.nav-tabs a').on('shown.bs.tab', function (e) {
history.pushState(null, null, e.target.hash);
});
});
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#settings">{{ lang._('Settings') }}</a></li>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li {% if showIntro|default('0')=='1' %}class="active"{% endif %}><a data-toggle="tab" id="settings-introduction" href="#subtab_settings-introduction"><b>{{ lang._('Introduction') }}</b></a></li>
<li {% if showIntro|default('0')=='0' %}class="active"{% endif %}><a data-toggle="tab" id="settings-tab" href="#settings"><b>{{ lang._('Settings') }}</b></a></li>
<li><a href="" id="scheduled_updates" style="display:none">{{ lang._('Update Schedule') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="settings" class="tab-pane fade in active">
<div class="content-box tab-content">
<div id="subtab_settings-introduction" class="tab-pane fade {% if showIntro|default('0')=='1' %}in active{% endif %}">
<div class="col-md-12">
<h1>{{ lang._('Quick Start Guide') }}</h1>
<p>{{ lang._("Welcome to the ACME Client plugin! This plugin allows you to create SSL certificates by using one of the following Certificate Authorities (CAs):") }}</p>
<ul>
<li>{{ lang._("%sLet's Encrypt:%s A free, automated, and open certificate authority, run for the public's benefit. It is a service provided by the Internet Security Research Group (ISRG). Read more about the ACME protocol in %stheir documentation%s.") | format('<b>', '</b>', '<a href="https://letsencrypt.org/how-it-works/" target="_blank">', '</a>') }}</li>
<li>{{ lang._('%sBuypass:%s A commercial, european certificate authority, based in Norway. Check out %stheir documentation%s for details about rate-limits and the usage policy.') | format('<b>', '</b>', '<a href="https://www.buypass.com/ssl/resources/go-ssl-technical-specification" target="_blank">', '</a>') }}</li>
<li>{{ lang._('%sSSL.com:%s A commercial, globally trusted certificate authority. They provide an %sextensive guide%s for using their paid services with the ACME protocol.') | format('<b>', '</b>', '<a href="https://www.ssl.com/guide/ssl-tls-certificate-issuance-and-revocation-with-acme/" target="_blank">', '</a>') }}</li>
<li>{{ lang._("%sZeroSSL:%s A commercial, european certificate authority, based in Austria. They provide a feature overview on %stheir website%s for users of Let's Encrypt.") | format('<b>', '</b>', '<a href="https://zerossl.com/letsencrypt-alternative/" target="_blank">', '</a>') }}</li>
</ul>
<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._('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>
</ul>
<p><b>{{ lang._("Please read the official documentation for the preferred CA before using this plugin. It should give you a good overview about how their implementation of the ACME protocol works, so you do not hit their %srate limits%s and avoid common misconfigurations. Otherwise all attempts to issue a certificate would most likely fail. ") | format('<a href="https://letsencrypt.org/docs/rate-limits/">', '</a>') }}</b>{{ lang._("Ensure to use a %stest CA%s when using this plugin for the first time or while testing a new challenge type. Note that you will have to reissue your certificates when switching from a test CA to a production CA to get valid certificates.") | format('<a href="https://letsencrypt.org/docs/staging-environment/">', '</a>') }}</p>
<p>{{ lang._('Please use the %sissue tracker%s to report bugs or request new features. Note that some CAs offer paid services. These services are not affiliated to this plugin. The maintainers and developers of this plugin will not provide support for paid services.') | format('<a href="https://github.com/opnsense/plugins/issues">', '</a>') }}</p>
</div>
</div>
<div id="settings" class="tab-pane fade {% if showIntro|default('0')=='0' %}in active{% endif %}">
{{ partial("layout_partials/base_form",['fields':settingsForm,'id':'frm_settings'])}}
</div>
<div class="col-md-12">
@@ -247,12 +281,9 @@ POSSIBILITY OF SUCH DAMAGE.
<br/>
</div>
<div class="col-md-12">
<b>{{ lang._("Please read the official %sLet's Encrypt documentation%s before using this plugin. It should give you a good overview about how the various ACME CAs work, so you do not hit their %srate limits%s and avoid common misconfigurations, which would let all your attempts to issue a certificate fail.") | format('<a href="https://letsencrypt.org/how-it-works/">', '</a>', '<a href="https://letsencrypt.org/docs/rate-limits/">', '</a>') }}</b>{{ lang._("Please use a %stest CA%s when using this plugin for the first time or while testing a new challenge type. Note that you will have to reissue your certificates when switching from a test to a production CA to get valid certificates.") | format('<a href="https://letsencrypt.org/docs/staging-environment/">', '</a>') }}
<br/>
{{ lang._('Please use the %sissue tracker%s to report bugs or request new features.') | format('<a href="https://github.com/opnsense/plugins/issues">', '</a>') }}
<br/>
<br/>
<p>{{ lang._('This plugin includes code from the %s project.') | format('<a href="https://github.com/acmesh-official/acme.sh">acmesh-official/acme.sh</a>' ) }} {{ lang._('Licensed under GPLv3.') }}<br/>{{ lang._('Let"s Encrypt(tm) is a trademark of the Internet Security Research Group. All rights reserved.') }}</p>
<p>{{ lang._('This plugin includes code from the %s project.') | format('<a href="https://github.com/acmesh-official/acme.sh">acmesh-official/acme.sh</a>' ) }} {{ lang._('Licensed under %sGPLv3%s.') | format('<a href="https://github.com/acmesh-official/acme.sh/blob/master/LICENSE.md">', '</a>' ) }}<br/>{{ lang._("Let's Encrypt(tm) is a trademark of the Internet Security Research Group. All rights reserved.") }}</p>
<br/>
</div>
</div>
@@ -1,6 +1,6 @@
{#
Copyright (C) 2017 Frank Wall
Copyright (C) 2017-2021 Frank Wall
OPNsense® is Copyright © 2014-2015 by Deciso B.V.
All rights reserved.
@@ -90,12 +90,26 @@ POSSIBILITY OF SUCH DAMAGE.
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#validations">{{ lang._('Challenge Types') }}</a></li>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li {% if showIntro|default('0')=='1' %}class="active"{% endif %}><a data-toggle="tab" id="validations-introduction" href="#subtab_validations-introduction"><b>{{ lang._('Introduction') }}</b></a></li>
<li {% if showIntro|default('0')=='0' %}class="active"{% endif %}><a data-toggle="tab" id="validations-tab" href="#validations"><b>{{ lang._('Challenge Types') }}</b></a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="validations" class="tab-pane fade in active">
<div class="content-box tab-content">
<div id="subtab_validations-introduction" class="tab-pane fade {% if showIntro|default('0')=='1' %}in active{% endif %}">
<div class="col-md-12">
<h1>{{ lang._('Challenge Types') }}</h1>
<p>{{ lang._('As defined by the ACME standard, Certificate Authorities (CAs) must validate that you control a domain name. This is done by using "challenges". The following challenge types are supported:') }}</p>
<ul>
<li>{{ lang._('%sDNS-01:%s This is the most reliable challenge type and thus highly recommended when using this plugin. It requires that you control the DNS for your domain name and that your DNS provider is supported both %sby acme.sh%s and this plugin.') | format('<b>', '</b>', '<a href="https://github.com/acmesh-official/acme.sh/wiki/dnsapi" target="_blank">', '</a>') }}</li>
<li>{{ lang._("%sHTTP-01:%s This challenge type usually requires manual configuration and is not recommended. The DNS name used in the certificate must point to the OPNsense host where the ACME Client plugin is running on. The integrated web service will try to guess the correct settings for your setup, but this may not always work out-of-the-box. Furthermore this challenge type cannot be used to create %swildcard certificates with Let's Encrypt%s.") | format('<b>', '</b>', '<a href="https://letsencrypt.org/docs/challenge-types/#http-01-challenge" target="_blank">', '</a>') }}</li>
</ul>
<p>{{ lang._('When experiencing issues with a challenge type, try setting the log level to "debug". Please provide full logs when %sreporting issues%s for a challenge type. You should also consider to ask the Certificate Authority for support, if you choose to use a commercial CA.') | format('<a href="https://github.com/opnsense/plugins/issues">', '</a>') }}</p>
</div>
</div>
<div id="validations" class="tab-pane fade {% if showIntro|default('0')=='0' %}in active{% endif %}">
<table id="grid-validations" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogValidation">
<thead>
<tr>
@@ -119,6 +133,7 @@ POSSIBILITY OF SUCH DAMAGE.
</tfoot>
</table>
</div>
</div>
{# include dialogs #}