Skip to content

Commit

Permalink
www/caddy: Add Layer4 openvpn, winbox and quic matcher (#4325)
Browse files Browse the repository at this point in the history
* www/caddy: Add CRUD for Layer4 OpenVPN matcher with mode and static key support.

* www/caddy: Export static keys to the filesystem as uuid.key

* www/caddy: Remove validation that checks for multiple keys, help text is enough.

* www/caddy: Expand layer4 template for all supported OpenVPN modes.

* www/caddy: Prevent multiple static keys for modes other than crypt2_client. Fix helptexts.

* www/caddy: Add unique constraint to description of openvpn static key

* www/caddy: Changelog and version bump

* www/caddy: Make static key optional when choosing the tls mode in openvpn matcher

* www/caddy: Prepare new Layer7 Matcher Tab for more customizable matchers in the future.

* www/caddy: Add Layer4 QUIC matcher.

* www/caddy: Rename matcherTab

* www/caddy: Revert a4ea0cb since its non operational and will not be needed for a while anyway

* www/caddy: Changelog
  • Loading branch information
Monviech authored Nov 4, 2024
1 parent e806ea3 commit 72e09d5
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 55 deletions.
2 changes: 1 addition & 1 deletion www/caddy/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PLUGIN_NAME= caddy
PLUGIN_VERSION= 1.7.3
PLUGIN_VERSION= 1.7.4
PLUGIN_DEPENDS= caddy-custom
PLUGIN_COMMENT= Modern Reverse Proxy with Automatic HTTPS, Dynamic DNS and Layer4 Routing
PLUGIN_MAINTAINER= [email protected]
Expand Down
12 changes: 12 additions & 0 deletions www/caddy/pkg-descr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ DOC: https://docs.opnsense.org/manual/how-tos/caddy.html
Plugin Changelog
================

1.7.4

* Add: Layer4 OpenVPN matcher with mode, digest and static key support
* Add: Layer4 Winbox matcher
* Add: Layer4 QUIC matcher
* Build: Update dependency to lang/go123
* Build: Update caddy-l4 and caddy-dynamicdns module
* Build: Fix that caddy-l4 does not stop when ssh is proxied
* Build: DNS Providers: Update porkbun, dnsmadeeasy
* Cleanup: Layer4 default route in listener_wrappers has been removed (obsolete)
* Fix: Error when same Access List is set to wildcard and subdomain

1.7.3

* Add: Clear All button to Filter by Domain selectpicker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,40 +209,6 @@ public function toggleHandleAction($uuid, $enabled = null)
return $this->toggleBase("reverseproxy.handle", $uuid, $enabled);
}


// Layer4 Section

public function searchLayer4Action()
{
return $this->searchBase("reverseproxy.layer4", null, 'description');
}

public function setLayer4Action($uuid)
{
return $this->setBase("layer4", "reverseproxy.layer4", $uuid);
}

public function addLayer4Action()
{
return $this->addBase("layer4", "reverseproxy.layer4");
}

public function getLayer4Action($uuid = null)
{
return $this->getBase("layer4", "reverseproxy.layer4", $uuid);
}

public function delLayer4Action($uuid)
{
return $this->delBase("reverseproxy.layer4", $uuid);
}

public function toggleLayer4Action($uuid, $enabled = null)
{
return $this->toggleBase("reverseproxy.layer4", $uuid, $enabled);
}


// AccessList Section

public function searchAccessListAction()
Expand Down Expand Up @@ -349,4 +315,70 @@ public function delHeaderAction($uuid)
{
return $this->delBase("reverseproxy.header", $uuid);
}


// Layer4 Proxy Section

public function searchLayer4Action()
{
return $this->searchBase("reverseproxy.layer4", null, 'description');
}

public function setLayer4Action($uuid)
{
return $this->setBase("layer4", "reverseproxy.layer4", $uuid);
}

public function addLayer4Action()
{
return $this->addBase("layer4", "reverseproxy.layer4");
}

