diff --git a/security/acme-client/pkg-descr b/security/acme-client/pkg-descr index 670324157..167a9b7f4 100644 --- a/security/acme-client/pkg-descr +++ b/security/acme-client/pkg-descr @@ -12,10 +12,13 @@ Plugin Changelog Added: * new automation: update local Unifi keystore (#2664) +* add support for dynv6 HTTP API (#2678) +* add support for TLS-ALPN-01 challenge type (#2661) Fixed: * fix SFTP upload (#2671) * fix PHP error when acme.sh deploy hook returns an error (#2674) +* fix path for storing pf config files when using HTTP-01 3.5 diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml index 36a285356..3e2efdbd1 100644 --- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml +++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogValidation.xml @@ -78,6 +78,42 @@ true Choose the local HAProxy frontends. They will automatically be configured to redirect acme challenges to the internal acme client. The HAProxy service will automatically be restarted if a certificate was renewed. + + + header + + + + validation.tlsalpn_service + + dropdown + + + + header + + + + validation.tlsalpn_acme_autodiscovery + + checkbox +
NOTE:This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.
]]>
+
+ + validation.tlsalpn_acme_interface + + dropdown +
NOTE:This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.
]]>
+
+ + validation.tlsalpn_acme_ipaddresses + + select_multiple + + true +
NOTE:This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.
]]>
+ Enter IP addresses here. Finish each with TAB. +
header @@ -1353,4 +1389,14 @@ password + + + header + + + + validation.dns_dynv6_token + + password + diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml index 4089801a0..a6b30ca04 100644 --- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml +++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/settings.xml @@ -36,6 +36,13 @@ true + + acmeclient.settings.TLSchallengePort + + text + + true + acmeclient.settings.restartTimeout diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/Base.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/Base.php index 433572cba..3c0a6881a 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/Base.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/Base.php @@ -85,6 +85,9 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon case 'http01': $this->acme_args[] = '--webroot ' . self::ACME_WEBROOT; break; + case 'tlsalpn01': + $this->acme_args[] = '--alpn'; + break; } // Store acme filenames diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsDynv6.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsDynv6.php new file mode 100644 index 000000000..c1d9115f0 --- /dev/null +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsDynv6.php @@ -0,0 +1,44 @@ +acme_env['DYNV6_TOKEN'] = (string)$this->config->dns_dynv6_token; + } +} diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/HttpOpnsense.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/HttpOpnsense.php index 32f6d3138..3b2d7a3c9 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/HttpOpnsense.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/HttpOpnsense.php @@ -1,7 +1,7 @@ cert_id); + // Get configured HTTP port for local lighttpd server. $configObj = Config::getInstance()->object(); $local_http_port = $configObj->OPNsense->AcmeClient->settings->challengePort; diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/TlsalpnAcme.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/TlsalpnAcme.php new file mode 100644 index 000000000..db8466892 --- /dev/null +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/TlsalpnAcme.php @@ -0,0 +1,143 @@ +cert_id); + + // Get configured TLS port for acme.sh web server. + $configObj = Config::getInstance()->object(); + $local_tls_port = $configObj->OPNsense->AcmeClient->settings->TLSchallengePort; + $this->acme_args[] = LeUtils::execSafe('--tlsport %s', (string)$local_tls_port); + + // Collect all IP addresses here, automatic port forward will be applied for each IP + $iplist = array(); + + // Add IP addresses from auto-discovery feature + if ($this->config->tlsalpn_acme_autodiscovery == 1) { + $dnslist = explode(',', $this->cert_altnames); + $dnslist[] = $this->cert_name; + foreach ($dnslist as $fqdn) { + // NOTE: This may take some time. + $ip_found = gethostbyname("${fqdn}."); + if (!empty($ip_found)) { + $iplist[] = (string)$ip_found; + } + } + } + + // Add IP addresses from user input + $additional_ip = (string)$this->config->tlsalpn_acme_ipaddresses; + if (!empty($additional_ip)) { + foreach (explode(',', $additional_ip) as $ip) { + $iplist[] = $ip; + } + } + + // Add IP address from chosen interface + if (!empty((string)$this->config->tlsalpn_acme_interface)) { + $interface_ip = get_interface_ip((string)$this->config->tlsalpn_acme_interface); + if (!empty($interface_ip)) { + $iplist[] = $interface_ip; + } + } + + // Check if IPv6 support is enabled + if (isset($configObj->system->ipv6allow) && ($configObj->system->ipv6allow == '1')) { + $_ipv6_enabled = true; + } else { + $_ipv6_enabled = false; + } + + // Generate rules for all IP addresses + $anchor_rules = ""; + if (!empty($iplist)) { + $dedup_iplist = array_unique($iplist); + // Add one rule for every IP + foreach ($dedup_iplist as $ip) { + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + // IPv4 + $_dst = '127.0.0.1'; + $_family = 'inet'; + LeUtils::log("using IPv4 address: ${ip}"); + } elseif (($_ipv6_enabled == true) && (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) { + // IPv6 + $_dst = '::1'; + $_family = 'inet6'; + LeUtils::log("using IPv6 address: ${ip}"); + } else { + continue; // skip broken entries + } + $anchor_rules .= "rdr pass ${_family} proto tcp from any to ${ip} port 443 -> ${_dst} port ${local_tls_port}\n"; + } + } else { + LeUtils::log_error("no IP addresses found to setup port forward"); + return false; + } + + // Abort if no rules were generated + if (empty($anchor_rules)) { + LeUtils::log_error("unable to setup a port forward (empty ruleset)"); + return false; + } + + // Create temporary port forward to allow acme challenges to get through + $anchor_setup = "rdr-anchor \"acme-client\"\n"; + file_put_contents("${configdir}/acme_anchor_setup", $anchor_setup); + chmod("${configdir}/acme_anchor_setup", 0600); + mwexec("/sbin/pfctl -f ${configdir}/acme_anchor_setup"); + file_put_contents("${configdir}/acme_anchor_rules", $anchor_rules); + chmod("${configdir}/acme_anchor_rules", 0600); + mwexec("/sbin/pfctl -a acme-client -f ${configdir}/acme_anchor_rules"); + } + + public function cleanup() + { + // Flush OPNsense port forward rules. + mwexec('/sbin/pfctl -a acme-client -F all'); + + // Workaround to solve disconnection issues reported by some users. + $backend = new \OPNsense\Core\Backend(); + $response = $backend->configdRun('filter reload'); + return true; + } +} diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidationFactory.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidationFactory.php index 1a2611d21..06e8be49b 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidationFactory.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidationFactory.php @@ -1,7 +1,7 @@ http_service; break; + case 'tlsalpn01': + $search_name = "tlsalpn_" . $obj->tlsalpn_service; + break; } // Convert to PascalCase diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml index 0fa09b84c..37e3df295 100644 --- a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml +++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml @@ -1,6 +1,6 @@ //OPNsense/AcmeClient - 3.1.0 + 3.2.0 A secure ACME Client plugin @@ -34,12 +34,18 @@ Staging Environment - + 43580 1024 65535 Y + + 43581 + 1024 + 65535 + Y + 600 10 @@ -339,6 +345,7 @@ HTTP-01 DNS-01 + TLS-ALPN-01 @@ -384,6 +391,28 @@ Y N + + Y + acme + + acme.sh TLS Web Server (automatic port forward) + + + + 1 + N + + + N + wan + + /^(?!0).*$/ + + + + N + Y + Y dns_freedns @@ -417,6 +446,7 @@ DuckDNS API Dyn Managed DNS API Dynu API + dynv6 HTTP API EUserv FreeDNS API Gandi LiveDNS API @@ -1018,6 +1048,9 @@ N + + N + diff --git a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt index bd1e4c42f..c095c4298 100644 --- a/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt +++ b/security/acme-client/src/opnsense/mvc/app/views/OPNsense/AcmeClient/validations.volt @@ -77,11 +77,20 @@ POSSIBILITY OF SUCH DAMAGE. } else { } }); + $("#validation\\.tlsalpn_service").change(function(){ + var service_id = 'table_tlsalpn_' + $(this).val(); + $(".table_tlsalpn").hide(); + if ($("#validation\\.method").val() == 'tlsalpn01') { + $("."+service_id).show(); + } else { + } + }); $("#validation\\.method").change(function(){ $(".method_table").hide(); $(".method_table_"+$(this).val()).show(); $("#validation\\.dns_service").change(); $("#validation\\.http_service").change(); + $("#validation\\.tlsalpn_service").change(); }); $("#validation\\.method").change(); @@ -103,7 +112,8 @@ POSSIBILITY OF SUCH DAMAGE.

{{ 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:') }}

  • {{ 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('', '', '', '') }}
  • -
  • {{ 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('', '', '', '') }}
  • +
  • {{ 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 validate %swildcard certificates with Let's Encrypt%s.") | format('', '', '', '') }}
  • +
  • {{ lang._("%sTLS-ALPN-01:%s This works similar to the HTTP-01 challenge type and has the same requirements. It works if port 80 is unavailable. Other challenge types should be preferred. This challenge type cannot be used to validate %swildcard certificates with Let's Encrypt%s.") | format('', '', '', '') }}

{{ 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('', '') }}