diff --git a/www/caddy/Makefile b/www/caddy/Makefile index 138ee9fee..208aef7c0 100644 --- a/www/caddy/Makefile +++ b/www/caddy/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= caddy -PLUGIN_VERSION= 1.5.3 +PLUGIN_VERSION= 1.5.4 +PLUGIN_REVISION= 1 PLUGIN_DEPENDS= caddy-custom PLUGIN_COMMENT= Easy to configure Reverse Proxy with Automatic HTTPS and Dynamic DNS PLUGIN_MAINTAINER= cedrik@pischem.com diff --git a/www/caddy/pkg-descr b/www/caddy/pkg-descr index 72f60a2da..93d02a539 100644 --- a/www/caddy/pkg-descr +++ b/www/caddy/pkg-descr @@ -18,12 +18,23 @@ Main features of this plugin: * Basic Auth to restrict access by username and password * Syslog-ng integration and HTTP Access Log * NTLM Transport +* Header manipulation with header_up and header_down DOC: https://docs.opnsense.org/manual/how-tos/caddy.html Plugin Changelog ================ +1.5.4 + +* Fix: When pressing Apply, the Caddy service will be reloaded instead of restarted. This fixes long restart times and service interruptions. +* Change: All Description Fields are now required to be populated. +* Change: Model Relation Fields now display two values instead of one to make most options appear unique. +* Add: HTTP response code and HTTP response message can be set per access list in advanced mode. +* Add: Header functionality added. Multiple header manipulations can be set per handler. +* Cleanup: Update searchBase() in ReverseProxyController.php for easier maintainability. +* Fix: Move selectpicker empty option to model in general.volt, using BlankDesc. This fixes the option IPv4+IPv6 not appearing in Dynamic DNS. + 1.5.3 * Change from "Phalcon Messages" to "OPNsense Messages" in Caddy.php. diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ReverseProxyController.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ReverseProxyController.php index 5236dfba9..140330a06 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ReverseProxyController.php +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ReverseProxyController.php @@ -41,9 +41,9 @@ class ReverseProxyController extends ApiMutableModelControllerBase /*ReverseProxy Section*/ - public function searchReverseProxyAction() + public function searchReverseProxyAction($add_empty='0') { - return $this->searchBase("reverseproxy.reverse", ['enabled', 'FromDomain', 'FromPort', 'accesslist', 'basicauth', 'DnsChallenge', 'CustomCertificate', 'AccessLog', 'DynDns', 'AcmePassthrough', 'description']); + return $this->searchBase("reverseproxy.reverse", null, 'description'); } public function setReverseProxyAction($uuid) @@ -74,9 +74,9 @@ class ReverseProxyController extends ApiMutableModelControllerBase /*Subdomain Section*/ - public function searchSubdomainAction() + public function searchSubdomainAction($add_empty='0') { - return $this->searchBase("reverseproxy.subdomain", ['enabled', 'reverse', 'FromDomain', 'FromPort', 'accesslist', 'basicauth', 'DynDns', 'description']); + return $this->searchBase("reverseproxy.subdomain", null, 'description'); } public function setSubdomainAction($uuid) @@ -107,9 +107,9 @@ class ReverseProxyController extends ApiMutableModelControllerBase /*Handler Section*/ - public function searchHandleAction() + public function searchHandleAction($add_empty='0') { - return $this->searchBase("reverseproxy.handle", ['enabled', 'reverse', 'subdomain', 'HandleType', 'HandlePath', 'ToDomain', 'ToPort', 'ToPath', 'HttpTls', 'HttpTlsTrustedCaCerts', 'HttpTlsServerName', 'HttpNtlm', 'HttpTlsInsecureSkipVerify', 'description']); + return $this->searchBase("reverseproxy.handle", null, 'description'); } public function setHandleAction($uuid) @@ -140,9 +140,9 @@ class ReverseProxyController extends ApiMutableModelControllerBase /* AccessList Section */ - public function searchAccessListAction() + public function searchAccessListAction($add_empty='0') { - return $this->searchBase("reverseproxy.accesslist", ['accesslistName', 'clientIps', 'accesslistInvert', 'description']); + return $this->searchBase("reverseproxy.accesslist", null, 'description'); } public function setAccessListAction($uuid) @@ -168,9 +168,9 @@ class ReverseProxyController extends ApiMutableModelControllerBase /* BasicAuth Section */ - public function searchBasicAuthAction() + public function searchBasicAuthAction($add_empty='0') { - return $this->searchBase("reverseproxy.basicauth", ['basicauthuser', 'basicauthpass', 'description']); + return $this->searchBase("reverseproxy.basicauth", null, 'description'); } public function setBasicAuthAction($uuid) @@ -212,4 +212,32 @@ class ReverseProxyController extends ApiMutableModelControllerBase { return $this->delBase("reverseproxy.basicauth", $uuid); } + + + /* Header Section */ + + public function searchHeaderAction($add_empty='0') + { + return $this->searchBase("reverseproxy.header", null, 'description'); + } + + public function setHeaderAction($uuid) + { + return $this->setBase("header", "reverseproxy.header", $uuid); + } + + public function addHeaderAction() + { + return $this->addBase("header", "reverseproxy.header"); + } + + public function getHeaderAction($uuid = null) + { + return $this->getBase("header", "reverseproxy.header", $uuid); + } + + public function delHeaderAction($uuid) + { + return $this->delBase("reverseproxy.header", $uuid); + } } diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ServiceController.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ServiceController.php index fa1b43842..f719a74d9 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ServiceController.php +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Api/ServiceController.php @@ -41,6 +41,12 @@ class ServiceController extends ApiMutableServiceControllerBase protected static $internalServiceEnabled = 'general.enabled'; protected static $internalServiceName = 'caddy'; + protected function reconfigureForceRestart() + { + // Caddy can use a reload action instead + return 0; + } + public function validateAction() { $backend = new Backend(); diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php index 4a9947d75..fde12d63c 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php @@ -43,5 +43,6 @@ class ReverseProxyController extends IndexController $this->view->formDialogHandle = $this->getForm("dialogHandle"); $this->view->formDialogAccessList = $this->getForm("dialogAccessList"); $this->view->formDialogBasicAuth = $this->getForm("dialogBasicAuth"); + $this->view->formDialogHeader = $this->getForm("dialogHeader"); } } diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogAccessList.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogAccessList.xml index b38a67826..8fff82b7e 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogAccessList.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogAccessList.xml @@ -19,6 +19,22 @@ checkbox + + accesslist.HttpResponseCode + + text + 403 + + true + + + accesslist.HttpResponseMessage + + text + Forbidden + + true + accesslist.description diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml index 68ac76a59..51bf04077 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml @@ -36,6 +36,19 @@ text + + header + + true + + + handle.header + + dropdown + select_multiple + 5 + + header diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHeader.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHeader.xml new file mode 100644 index 000000000..585ddf512 --- /dev/null +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHeader.xml @@ -0,0 +1,34 @@ +
+ + header.HeaderUpDown + + dropdown + + + + header.HeaderType + + Host + text + + + + header.HeaderValue + + {upstream_hostport} + text + + + + header.HeaderReplace + + text + + + + header.description + + text + + +
diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dynamicdns.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dynamicdns.xml index d7fa66ef1..a0a363095 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dynamicdns.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dynamicdns.xml @@ -3,7 +3,7 @@ caddy.general.DynDnsIpVersions dropdown - +
caddy.general.DynDnsCheckInterval diff --git a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml index 3d8497fd3..d967254ef 100644 --- a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml +++ b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml @@ -1,7 +1,7 @@ //Pischem/caddy A GUI model for configuring a reverse proxy in the Caddy web server. - 1.1.5 + 1.1.7 @@ -12,8 +12,8 @@ Please enter a valid email address. + On (default) - On (default) Off Disable Redirects Disable Certs @@ -21,8 +21,8 @@ + None (default) - None (default) Cloudflare Duck DNS DigitalOcean @@ -59,7 +59,8 @@ OPNsense.Caddy.Caddy reverseproxy.accesslist - accesslistName + accesslistName,description + %s - %s @@ -84,12 +85,11 @@ Y - ipv4 + IPv4+IPv6 IPv4 only IPv6 only - Y 1 @@ -123,7 +123,8 @@ OPNsense.Caddy.Caddy reverseproxy.accesslist - accesslistName + accesslistName,description + %s - %s @@ -132,7 +133,8 @@ OPNsense.Caddy.Caddy reverseproxy.basicauth - basicauthuser + basicauthuser,description + %s - %s Y @@ -160,7 +162,8 @@ OPNsense.Caddy.Caddy reverseproxy.reverse - description + FromDomain,FromPort + %s:%s @@ -179,7 +182,8 @@ OPNsense.Caddy.Caddy reverseproxy.accesslist - accesslistName + accesslistName,description + %s - %s @@ -188,7 +192,8 @@ OPNsense.Caddy.Caddy reverseproxy.basicauth - basicauthuser + basicauthuser,description + %s - %s Y @@ -209,7 +214,8 @@ OPNsense.Caddy.Caddy reverseproxy.reverse - description + FromDomain,FromPort + %s:%s @@ -218,7 +224,8 @@ OPNsense.Caddy.Caddy reverseproxy.subdomain - description + FromDomain,FromPort + %s:%s @@ -234,6 +241,17 @@ /^(\/.*)?$/u Please enter a valid 'Handle Path' that starts with '/'. +
+ + + OPNsense.Caddy.Caddy + reverseproxy.header + HeaderUpDown,HeaderType,HeaderValue,description + %s %s %s - %s + + + Y +
Y Please enter a valid 'to' domain or IP address. @@ -261,7 +279,9 @@ Y N - + + Y + @@ -278,7 +298,15 @@ Please enter valid IP address(es) or network(s), separated by commas. - + + 100 + 599 + Please enter a valid HTTP response code between 100 and 599 + + + + Y + @@ -289,8 +317,36 @@ Y - + + Y + +
+ + header_up + + header_up + header_down + + Y + + + Y + /^([^"]{0,1024})$/u + The header type must not contain quotation marks (") and must be less than 1024 characters. + + + /^([^"]{0,1024})$/u + The header value must not contain quotation marks (") and must be less than 1024 characters. + + + /^([^"]{0,1024})$/u + The header replacement must not contain quotation marks (") and must be less than 1024 characters. + + + Y + +
diff --git a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/general.volt b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/general.volt index 9fcca897b..eac7320b6 100644 --- a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/general.volt +++ b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/general.volt @@ -28,33 +28,6 @@ $(document).ready(function() { var data_get_map = {'frm_GeneralSettings':"/api/caddy/General/get"}; mapDataToFormUI(data_get_map).done(function(data){ - // console.log("Fetched data:", data); // Log the fetched data - var generalSettings = data.frm_GeneralSettings.caddy.general; - - // Populate TlsAutoHttps dropdown - var tlsAutoHttpsSelect = $('#caddy\\.general\\.TlsAutoHttps'); - tlsAutoHttpsSelect.empty(); // Clear existing options - $.each(generalSettings.TlsAutoHttps, function(key, option) { - if (key !== "") { // Filter out the unwanted "None" option - tlsAutoHttpsSelect.append(new Option(option.value, key, false, option.selected === 1)); - } - }); - - // Populate TlsDnsProvider dropdown - var tlsDnsProviderSelect = $('#caddy\\.general\\.TlsDnsProvider'); - tlsDnsProviderSelect.empty(); // Clear existing options - $.each(generalSettings.TlsDnsProvider, function(key, option) { - if (key !== "") { // Filter out the unwanted "None" option - tlsDnsProviderSelect.append(new Option(option.value, key, false, option.selected === 1)); - } - }); - - // Populate Trusted Proxies dropdown - var accesslistSelect = $('#caddy\\.general\\.accesslist'); - accesslistSelect.empty(); // Clear existing options - $.each(generalSettings.accesslist, function(key, option) { - accesslistSelect.append(new Option(option.value, key, false, option.selected === 1)); - }); // Refresh selectpicker for these dropdowns $('.selectpicker').selectpicker('refresh'); diff --git a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt index 92aaac25d..7f397b615 100644 --- a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt +++ b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt @@ -69,6 +69,14 @@ del:'/api/caddy/ReverseProxy/delBasicAuth/', }); + $("#reverseHeaderGrid").UIBootgrid({ + search:'/api/caddy/ReverseProxy/searchHeader/', + get:'/api/caddy/ReverseProxy/getHeader/', + set:'/api/caddy/ReverseProxy/setHeader/', + add:'/api/caddy/ReverseProxy/addHeader/', + del:'/api/caddy/ReverseProxy/delHeader/', + }); + // Function to show alerts in the HTML message area function showAlert(message, type = "error") { var alertClass = type === "error" ? "alert-danger" : "alert-success"; @@ -143,6 +151,7 @@
  • Domains
  • Handlers
  • Access
  • +
  • Headers
  • @@ -234,6 +243,7 @@ Subdomain Handle Type Handle Path + Header Upstream Domain Upstream Port Upstream Path @@ -275,6 +285,8 @@ Name Client IPs Invert + HTTP Code + HTTP Message Description Commands @@ -322,6 +334,39 @@
    + + +
    +
    +

    Headers

    +
    + + + + + + + + + + + + + + + + + + + + +
    IDHeaderHeader TypeHeader ValueHeader ReplaceDescriptionCommands
    + + +
    +
    +
    +
    @@ -351,3 +396,4 @@ {{ partial("layout_partials/base_dialog",['fields':formDialogHandle,'id':'DialogHandle','label':lang._('Edit Handler')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogAccessList,'id':'DialogAccessList','label':lang._('Edit Access List')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogBasicAuth,'id':'DialogBasicAuth','label':lang._('Edit Basic Auth')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogHeader,'id':'DialogHeader','label':lang._('Edit Header')])}} diff --git a/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_control.py b/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_control.py index da949afef..6598926dc 100755 --- a/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_control.py +++ b/www/caddy/src/opnsense/scripts/OPNsense/Caddy/caddy_control.py @@ -64,9 +64,10 @@ def run_service_command(action, action_message): # Updated actions dictionary actions = { - "start": "onestart", - "stop": "onestop", - "restart": "onerestart", + "start": "start", + "stop": "stop", + "restart": "restart", + "reload": "reload", "validate": "validate" # Validate 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 28b10be8d..cd5f1b2f6 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 @@ -14,9 +14,16 @@ message:Stopping Caddy service command:/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_control.py restart parameters: type:script -message:Reloading Caddy configuration +message:Restarting Caddy service description:Restart Caddy service +[reload] +command:/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_control.py reload +parameters: +type:script +message:Reloading Caddy configuration +description:Reload Caddy service + [validate] command:/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_control.py validate parameters: diff --git a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile index e0e84bc72..6a4272fb2 100644 --- a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile +++ b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile @@ -1,9 +1,40 @@ +{# +# Copyright (c) 2023-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. +#} + # DO NOT EDIT THIS FILE -- OPNsense auto-generated file {% set generalSettings = helpers.getNodeByTag('Pischem.caddy.general') %} # Global Options { + {# + # Section: Global Log Settings + # Purpose: Sets up global log settings. The time format and unix socket make Caddy compatible + # with the syslog-ng instance running on the OPNsense. + #} log { {% if generalSettings.LogAccessPlain|default("0") == "0" %} {% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %} @@ -19,6 +50,11 @@ } } + {# + # Section: Global Trusted Proxy and Credential Logging + # Purpose: The trusted proxy section is important when using CDNs so that headers are trusted. + # Credential logging is useful for troubleshooting basic auth. + #} {% set accessListUuid = generalSettings.accesslist %} {% set logCredentials = generalSettings.LogCredentials %} @@ -47,6 +83,26 @@ } {% endif %} + {# + # Section: Dynamic DNS Global Configuration + # Purpose: Sets up global configuration for Dynamic DNS. Caddy needs to be compiled with + # https://github.com/mholt/caddy-dynamicdns and https://github.com/caddy-dns. Otherwise the + # generated Caddyfile won't run. Each DNS Provider that is added below has to be compiled in. + # Some Providers don't support setting A and AAAA-Records, like acmedns. + # Most need specific configurations. Since only one provider can be used at the same time, + # they all share the same fields for configuration. + # Parameters: + # - @param dnsProvider (string): Specifies the DNS provider for DDNS updates. + # - @param dnsApiKey (string): The API key for authenticating with the DNS provider. + # - @param dnsSecretApiKey (string): A secret API key or token for additional authentication security. + # - @param dnsOptionalField1 to 4 (string): Optional configuration field for the DNS provider. + # - @param dynDnsSimpleHttp (string): URL for a simple HTTP-based service to discover the server's public IP. + # - @param dynDnsInterface (string): Network interface(s) to use for IP discovery. + # - @param dynDnsCheckInterval (integer): Interval in minutes to check for IP changes. + # - @param dynDnsIpVersions (string): The IP version(s) (IPv4, IPv6) for the DDNS update. + # - @param dynDnsTTL (integer): Time-To-Live for the DNS records, in hours. + # - @param dynDnsDomains (list): Domains and subdomains list for which DDNS updates are enabled. + #} {% set dnsProvider = helpers.toList('Pischem.caddy.general.TlsDnsProvider') | first %} {% set dnsApiKey = generalSettings.TlsDnsApiKey %} {% set dnsSecretApiKey = generalSettings.TlsDnsSecretApiKey %} @@ -78,7 +134,7 @@ {% endfor %} {% endfor %} - {% if dnsProvider and dnsProvider != "none" and dnsProvider != "acmedns" and dynDnsDomains|length > 0 %} + {% if dnsProvider and dnsProvider != "acmedns" and dynDnsDomains|length > 0 %} dynamic_dns { {% if dnsProvider in ['porkbun', 'desec', 'route53', 'googleclouddns', 'azure', 'ovh', 'namecheap', 'powerdns', 'ddnss', 'linode', 'tencentcloud', 'dinahosting', 'hexonet', 'mailinabox'] %} provider {{ dnsProvider }} { @@ -207,12 +263,18 @@ } {% endif %} + {# + # Section: ACME Email, Auto HTTPS selection and global import statement + # Purpose: The ACME email is optional for receiving certificate notices. + # Auto HTTPS is optional, the default is on (which means the section is empty). + # The import statement is for user specific configuration out of scope of this template. + #} {% set emailValue = helpers.toList('Pischem.caddy.general.TlsEmail') | first %} {% if emailValue %} email {{ emailValue }} {% endif %} {% set autoHttpsValue = helpers.toList('Pischem.caddy.general.TlsAutoHttps') | first %} - {% if autoHttpsValue != "on" %} + {% if autoHttpsValue %} auto_https {{ autoHttpsValue }} {% endif %} import /usr/local/etc/caddy/caddy.d/*.global @@ -220,6 +282,11 @@ # Reverse Proxy Configuration +{# +# Section: HTTP-01 Challenge Redirection +# Purpose: A small premade reverse_proxy section +# that can redirect the HTTP-01 challenge to a different webserver. +#} {% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %} {% if reverse.enabled|default("0") == "1" and reverse.AcmePassthrough %} # HTTP-01 challenge redirection for domain: "{{ reverse['@uuid'] }}" @@ -234,8 +301,21 @@ {% endif %} {% endfor %} +{# +# Macro: tls_configuration +# Purpose: Configures TLS settings based on the DNS provider, API keys, and optional fields. +# Sets up the Caddyfile to update TXT Records with the chosen DNS Provider and receive +# certificates with the DNS-01 challenge. Refer to Dynamic DNS section for more details. +# Parameters: +# - @param dnsProvider (string): The DNS provider used for the DNS challenge. +# - @param dnsApiKey (string): API key for the DNS provider, essential for authentication. +# - @param customCert (string, optional): The config extracted name of a certificate. +# - @param dnsChallenge (boolean): Indicates if a DNS challenge is used for certificate authentication. +# - @param dnsSecretApiKey (string, optional): A secret API key or token for additional security, depending on the provider. +# - @param TlsDnsOptionalField1 to 4 (string, optional): Additional fields for specific DNS provider configurations. +#} {% macro tls_configuration(dnsProvider, dnsApiKey, customCert, dnsChallenge, dnsSecretApiKey, TlsDnsOptionalField1, TlsDnsOptionalField2, TlsDnsOptionalField3, TlsDnsOptionalField4) %} - {% if dnsChallenge == "1" and dnsProvider and dnsProvider != "none" %} + {% if dnsChallenge == "1" and dnsProvider %} {% if dnsProvider in ['duckdns', 'porkbun', 'desec', 'route53', 'acmedns', 'googleclouddns', 'azure', 'ovh', 'namecheap', 'powerdns', 'ddnss', 'linode', 'tencentcloud', 'dinahosting', 'hexonet', 'mailinabox'] %} tls { dns {{ dnsProvider }} { @@ -360,12 +440,74 @@ {% endif %} {% endmacro %} +{# +# Macro: header_manipulation +# Purpose: Customizes HTTP headers for requests or responses; to add, remove, or modify headers. +# It uses a 'handle' object that specifies which headers to manipulate based on their @UUIDs. +# Each handle can have multiple of these HTTP headers assigned. +# Parameters: +# @param handle (@object): +# - @uuid (@string) +# - HeaderUpDown (string): Determines the direction of the header. +# - HeaderType (string): Specifies the name of the header. +# - HeaderValue (string, optional): The new value to set for the header, if any. +# - HeaderReplace (string, optional): Specifies a value to replace in the header. +#} +{% macro header_manipulation(handle) %} + {% if handle.header %} + {% for header_uuid in handle.header.split(',') %} + {% set header = helpers.toList('Pischem.caddy.reverseproxy.header') | selectattr('@uuid', 'equalto', header_uuid) | first %} + {# Generate directive only if HeaderUpDown and HeaderType are present #} + {% if header.HeaderUpDown and header.HeaderType %} + {# Prepare variables, making HeaderValue and HeaderReplace optional #} + {% set header_value = header.HeaderValue | default('') %} + {% set header_replace = header.HeaderReplace | default('') %} + {# Adjust output formatting based on the presence and style of HeaderValue #} + {% if header.HeaderReplace and header.HeaderValue %} + {% if header_value.startswith('{') %} + {{ header.HeaderUpDown }} {{ header.HeaderType }} {{ header_value }} "{{ header_replace }}" + {% else %} + {{ header.HeaderUpDown }} {{ header.HeaderType }} "{{ header_value }}" "{{ header_replace }}" + {% endif %} + {% elif header.HeaderValue %} + {% if header_value.startswith('{') %} + {{ header.HeaderUpDown }} {{ header.HeaderType }} {{ header_value }} + {% else %} + {{ header.HeaderUpDown }} {{ header.HeaderType }} "{{ header_value }}" + {% endif %} + {% else %} + {{ header.HeaderUpDown }} {{ header.HeaderType }} + {% endif %} + {% endif %} + {% endfor %} + {% endif %} +{% endmacro %} + +{# +# Macro: reverse_proxy_configuration +# Purpose: Sets up the handle with the reverse proxy configurations. The TLS Settings are generated here for the Upstream. +# Integrated Macros: header_manipulation +# Parameters: +# @param handle (@object): +# - @uuid (@string) +# - HandleType (string): Specifies the handling strategy. +# - HandlePath (string, optional): The path the handle should match on. +# - ToDomain (string): Target domain for the reverse proxy. +# - ToPort (string, optional): Target port on the ToDomain. +# - ToPath (string, optional): Destination path on the ToDomain. +# - HttpTls (boolean, optional): Enable TLS for the connection. +# - HttpNtlm (boolean, optional): Enable NTLM authentication for the connection. +# - HttpTlsInsecureSkipVerify (boolean, optional): If true, the server's SSL certificate is not verified. +# - HttpTlsTrustedCaCerts (string, optional): The config extracted name of a CA certificate. +# - HttpTlsServerName (string, optional): Specifies the server name for the TLS handshake. +#} {% macro reverse_proxy_configuration(handle) %} {{ handle.HandleType }} {{ handle.HandlePath|default("") }} { {% if handle.ToPath|default("") != "" %} rewrite * {{ handle.ToPath }}{uri} {% endif %} reverse_proxy {{ handle.ToDomain }}{% if handle.ToPort %}:{{ handle.ToPort }}{% endif %} { + {{ header_manipulation(handle) }} {% if handle.HttpTls|default("0") == "1" or handle.HttpTlsInsecureSkipVerify|default("0") == "1" %} {% if handle.HttpNtlm|default("0") == "1" %} transport http_ntlm { @@ -401,6 +543,18 @@ } {% endmacro %} +{# +# Macro: access_list_configuration +# Purpose: Defines access lists based on client IP addresses. The standard logic is "allow these IP addresses, deny all others." +# A handle with an @ matcher is created that will put the reverse_proxy_configuration inside. That means, the traffic will +# only get to the reverse proxy, when the access list matches. Invert is also possible, to explicitely deny IPs. +# The assembly is handled by the "Section: Reverse Proxy Configurations". +# Parameters: +# @param accesslist (@object): +# - @uuid (@string) +# - clientIps (@string): A comma-separated list of client IP addresses +# - invert (@boolean): A flag that inverts the logic of the access list +#} {% macro access_list_configuration(accesslist, invert) %} {% set client_ips = accesslist.clientIps.split(',') %} {% set client_ips_space_separated = client_ips | join(' ') %} @@ -409,6 +563,16 @@ } {% endmacro %} +{# +# Macro: basicauth_configuration +# Purpose: Implements basic authentication with a username and password for access. +# Parameters: +# @param basicauth_uuids (@string): A comma-separated list of UUIDs, each UUID corresponding to +# a specific user credentials (username and password). +# - @uuid (@string) +# - basicauthuser (@string): The username required for authentication. +# - basicauthpass (@string): The password associated with the username. +#} {% macro basicauth_configuration(basicauth_uuids) %} {% if basicauth_uuids %} basicauth { @@ -422,6 +586,20 @@ {% endif %} {% endmacro %} +{# +# Section: Reverse Proxy Configurations +# Purpose: Assembles reverse proxy configurations using predefined macros. +# This is the main logic of the whole template, handle with care. +# Macros Used: +# - tls_configuration +# - basicauth_configuration +# - access_list_configuration +# - reverse_proxy_configuration +# - indirect: header_manipulation +# Important Details: +# - Order of Path specific Handles - Prioritizes order of specific path handles over catch-all handles. +# - Order of Wildcard Domains and Subdomains: Handles for wildcard domains come after all subdomains. +#} {% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %} {% if reverse.enabled|default("0") == "1" %} # Reverse Proxy Domain: "{{ reverse['@uuid'] }}" @@ -491,8 +669,17 @@ {% endif %} {% endfor %} {% endif %} - {% if Pischem.caddy.general.abort|default("0") == "1" %} - abort + + {% if subdomain.accesslist %} + {% if accesslist.HttpResponseCode or accesslist.HttpResponseMessage %} + respond {{ '"' + accesslist.HttpResponseMessage|default('') + '"' if accesslist.HttpResponseMessage else '' }} {{ accesslist.HttpResponseCode|default(403) }} + {% elif Pischem.caddy.general.abort|default("0") == "1" %} + abort + {% endif %} + {% else %} + {% if Pischem.caddy.general.abort|default("0") == "1" %} + abort + {% endif %} {% endif %} } {% endif %} @@ -531,8 +718,18 @@ {% endif %} {% endfor %} {% endif %} - {% if Pischem.caddy.general.abort|default("0") == "1" %} - abort + + {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', reverse.accesslist) | first %} + {% if accesslist %} + {% if accesslist.HttpResponseCode or accesslist.HttpResponseMessage %} + respond {{ '"' + accesslist.HttpResponseMessage|default('') + '"' if accesslist.HttpResponseMessage else '' }} {{ accesslist.HttpResponseCode|default(403) }} + {% elif Pischem.caddy.general.abort|default("0") == "1" %} + abort + {% endif %} + {% else %} + {% if Pischem.caddy.general.abort|default("0") == "1" %} + abort + {% endif %} {% endif %} } {% endif %}