diff --git a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile index 673fd397c..0ca5f257f 100644 --- a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile +++ b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile @@ -275,34 +275,28 @@ {# # Section: HTTP-01 Challenge Redirection -# Purpose: A small premade reverse_proxy section -# that can redirect the HTTP-01 challenge to a different webserver. +# Purpose: Redirects HTTP-01 challenges to a different webserver for reverse proxies and subdomains. #} +{% macro http01_challenge_redirection(domain, acme_passthrough) %} +http://{{ domain }} { + handle /.well-known/acme-challenge/* { + reverse_proxy {{ acme_passthrough }} + } + handle { + redir https://{host}{uri} 308 + } +} +{% endmacro %} + {% 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'] }}" - http://{{ reverse.FromDomain|default("") }} { - handle /.well-known/acme-challenge/* { - reverse_proxy {{ reverse.AcmePassthrough }} - } - handle { - redir https://{host}{uri} 308 - } - } + {{ http01_challenge_redirection(reverse.FromDomain|default(""), reverse.AcmePassthrough) }} {% endif %} {% endfor %} -{# Process redirection for subdomains afterwards, since a redirection of a wildcard domain has to match before them. #} + {% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %} {% if subdomain.enabled|default("0") == "1" and subdomain.AcmePassthrough %} - # HTTP-01 challenge redirection for subdomain: "{{ subdomain['@uuid'] }}" - http://{{ subdomain.FromDomain|default("") }} { - handle /.well-known/acme-challenge/* { - reverse_proxy {{ subdomain.AcmePassthrough }} - } - handle { - redir https://{host}{uri} 308 - } - } + {{ http01_challenge_redirection(subdomain.FromDomain|default(""), subdomain.AcmePassthrough) }} {% endif %} {% endfor %} @@ -399,24 +393,9 @@ {# # 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 +# Purpose: Sets up the handle with the reverse proxy configurations. # 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. Not all HTTP options apply to NTLM. -# - 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. -# - PassiveHealthFailDuration (integer, optional): Enables passive health checks when set > 0. -# - HttpVersion (string, optional): Choose HTTP version. Empty (default) is 1.1 and 2. -# - HttpKeepalive (string, optional): Keeaplive is either off (0) or a value. Empty (default) 120s. +# @param handle (object) #} {% macro reverse_proxy_configuration(handle) %} {{ handle.HandleType }} {{ handle.HandlePath|default("") }} { @@ -436,27 +415,15 @@ {% if handle.PassiveHealthFailDuration|default("") %} fail_duration {{ handle.PassiveHealthFailDuration }}s {% endif %} - {% if handle.HttpTls|default("0") == "1" or handle.HttpTlsInsecureSkipVerify|default("0") == "1" or handle.HttpTlsTrustedCaCerts or handle.HttpTlsServerName or handle.HttpVersion or handle.HttpKeepalive %} + {% set has_transport_options = handle.HttpVersion or handle.HttpKeepalive or handle.HttpTls|default("0") == "1" or handle.HttpTlsesecureSkipVerify|default("0") == "1" or handle.HttpTlsTrustedCaCerts or handle.HttpTlsServerName %} + {% if has_transport_options %} {% if handle.HttpNtlm|default("0") == "1" %} transport http_ntlm { - {% if handle.HttpTls|default("0") == "1" %} - tls - {% endif %} - {% if handle.HttpTlsInsecureSkipVerify|default("0") == "1" %} - tls_insecure_skip_verify - {% endif %} - {% if handle.HttpTlsTrustedCaCerts %} - tls_trust_pool file /var/db/caddy/data/caddy/certificates/temp/{{ handle.HttpTlsTrustedCaCerts }}.pem - {% endif %} - {% if handle.HttpTlsServerName %} - tls_server_name {{ handle.HttpTlsServerName }} - {% endif %} - } {% else %} transport http { - {# The model does not allow to set a single number as option directly, so we have to map them. #} - {% set version_map = {'http1': 1.1, 'http2': 2, 'http3': 3} %} + {% endif %} {% if handle.HttpVersion %} + {% set version_map = {'http1': 1.1, 'http2': 2, 'http3': 3} %} versions {{ version_map[handle.HttpVersion] }} {% endif %} {% if handle.HttpKeepalive %} @@ -479,43 +446,42 @@ tls_server_name {{ handle.HttpTlsServerName }} {% endif %} } - {% endif %} {% endif %} } } {% 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". +# Macro: handle_accesslist +# Purpose: Manages the logic for access lists, including configuration and handling. # 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 +# @param accesslist (string): The UUID of the access list to be applied. +# @param content (string): The content to be wrapped in the access list handler. +# @param 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(' ') %} - @{{ accesslist['@uuid'] }} { - {{ 'not' if invert else '' }} client_ip {{ client_ips_space_separated }} - } +{% macro handle_accesslist(accesslist, content, invert=false) %} + {% if accesslist %} + {% set accesslist_obj = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', accesslist) | first %} + {% set client_ips = accesslist_obj.clientIps.split(',') %} + {% set client_ips_space_separated = client_ips | join(' ') %} + @{{ accesslist_obj['@uuid'] }} { + {{ 'not' if invert or accesslist_obj.accesslistenvert|default("0") == "1" else '' }} client_ip {{ client_ips_space_separated }} + } + handle @{{ accesslist_obj['@uuid'] }} { + {{ content }} + } + {% else %} + {{ content }} + {% endif %} {% endmacro %} {# -# Macro: basicauth_configuration -# Purpose: Implements basic authentication with a username and password for access. +# Macro: render_basic_auth +# Purpose: Renders the basic authentication configuration. # 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. +# @param basicauth_uuids (string): A comma-separated list of UUIDs for basic authentication. #} -{% macro basicauth_configuration(basicauth_uuids) %} +{% macro render_basic_auth(basicauth_uuids) %} {% if basicauth_uuids %} basic_auth { {% for uuid in basicauth_uuids.split(',') %} @@ -528,24 +494,64 @@ {% endif %} {% endmacro %} +{# +# Macro: handle_response +# Purpose: Manages the response logic for access lists and aborts. +# Parameters: +# @param accesslist (object): The access list object containing response settings. +# @param general_abort (string): The global abort setting. +#} +{% macro handle_response(accesslist, general_abort) %} + {% if accesslist %} + {% if accesslist.HttpResponseCode or accesslist.HttpResponseMessage %} + respond {{ '"' + accesslist.HttpResponseMessage|default('') + '"' if accesslist.HttpResponseMessage else '' }} {{ accesslist.HttpResponseCode|default(403) }} + {% elif general_abort == "1" %} + abort + {% endif %} + {% elif general_abort == "1" %} + abort + {% endif %} +{% endmacro %} + +{# +# Macro: render_handles +# Purpose: Renders the handles in the correct order (path-specific first, then catch-all), +# including basic authentication configuration. +# Parameters: +# @param handles (list): A list of handle objects to be rendered. +# @param basicauth_uuids (string): A comma-separated list of UUIDs for basic authentication. +#} +{% macro render_handles(handles, basicauth_uuids=None) %} + {{ render_basic_auth(basicauth_uuids) }} + {% for handle in handles %} + {% if handle.enabled|default("0") == "1" and handle.HandlePath %} + {{ reverse_proxy_configuration(handle) }} + {% endif %} + {% endfor %} + {% for handle in handles %} + {% if handle.enabled|default("0") == "1" and not handle.HandlePath %} + {{ reverse_proxy_configuration(handle) }} + {% endif %} + {% endfor %} +{% 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 +# - tls_configuration: Configures TLS settings for the domain. +# - handle_accesslist: Manages the logic for access lists, including configuration. +# - render_handles: Renders the handles in the correct order, including basic authentication. +# - reverse_proxy_configuration: Sets up the handle with reverse proxy configurations. +# - handle_response: Manages the response logic for access lists and aborts. # Important Details: -# - Order of Path specific Handles - Prioritizes order of specific path handles over catch-all handles. +# - 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'] }}" - {# The default are encrypted connections, uncencrypted connections have to render http:// #} {% if reverse.DisableTls|default("0") == "1" %}http://{% endif %}{{ reverse.FromDomain|default("") }}{% if reverse.FromPort %}:{{ reverse.FromPort }}{% endif %} { {% if reverse.AccessLog|default("0") == "1" %} {% if generalSettings.LogAccessPlain|default("0") == "0" %} @@ -560,8 +566,6 @@ {% endif %} {% set customCert = reverse.CustomCertificate|default("") %} {% set dnsChallenge = reverse.DnsChallenge|default("0") %} - {% set tlsDnsPropagationTimeout = generalSettings.TlsDnsPropagationTimeout %} - {% set tlsDnsPropagationResolvers = generalSettings.TlsDnsPropagationResolvers %} {{ tls_configuration( customCert, dnsChallenge, @@ -572,122 +576,36 @@ tlsDnsOptionalField2, tlsDnsOptionalField3, tlsDnsOptionalField4, - tlsDnsPropagationTimeout, - tlsDnsPropagationResolvers + generalSettings.TlsDnsPropagationTimeout, + generalSettings.TlsDnsPropagationResolvers ) }} - {% if not reverse.accesslist %} - {% set basicauth_uuids = reverse.basicauth %} - {{ basicauth_configuration(basicauth_uuids) }} - {% endif %} - {% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %} {% if subdomain.enabled|default("0") == "1" and subdomain.reverse == reverse['@uuid'] %} @{{ subdomain['@uuid'] }} { host {{ subdomain.FromDomain }} } handle @{{ subdomain['@uuid'] }} { + {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %} + {% set subdomain_content %} + {{ render_handles(subdomain_handles, subdomain.basicauth) }} + {% endset %} + {{ handle_accesslist(subdomain.accesslist, subdomain_content) }} - {% if not subdomain.accesslist %} - {% set subdomain_basicauth_uuids = subdomain.basicauth %} - {{ basicauth_configuration(subdomain_basicauth_uuids) }} - {% endif %} - - {% if subdomain.accesslist %} - {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', subdomain.accesslist) | first %} - {{ access_list_configuration(accesslist, accesslist.accesslistInvert|default("0") == "1") }} - handle @{{ accesslist['@uuid'] }} { - - {% set subdomain_basicauth_uuids = subdomain.basicauth %} - {{ basicauth_configuration(subdomain_basicauth_uuids) }} - - {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %} - {% for handle in subdomain_handles %} - {% if handle.enabled|default("0") == "1" and handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% for handle in subdomain_handles %} - {% if handle.enabled|default("0") == "1" and not handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - } - {% else %} - {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %} - {% for handle in subdomain_handles %} - {% if handle.enabled|default("0") == "1" and handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% for handle in subdomain_handles %} - {% if handle.enabled|default("0") == "1" and not handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% endif %} - - {% 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 %} + {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', subdomain.accesslist) | first %} + {{ handle_response(accesslist, Pischem.caddy.general.abort|default("0")) }} } {% endif %} {% endfor %} - {% if reverse.accesslist %} - {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', reverse.accesslist) | first %} - {{ access_list_configuration(accesslist, accesslist.accesslistInvert|default("0") == "1") }} - handle @{{ accesslist['@uuid'] }} { - - {% set basicauth_uuids = reverse.basicauth %} - {{ basicauth_configuration(basicauth_uuids) }} - - {% set wildcard_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %} - {% for handle in wildcard_handles %} - {% if handle.enabled|default("0") == "1" and handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% for handle in wildcard_handles %} - {% if handle.enabled|default("0") == "1" and not handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - } - {% else %} - {% set wildcard_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %} - {% for handle in wildcard_handles %} - {% if handle.enabled|default("0") == "1" and handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% for handle in wildcard_handles %} - {% if handle.enabled|default("0") == "1" and not handle.HandlePath %} - {{ reverse_proxy_configuration(handle) }} - {% endif %} - {% endfor %} - {% endif %} + {% set wildcard_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %} + {% set wildcard_content %} + {{ render_handles(wildcard_handles, reverse.basicauth) }} + {% endset %} + {{ handle_accesslist(reverse.accesslist, wildcard_content) }} {% 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 %} + {{ handle_response(accesslist, Pischem.caddy.general.abort|default("0")) }} } {% endif %} {% endfor %}