public function getLayer4Action($uuid = null)
{
return $this->getBase("layer4", "reverseproxy.layer4", $uuid);
}

public function delLayer4Action($uuid)
{
return $this->delBase("reverseproxy.layer4", $uuid);
}

public function toggleLayer4Action($uuid, $enabled = null)
{
return $this->toggleBase("reverseproxy.layer4", $uuid, $enabled);
}


// Layer4 OpenVPN Section

public function searchLayer4OpenvpnAction()
{
return $this->searchBase("reverseproxy.layer4openvpn", null, 'description');
}

public function setLayer4OpenvpnAction($uuid)
{
return $this->setBase("layer4openvpn", "reverseproxy.layer4openvpn", $uuid);
}

public function addLayer4OpenvpnAction()
{
return $this->addBase("layer4openvpn", "reverseproxy.layer4openvpn");
}

public function getLayer4OpenvpnAction($uuid = null)
{
return $this->getBase("layer4openvpn", "reverseproxy.layer4openvpn", $uuid);
}

public function delLayer4OpenvpnAction($uuid)
{
return $this->delBase("reverseproxy.layer4openvpn", $uuid);
}

public function toggleLayer4OpenvpnAction($uuid, $enabled = null)
{
return $this->toggleBase("reverseproxy.layer4openvpn", $uuid, $enabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ public function indexAction()
{
$this->view->pick('OPNsense/Caddy/layer4');
$this->view->formDialogLayer4 = $this->getForm("dialogLayer4");
$this->view->formDialogLayer4Openvpn = $this->getForm("dialogLayer4Openvpn");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,26 @@
<id>layer4.FromDomain</id>
<label>Domain</label>
<type>select_multiple</type>
<style>style_matchers tokenize</style>
<style>style_matchers tokenize matchers_domain</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.FromOpenvpnModes</id>
<label>OpenVPN Mode</label>
<type>dropdown</type>
<style>style_matchers matchers_openvpn</style>
<help><![CDATA[Select the mode matching the OpenVPN Server or Client.]]></help>
</field>
<field>
<id>layer4.FromOpenvpnStaticKey</id>
<label>OpenVPN Static Key</label>
<type>select_multiple</type>
<style>style_matchers selectpicker matchers_openvpn</style>
<hint>Any</hint>
<size>5</size>
<help><![CDATA[Select a Static Key to match. Multiple Static Keys are only supported for tls-crypt2_client mode.]]></help>
</field>
<field>
<id>layer4.InvertMatchers</id>
<label>Invert Matchers</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<fields>
<field>
<id>layer4openvpn.description</id>
<label>Description</label>
<type>text</type>
</field>
<field>
<id>layer4openvpn.StaticKey</id>
<label>Static Key</label>
<type>textbox</type>
<help>Paste an OpenVPN Static key.</help>
</field>
</fields>
33 changes: 29 additions & 4 deletions www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ private function checkLayer4Matchers($messages)
foreach ($this->reverseproxy->layer4->iterateItems() as $item) {
if ($item->isFieldChanged()) {
$key = $item->__reference;
if (in_array((string)$item->Matchers, ['httphost', 'tlssni']) && empty((string)$item->FromDomain)) {
if (in_array((string)$item->Matchers, ['httphost', 'tlssni', 'quicsni']) && empty((string)$item->FromDomain)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
Expand All @@ -191,7 +191,7 @@ private function checkLayer4Matchers($messages)
$key . ".FromDomain"
));
} elseif (
!in_array((string)$item->Matchers, ['httphost', 'tlssni']) &&
!in_array((string)$item->Matchers, ['httphost', 'tlssni', 'quicsni']) &&
(
!empty((string)$item->FromDomain) &&
(string)$item->FromDomain != '*'
Expand All @@ -208,6 +208,30 @@ private function checkLayer4Matchers($messages)
));
}

if ((string)$item->Matchers !== 'openvpn' && !empty((string)$item->FromOpenvpnModes)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When "%s" matcher is selected, field must be empty.'
),
$item->Matchers
),
$key . ".FromOpenvpnModes"
));
}

