www/caddy: Complete Layer4 routing feature (#4281)

* www/caddy: Improve Layer 4 Routes to allow configurations outside the context of listener_wrappers, streaming and loadbalancing any TCP/UDP traffic on custom ports.

* www/caddy: Add validations for Layer 4 Routes.

* www/caddy: Control visibility of FromDomain in Layer4 based on Matcher. Remove need to always select * even when matcher does not support domains. Remove requirement for FromDomain, it is now validated based on selected Matcher.

* www/caddy: Cleanup validations, add isFieldChanged where applicable, removed weird old subdomain validation.

* www/caddy: Introduce InvertMatchers to invert any matchers, replacing the not tls sni matcher. Add HTTP and TLS without domain requirement as additional matchers. Add validations to ensure these matchers can not be chosen for listener_wrappers. Improve validations to ensure domain is empty when not using HTTP Host Header or TLS SNI Client Hello matchers.

* www/caddy: Bump model version. Add changelog.

* www/caddy: Since the prior validation demanded * and required FromDomain to be filled out, the new validation has to allow this too to avoid migration issues.

* www/caddy: Implement sequence number so rules can be sorted and be processed in custom order.
This commit is contained in:
Monviech
2024-10-08 13:00:43 +02:00
committed by GitHub
parent 8e4128a353
commit 30a1d4796e
7 changed files with 312 additions and 172 deletions
+4
View File
@@ -16,6 +16,10 @@ Plugin Changelog
1.7.2
* Add: Directive in HTTP Handler can be chosen, "reverse_proxy" and "redir"
* Add: Layer4 routes feature. Routing Type "global" or "listener_wrapper" can be chosen
* Add: Any Layer4 TCP/UDP traffic can be proxied without choosing a Layer 7 protocol matcher
* Change: Layer4 routes are not ordered automatically anymore
* Change: Layer4 "not tls sni" matcher has been replaced by generalized "Invert Matchers" checkbox
* Build: Update Caddy Layer4 module, fixes TLS matcher in chromium based browsers
* Fix: When Apply takes longer than 20 seconds, Caddy will be forcefully restarted
@@ -5,6 +5,12 @@
<type>checkbox</type>
<help><![CDATA[Enable this Layer4 route.]]></help>
</field>
<field>
<id>layer4.Sequence</id>
<label>Sequence</label>
<type>text</type>
<help><![CDATA[Rules are sorted based on the sequence number, higher number means lower priority. Rules without a sequence number will be processed first.]]></help>
</field>
<field>
<id>layer4.description</id>
<label>Description</label>
@@ -13,21 +19,59 @@
</field>
<field>
<type>header</type>
<label>Frontend</label>
<label>Type</label>
<advanced>true</advanced>
</field>
<field>
<id>layer4.FromDomain</id>
<label>Domain</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Enter one or multiple domains to route via SNI or Host Header. The domain will be added to the "listener_wrapper". Essentially, all traffic that Caddy receives on its frontend listeners (most likely the default HTTP and HTTPS ports) is funnelled through this wrapper to be processed and routed. Wildcard domains are allowed, e.g. "*.example.com". Host wildcards are allowed too, e.g. "*". Some protocols match all domains and "*" is mandatory.]]></help>
<id>layer4.Type</id>
<label>Routing Type</label>
<type>dropdown</type>
<help><![CDATA[Choose either "listener_wrappers" for multiplexing protocols on the default HTTP and HTTPS ports on OSI Layer 7, or "global" for raw TCP/UDP traffic routing on a custom "Local port" on OSI Layer 4 with optional OSI Layer 7 protocol matching.]]></help>
<advanced>true</advanced>
</field>
<field>
<type>header</type>
<label>Layer 4</label>
<advanced>true</advanced>
</field>
<field>
<id>layer4.Protocol</id>
<label>Protocol</label>
<type>dropdown</type>
<help><![CDATA[Match the received traffic on OSI Layer 4, either TCP or UDP. When "Routing Type" is "listener_wrappers", currently only TCP will match.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>layer4.FromPort</id>
<label>Local Port</label>
<type>text</type>
<help><![CDATA[Choose a custom local port to listen on.]]></help>
<advanced>true</advanced>
</field>
<field>
<type>header</type>
<label>Layer 7</label>
</field>
<field>
<id>layer4.Matchers</id>
<label>Matchers</label>
<type>dropdown</type>
<help><![CDATA[Match the traffic of the selected domains. The TCP/UDP packets will be routed to the selected upstream domains without terminating TLS or altering the traffic. Only protocols that send a "Client Hello" (like TLS), or a "Host Header" (like HTTP) can be routed here. Routing Precedence: 1. "SSH (or other protocols)", 2. "HTTP (Host Header)", 3. "TLS (SNI)", 4. "TLS (inverted SNI)", 5. "HTTP Handlers" (hidden default route for all unmatched traffic). The "SSH" matcher (and any other matcher that does not evaluate Host Header or SNI), will match any SSH like traffic on the default ports. That means, these protocols will only match once per ruleset and will proxy traffic to one upstream.]]></help>
<help><![CDATA[Match the received traffic on OSI Layer 7. When choosing a protocol like TLS, it will be matched and routed to the upstream destination. When "Routing Type" of the matcher is "listener_wrapper", any unmatched traffic will be received by the "HTTP App" (reverse_proxy). When "Routing Type" of the matcher is "global", unmatched traffic will be consumed and blocked. Choose "ANY" to not match on OSI Layer 7 and allow any traffic.]]></help>
</field>
<field>
<id>layer4.FromDomain</id>
<label>Domain</label>
<type>select_multiple</type>
<style>style_matchers tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Enter one or multiple domains to route via SNI or Host Header. Wildcard domains and host wildcards are allowed, e.g. "*.example.com" and "*".]]></help>
</field>
<field>
<id>layer4.InvertMatchers</id>
<label>Invert Matchers</label>
<type>checkbox</type>
<help><![CDATA[Invert the sense of the matcher. E.g., if the protocol is TLS, inverting will match all traffic that is not TLS. When domains have been chosen, these will be equally inverted.]]></help>
<advanced>true</advanced>
</field>
<field>
<type>header</type>
@@ -64,7 +108,6 @@
<field>
<type>header</type>
<label>Access</label>
<collapse>true</collapse>
</field>
<field>
<id>layer4.RemoteIp</id>
@@ -37,11 +37,11 @@ use OPNsense\Core\Config;
class Caddy extends BaseModel
{
// Check domain-port combinations
private function checkForUniquePortCombos($items, $messages)
private function checkForUniquePortCombos($messages)
{
$combos = [];
foreach ($items as $item) {
$key = $item->__reference; // Dynamic key based on item reference
foreach ($this->reverseproxy->reverse->iterateItems() as $item) {
$key = $item->__reference;
$fromDomain = (string) $item->FromDomain;
$fromPort = (string) $item->FromPort;
@@ -52,12 +52,9 @@ class Caddy extends BaseModel
}
foreach ($defaultPorts as $port) {
// Create a unique key for domain-port combination
$comboKey = $fromDomain . ':' . $port;
// Check for duplicate combinations
if (isset($combos[$comboKey])) {
// Use dynamic $key for message referencing
$messages->appendMessage(new Message(
sprintf(
gettext(
@@ -76,48 +73,6 @@ class Caddy extends BaseModel
}
}
// Check that subdomains are under a wildcard or exact domain
private function checkSubdomainsAgainstDomains($subdomains, $domains, $messages)
{
$wildcardDomainList = [];
foreach ($domains as $domain) {
if ((string) $domain->enabled === '1') {
$domainName = (string) $domain->FromDomain;
if (str_starts_with($domainName, '*.')) {
$wildcardBase = substr($domainName, 2);
$wildcardDomainList[$wildcardBase] = $domainName;
}
}
}
foreach ($subdomains as $subdomain) {
if ((string) $subdomain->enabled === '1') {
$subdomainName = (string) $subdomain->FromDomain;
$isValid = false;
foreach ($wildcardDomainList as $baseDomain => $wildcardDomain) {
if (str_ends_with($subdomainName, $baseDomain)) {
$isValid = true;
break;
}
}
if (!$isValid) {
$key = $subdomain->__reference; // Dynamic key based on subdomain reference
$messages->appendMessage(new Message(
sprintf(
gettext(
'Invalid subdomain configuration: %s does not fall ' .
'under any configured wildcard domain.'
),
$subdomainName
),
$key . ".FromDomain"
));
}
}
}
}
// Get the current OPNsense WebGUI ports and check for conflicts with Caddy
private function getWebGuiPorts()
{
@@ -172,8 +127,7 @@ class Caddy extends BaseModel
private function checkDisableTlsConflicts($messages)
{
foreach ($this->reverseproxy->reverse->iterateItems() as $item) {
// First check if the DisableTls field has been changed
if ($item->isFieldChanged('DisableTls')) {
if ($item->isFieldChanged()) {
if ((string) $item->DisableTls === '1') {
$conflictChecks = [
'DnsChallenge' => (string) $item->DnsChallenge === '1',
@@ -214,7 +168,7 @@ class Caddy extends BaseModel
if ($httpPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, HTTP port must not be empty and must be 1024 or above.'
'www user is active, HTTP port must not be empty and must be 1024 or above.'
),
"general.HttpPort"
));
@@ -224,7 +178,7 @@ class Caddy extends BaseModel
if ($httpsPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, HTTPS port must not be empty and must be 1024 or above.'
'www user is active, HTTPS port must not be empty and must be 1024 or above.'
),
"general.HttpsPort"
));
@@ -237,7 +191,7 @@ class Caddy extends BaseModel
if ($fromPort !== null && $fromPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, port must be empty or must be 1024 or above.'
'www user is active, port must be empty or must be 1024 or above.'
),
$item->__reference . ".FromPort"
));
@@ -249,36 +203,111 @@ class Caddy extends BaseModel
));
}
}
foreach ($this->reverseproxy->layer4->iterateItems() as $item) {
$fromPort = !empty((string)$item->FromPort) ? (string)$item->FromPort : null;
if ($fromPort !== null && $fromPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'www user is active, port must be empty or must be 1024 or above.'
),
$item->__reference . ".FromPort"
));
$messages->appendMessage(new Message(
gettext(
'Ports in "Reverse Proxy - Layer4 Routes" must be empty or must be 1024 or above.'
),
"general.DisableSuperuser"
));
}
}
}
}
/**
* Check that when certain Layer4 matchers are selected, only "*" is valid as FromDomain.
* This happens because they cannot be matched by host header or SNI, so they match all traffic.
* The "*" shows the user that all traffic will be matched, and that creating multiple
* matchers will not result in more routes for the same traffic type to work.
*/
private function checkLayer4Matchers($messages)
{
foreach ($this->reverseproxy->layer4->iterateItems() as $item) {
$matchers = (string) $item->Matchers;
$fromDomain = (string) $item->FromDomain;
// Check if matchers is not in the list of specific values
$isNotInSpecificMatchers = !in_array($matchers, ['httphost', 'tlssni', 'nottlssni']);
$isInvalidFromDomain = $fromDomain !== '*';
if ($isNotInSpecificMatchers && $isInvalidFromDomain) {
if ($item->isFieldChanged()) {
$key = $item->__reference;
$messages->appendMessage(new Message(
sprintf(
gettext(
'When "%s" matcher is selected, the only valid entry in Domain is "*".'
if (in_array((string)$item->Matchers, ['httphost', 'tlssni']) && empty((string)$item->FromDomain)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When "%s" matcher is selected, domain is required.'
),
$item->Matchers
),
$matchers
),
$key . ".FromDomain"
));
$key . ".FromDomain"
));
} elseif (
!in_array((string)$item->Matchers, ['httphost', 'tlssni']) &&
(
!empty((string)$item->FromDomain) &&
(string)$item->FromDomain != '*'
)
) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When "%s" matcher is selected, domain must be empty or *.'
),
$item->Matchers
),
$key . ".FromDomain"
));
}
if ((string)$item->Type === 'global' && empty((string)$item->FromPort)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When routing type is "%s", port is required.'
),
$item->Type
),
$key . ".FromPort"
));
} elseif ((string)$item->Type !== 'global' && !empty((string)$item->FromPort)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When routing type is "%s", port must be empty.'
),
$item->Type
),
$key . ".FromPort"
));
}
if ((string)$item->Type !== 'global' && ((string)$item->Protocol !== 'tcp')) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When routing type is "%s", protocol must be TCP.'
),
$item->Type
),
$key . ".Protocol"
));
}
if ((string)$item->Type !== 'global' &&
(
(string)$item->Matchers == 'tls' ||
(string)$item->Matchers == 'http'
)
) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When routing type is "%s", matchers "HTTP" or "TLS" cannot be chosen.'
),
$item->Type
),
$key . ".Matchers"
));
}
}
}
}
@@ -288,29 +317,10 @@ class Caddy extends BaseModel
{
$messages = parent::performValidation($validateFullModel);
// Check domain-port combinations
$this->checkForUniquePortCombos(
$this->reverseproxy->reverse->iterateItems(),
$messages
);
// Check that subdomains are under a wildcard or exact domain
$this->checkSubdomainsAgainstDomains(
$this->reverseproxy->subdomain->iterateItems(),
$this->reverseproxy->reverse->iterateItems(),
$messages
);
// Check WebGUI conflicts
$this->checkForUniquePortCombos($messages);
$this->checkWebGuiSettings($messages);
// Check for TLS conflicts in Domain
$this->checkDisableTlsConflicts($messages);
// Check DisableSuperuser Port conflicts
$this->checkSuperuserPorts($messages);
// Check Layer4 matchers
$this->checkLayer4Matchers($messages);
return $messages;
@@ -1,7 +1,7 @@
<model>
<mount>//Pischem/caddy</mount>
<description>Caddy Reverse Proxy</description>
<version>1.3.2</version>
<version>1.3.3</version>
<items>
<general>
<enabled type="BooleanField">
@@ -453,8 +453,35 @@
<Default>1</Default>
<Required>Y</Required>
</enabled>
<FromDomain type="HostnameField">
<Sequence type="AutoNumberField">
<MinimumValue>1</MinimumValue>
<MaximumValue>99999</MaximumValue>
<ValidationMessage>Please enter a value between 1 and 99999 or leave empty.</ValidationMessage>
<Constraints>
<check001>
<ValidationMessage>Sequence value has to be unique or empty.</ValidationMessage>
<type>UniqueConstraint</type>
</check001>
</Constraints>
</Sequence>
<Type type="OptionField">
<Required>Y</Required>
<Default>listener_wrappers</Default>
<OptionValues>
<listener_wrappers>listener_wrappers</listener_wrappers>
<global>global</global>
</OptionValues>
</Type>
<Protocol type="OptionField">
<Required>Y</Required>
<Default>tcp</Default>
<OptionValues>
<tcp>TCP</tcp>
<udp>UDP</udp>
</OptionValues>
</Protocol>
<FromPort type="PortField"/>
<FromDomain type="HostnameField">
<IpAllowed>N</IpAllowed>
<FqdnWildcardAllowed>Y</FqdnWildcardAllowed>
<HostWildcardAllowed>Y</HostWildcardAllowed>
@@ -466,7 +493,9 @@
<Required>Y</Required>
<Default>tlssni</Default>
<OptionValues>
<any>ANY</any>
<dns>DNS</dns>
<http>HTTP</http>
<httphost>HTTP (Host Header)</httphost>
<postgres>Postgres</postgres>
<proxy_protocol>Proxy Protocol</proxy_protocol>
@@ -474,11 +503,13 @@
<socks4>SOCKSv4</socks4>
<socks5>SOCKSv5</socks5>
<ssh>SSH</ssh>
<tlssni>TLS (SNI)</tlssni>
<nottlssni>TLS (inverted SNI)</nottlssni>
<tls>TLS</tls>
<tlssni>TLS (SNI Client Hello)</tlssni>
<wireguard>Wireguard</wireguard>
<xmpp>XMPP</xmpp>
</OptionValues>
</Matchers>
<InvertMatchers type="BooleanField"/>
<ToDomain type="HostnameField">
<Required>Y</Required>
<FieldSeparator>,</FieldSeparator>
@@ -339,6 +339,14 @@
}
});
$("#layer4\\.Matchers").change(function() {
if ($(this).val() !== "tlssni" && $(this).val() !== "httphost") {
$(".style_matchers").closest('tr').hide();
} else {
$(".style_matchers").closest('tr').show();
}
});
// Initialize tabs, service control and filter selectpicker
initializeTabs();
updateServiceControlUI('caddy');
@@ -617,8 +625,13 @@
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="enabled" data-width="6em" data-type="boolean" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="Sequence" data-type="string">{{ lang._('Sequence') }}</th>
<th data-column-id="Type" data-type="string" data-visible="false">{{ lang._('Routing Type') }}</th>
<th data-column-id="Protocol" data-type="string">{{ lang._('Protocol') }}</th>
<th data-column-id="FromPort" data-type="string" data-visible="false">{{ lang._('Local Port') }}</th>
<th data-column-id="FromDomain" data-type="string">{{ lang._('Domain') }}</th>
<th data-column-id="Matchers" data-type="string">{{ lang._('Matcher') }}</th>
<th data-column-id="Matchers" data-type="string">{{ lang._('Matchers') }}</th>
<th data-column-id="InvertMatchers" data-type="boolean" data-formatter="boolean" data-visible="false">{{ lang._('Invert Matchers') }}</th>
<th data-column-id="ToDomain" data-type="string">{{ lang._('Upstream Domain') }}</th>
<th data-column-id="ToPort" data-type="string">{{ lang._('Upstream Port') }}</th>
<th data-column-id="RemoteIp" data-type="string" data-visible="false">{{ lang._('Remote IP') }}</th>
@@ -90,13 +90,27 @@
{% endif %}
{% if generalSettings.EnableLayer4|default("0") == "1" %}
listener_wrappers {
{# Plug the Layer 4 template in #}
{% include "OPNsense/Caddy/includeLayer4" %}
layer4 {
import /usr/local/etc/caddy/caddy.d/*.layer4listener
{% set context_var = "listener_wrappers" %}
{% include "OPNsense/Caddy/includeLayer4" %}
{# Empty Route that catches all other traffic #}
route
}
{# Route all other traffic to HTTP App #}
tls
}
{% endif %}
}
{% if generalSettings.EnableLayer4|default("0") == "1" %}
layer4 {
import /usr/local/etc/caddy/caddy.d/*.layer4global
{% set context_var = "global" %}
{% include "OPNsense/Caddy/includeLayer4" %}
}
{% endif %}
{#
# Section: Dynamic DNS Global Configuration
# Purpose: Sets up global configuration for Dynamic DNS. Caddy needs to be compiled with
@@ -1,15 +1,31 @@
{#
# This file sets up the listener_wrapper for layer 4 routing support.
# - Section: Servers Global Configuration
# Also allows for custom configurations with the import statement.
# This file sets up layer4 routing support.
# There are two contexts: "listener_wrappers" and "global"
#
# "listener_wrappers" multiplexes on OSI Layer 7 on the default HTTP and HTTPS ports and requires a traffic matcher since
# otherwise the "reverse_proxy" would stop receiving any requests. The "any" Layer 7 matcher is not allowed here.
# This context allows for matching domains via SNI and route them without terminating TLS.
#
# "global" can set up custom ports and also route any OSI Layer 4 TCP/UDP traffic without a matcher.
# They will be grouped under the same protocol/port combination
# to allow multiple Layer 7 matchers inside the scope of the same Layer 4 matcher.
# This context is for advanced usecases where raw TCP/UDP traffic on custom ports should be proxied or load balanced.
#}
{% set layer4_configs = helpers.toList('Pischem.caddy.reverseproxy.layer4') %}
{# Nested Macro for proxy definition #}
{% macro define_proxy(to_domains, to_port, fail_duration, proxy_protocol) %}
{% set unsorted_layer4_configs = helpers.toList('Pischem.caddy.reverseproxy.layer4') %}
{# Ensure that 'Sequence' is present and converted to an integer in each item #}
{% for item in unsorted_layer4_configs %}
{% set _ = item.update({'Sequence': item.get('Sequence', '0') | int}) %}
{% endfor %}
{# Sort the configurations based on 'Sequence' #}
{% set layer4_configs = unsorted_layer4_configs | sort(attribute='Sequence') %}
{% macro define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) %}
proxy {% for domain in to_domains.split(',') %}
{% set is_ipv6 = (':' in domain) %} {# Check if the domain contains a colon, typical in IPv6 addresses #}
{{ '[' if is_ipv6 }}{{ domain }}{{ ']' if is_ipv6 }}:{{ to_port }}{% if not loop.last %} {% endif %}
{% set is_ipv6 = (':' in domain) %}
{{ layer4.Protocol }}/{{ '[' if is_ipv6 }}{{ domain }}{{ ']' if is_ipv6 }}:{{ to_port }}{% if not loop.last %} {% endif %}
{% endfor %} {
{% if fail_duration %}
fail_duration {{ fail_duration }}s
@@ -20,64 +36,73 @@
}
{% endmacro %}
{# Macro for configuring the proxy with additional remote IP access list #}
{% macro configure_proxy(to_domains, to_port, remote_ips, fail_duration, proxy_protocol) %}
{% if remote_ips %}
{% set ip_list = remote_ips.split(',') %}
subroute {
@allowed_ips remote_ip {{ ip_list|join(' ') }}
route @allowed_ips {
{# Call Nested Macro #}
{{ define_proxy(to_domains, to_port, fail_duration, proxy_protocol) }}
{% macro configure_proxy(layer4, to_domains, to_port, remote_ips, fail_duration, proxy_protocol) %}
{% set content %}
{% if remote_ips %}
{% set ip_list = remote_ips.split(',') %}
subroute {
@allowed_ips remote_ip {{ ip_list|join(' ') }}
route @allowed_ips {
{{ define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) }}
}
}
}
{% else %}
{{ define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) }}
{% endif %}
{% endset %}
{{ content|trim }}
{% endmacro %}
{% set grouped_configs = {} %}
{% for layer4 in layer4_configs %}
{% if layer4.FromPort and layer4.Protocol and layer4.enabled == "1" %}
{% set key = layer4.Protocol ~ '/:' ~ layer4.FromPort %}
{% if not key in grouped_configs %}
{% set _ = grouped_configs.update({key: []}) %}
{% endif %}
{% set _ = grouped_configs[key].append(layer4) %}
{% endif %}
{% endfor %}
{% macro handle_special_matchers(layer4) %}
{% set invert_prefix = 'not ' if layer4.InvertMatchers == '1' else '' %}
{% if layer4.Matchers == 'httphost' %}
{{ invert_prefix }}http host {{ layer4.FromDomain.replace(',', ' ') }}
{% elif layer4.Matchers == 'tlssni' %}
{{ invert_prefix }}tls sni {{ layer4.FromDomain.replace(',', ' ') }}
{% else %}
{# Call Nested Macro #}
{{ define_proxy(to_domains, to_port, fail_duration, proxy_protocol) }}
{{ invert_prefix }}{{ layer4.Matchers }}
{% endif %}
{% endmacro %}
{# Set up Layer4 App #}
layer4 {
import /usr/local/etc/caddy/caddy.d/*.layer4
{# 1. loop to handle any traffic matchers that can only be added once since they match all specific protocol traffic. #}
{% if context_var == 'listener_wrappers' %}
{% for layer4 in layer4_configs %}
{% if layer4.enabled == "1" and layer4.Matchers not in ['httphost', 'tlssni', 'nottlssni'] %}
@{{ layer4['@uuid'] }} {{ layer4.Matchers }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% if layer4.enabled == "1" and layer4.Type == 'listener_wrappers' %}
{% if layer4.Matchers != 'any' %}
@{{ layer4['@uuid'] }} {{ handle_special_matchers(layer4) }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endif %}
{% endfor %}
{# 2. loop to handle http host matchers #}
{% for layer4 in layer4_configs %}
{% if layer4.enabled == "1" and layer4.Matchers == 'httphost' %}
@{{ layer4['@uuid'] }} http host {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% elif context_var == 'global' %}
{% for key, layers in grouped_configs.items() %}
{{ key }} {
{% for layer4 in layers %}
{% if layer4.enabled == "1" and layer4.Type == 'global' %}
{% if layer4.Matchers != 'any' %}
@{{ layer4['@uuid'] }} {{ handle_special_matchers(layer4) }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% else %}
route {
{{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endif %}
{% endfor %}
}
{% endfor %}
{# 3. loop to handle tls sni matchers #}
{% for layer4 in layer4_configs %}
{% if layer4.enabled == "1" and layer4.Matchers == 'tlssni' %}
@{{ layer4['@uuid'] }} tls sni {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}
{# 4. loop to handle not tls sni matchers #}
{% for layer4 in layer4_configs %}
{% if layer4.enabled == "1" and layer4.Matchers == 'nottlssni' %}
@{{ layer4['@uuid'] }} not tls sni {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}
{# Empty Route that catches all other traffic #}
route
}
{# Route all other traffic to HTTP App #}
tls
{% endif %}