www/caddy: Add remote_ip matcher for access list functionality in Layer4 proxy (#4147)

This commit is contained in:
Monviech
2024-08-05 11:50:39 +02:00
committed by GitHub
parent dd68ab68d8
commit 8bc3779eeb
5 changed files with 44 additions and 6 deletions
+1
View File
@@ -33,6 +33,7 @@ Plugin Changelog
* Add: Authentik as authentication provider (contributed by Tim-Sc)
* Add: Feature Preview - Layer4 routing to proxy traffic without TLS termination. Can be enabled in advanced mode of "General Settings"
* Add: Layer4 protocols: HTTP, Postgres, Proxy Protocol, RDP, SOCKS4, SOCKS5, SSH, TLS, XMPP
* Add: Layer4 Remote IP matcher to restrict access to proxied services
1.6.1
@@ -61,4 +61,17 @@
<type>dropdown</type>
<help><![CDATA[Add the HA Proxy Protocol header. Either version 1 or 2 can be chosen. The default is off, since it is only needed when the upstream can use the Proxy Protocol header.]]></help>
</field>
<field>
<type>header</type>
<label>Access</label>
<collapse>true</collapse>
</field>
<field>
<id>layer4.RemoteIp</id>
<label>Remote IP</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Enter one or multiple IP addresses and networks. Leaving this empty will allow any remote client to connect. If populated and the remote IP address of a client matches, the connection to the "Upstream Domain" is allowed, otherwise the connection is dropped. Due to restrictions with this matcher, this list can not be inverted.]]></help>
</field>
</form>
@@ -438,6 +438,12 @@
<v2>v2</v2>
</OptionValues>
</ProxyProtocol>
<RemoteIp type="NetworkField">
<FieldSeparator>,</FieldSeparator>
<AsList>Y</AsList>
<Strict>Y</Strict>
<ValidationMessage>Please enter one or multiple valid IP addresses or networks.</ValidationMessage>
</RemoteIp>
<description type="DescriptionField"/>
</layer4>
</reverseproxy>
@@ -561,6 +561,7 @@
<th data-column-id="Matchers" data-type="string">{{ lang._('Matcher') }}</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>
<th data-column-id="PassiveHealthFailDuration" data-type="string" data-visible="false">{{ lang._('Fail Duration') }}</th>
<th data-column-id="ProxyProtocol" data-type="string" data-visible="false">{{ lang._('Proxy Protocol') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
@@ -5,8 +5,8 @@
#}
{% set layer4_configs = helpers.toList('Pischem.caddy.reverseproxy.layer4') %}
{# Define a macro for setting up the proxy #}
{% macro setup_proxy(to_domains, to_port, fail_duration, proxy_protocol) %}
{# Nested Macro for proxy definition #}
{% macro define_proxy(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 %}
@@ -20,6 +20,23 @@
}
{% 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) }}
}
}
{% else %}
{# Call Nested Macro #}
{{ define_proxy(to_domains, to_port, fail_duration, proxy_protocol) }}
{% endif %}
{% endmacro %}
{# Set up Layer4 App #}
layer4 {
import /usr/local/etc/caddy/caddy.d/*.layer4
@@ -28,7 +45,7 @@ layer4 {
{% if layer4.enabled == "1" and layer4.Matchers not in ['httphost', 'tlssni', 'nottlssni'] %}
@{{ layer4['@uuid'] }} {{ layer4.Matchers }}
route @{{ layer4['@uuid'] }} {
{{ setup_proxy(layer4.ToDomain, layer4.ToPort, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}
@@ -37,7 +54,7 @@ layer4 {
{% if layer4.enabled == "1" and layer4.Matchers == 'httphost' %}
@{{ layer4['@uuid'] }} http host {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ setup_proxy(layer4.ToDomain, layer4.ToPort, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}
@@ -46,7 +63,7 @@ layer4 {
{% if layer4.enabled == "1" and layer4.Matchers == 'tlssni' %}
@{{ layer4['@uuid'] }} tls sni {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ setup_proxy(layer4.ToDomain, layer4.ToPort, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}
@@ -55,7 +72,7 @@ layer4 {
{% if layer4.enabled == "1" and layer4.Matchers == 'nottlssni' %}
@{{ layer4['@uuid'] }} not tls sni {{ layer4.FromDomain.replace(',', ' ') }}
route @{{ layer4['@uuid'] }} {
{{ setup_proxy(layer4.ToDomain, layer4.ToPort, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
{{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
}
{% endif %}
{% endfor %}