if ((string)$item->Matchers !== 'openvpn' && !empty((string)$item->FromOpenvpnStaticKey)) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When "%s" matcher is selected, field must be empty.'
),
$item->Matchers
),
$key . ".FromOpenvpnStaticKey"
));
}

if ((string)$item->Type === 'global' && empty((string)$item->FromPort)) {
$messages->appendMessage(new Message(
sprintf(
Expand Down Expand Up @@ -246,13 +270,14 @@ private function checkLayer4Matchers($messages)
(string)$item->Type !== 'global' &&
(
(string)$item->Matchers == 'tls' ||
(string)$item->Matchers == 'http'
(string)$item->Matchers == 'http' ||
(string)$item->Matchers == 'quic'
)
) {
$messages->appendMessage(new Message(
sprintf(
gettext(
'When routing type is "%s", matchers "HTTP" or "TLS" cannot be chosen.'
'When routing type is "%s", matchers "HTTP", "TLS" or "QUIC" cannot be chosen.'
),
$item->Type
),
Expand Down
41 changes: 41 additions & 0 deletions www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,29 @@
<AsList>Y</AsList>
<ValidationMessage>Please enter one or multiple hostnames or FQDNs.</ValidationMessage>
</FromDomain>
<FromOpenvpnModes type="OptionField">
<BlankDesc>Any</BlankDesc>
<OptionValues>
<auth_sha256_normal>tls-auth_sha256_normal</auth_sha256_normal>
<auth_sha256_inverse>tls-auth_sha256_inverse</auth_sha256_inverse>
<auth_sha512_normal>tls-auth_sha512_normal</auth_sha512_normal>
<auth_sha512_inverse>tls-auth_sha512_inverse</auth_sha512_inverse>
<crypt>tls-crypt</crypt>
<crypt2_client>tls-crypt2_client</crypt2_client>
<crypt2_server>tls-crypt2_server</crypt2_server>
</OptionValues>
</FromOpenvpnModes>
<FromOpenvpnStaticKey type="ModelRelationField">
<Model>
<reverseproxy>
<source>OPNsense.Caddy.Caddy</source>
<items>reverseproxy.layer4openvpn</items>
<display>description</display>
<display_format>%s</display_format>
</reverseproxy>
</Model>
<Multiple>Y</Multiple>
</FromOpenvpnStaticKey>
<Matchers type="OptionField">
<Required>Y</Required>
<Default>tlssni</Default>
Expand All @@ -497,14 +520,18 @@
<dns>DNS</dns>
<http>HTTP</http>
<httphost>HTTP (Host Header)</httphost>
<openvpn>OpenVPN</openvpn>
<postgres>Postgres</postgres>
<proxy_protocol>Proxy Protocol</proxy_protocol>
<quic>QUIC</quic>
<quicsni>QUIC (SNI Client Hello)</quicsni>
<rdp>RDP</rdp>
<socks4>SOCKSv4</socks4>
<socks5>SOCKSv5</socks5>
<ssh>SSH</ssh>
<tls>TLS</tls>
<tlssni>TLS (SNI Client Hello)</tlssni>
<winbox>Winbox</winbox>
<wireguard>Wireguard</wireguard>
<xmpp>XMPP</xmpp>
</OptionValues>
Expand Down Expand Up @@ -539,6 +566,20 @@
</RemoteIp>
<description type="DescriptionField"/>
</layer4>
<layer4openvpn type="ArrayField">
<StaticKey type="TextField">
<Required>Y</Required>
</StaticKey>
<description type="DescriptionField">
<Required>Y</Required>
<Constraints>
<check001>
<ValidationMessage>Description must be unique.</ValidationMessage>
<type>UniqueConstraint</type>
</check001>
</Constraints>
</description>
</layer4openvpn>
</reverseproxy>
</items>
</model>
Loading

0 comments on commit 72e09d5

Please sign in to comment.