www/caddy: Add optional "DisableSuperuser" to run caddy as non root (#4081)

* www/caddy: Add option to run caddy as www user instead of root superuser.

* www/caddy: Add validation for DisableSuperuser that makes sure only ports 1024 or above are used.

* www/caddy: Change rc.conf.d/caddy script to conditionally set the caddy_user and caddy_group to www when DisableSuperuser is activated.

* www/caddy: Print the current Caddy user in the Caddyfile for easier support.

* www/caddy: Improve setup.sh script. Create all directories, add root:www owners, differentiate between directories and files when creating permissions.

* www/caddy: Remove copyright header in rc.conf.d/caddy.

* www/caddy: Improve helptext for new DisableSuperuser feature in general.xml.

* www/caddy: Fix style of rc.conf.d/caddy and caddy.php. Add changelog and bump plugin version.

* www/caddy: Append additional message to DisableSuperuser that warns users about conflicting ports in otherwise hidden forms in the Reverse Proxy Domains view.

* www/caddy: Add changelog.
This commit is contained in:
Monviech
2024-07-16 13:28:22 +02:00
committed by GitHub
parent 160c6515c2
commit 99628488ce
9 changed files with 150 additions and 21 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PLUGIN_NAME= caddy
PLUGIN_VERSION= 1.6.0
PLUGIN_VERSION= 1.6.1
PLUGIN_DEPENDS= caddy-custom
PLUGIN_COMMENT= Easy to configure Reverse Proxy with Automatic HTTPS and Dynamic DNS
PLUGIN_MAINTAINER= cedrik@pischem.com
+5
View File
@@ -25,6 +25,11 @@ DOC: https://docs.opnsense.org/manual/how-tos/caddy.html
Plugin Changelog
================
1.6.1
Add: Run Caddy as "www" user and group, by enabling "Disable Superuser" in General Settings.
Cleanup: Validations in general.volt are all appended to their form keys, instead of triggering a Bootstrap Dialog.
1.6.0
* Add: New Dashboard widgets for 24.7, showing domain status and certificate validity status.
@@ -5,7 +5,7 @@
#
# Define Unix socket source for Caddy
source s_caddy {
unix-dgram("/var/caddy/var/run/log");
unix-dgram("/var/run/caddy/log.sock");
};
# Parser for Caddy log levels
@@ -21,6 +21,13 @@
<help><![CDATA[If the default HTTPS port is changed to e.g. 8443, then a port forward from port 443 to 8443 is necessary to issue automatic certificates with the TLS-ALPN-01 challenge and serve clients the reverse proxied resources.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>caddy.general.DisableSuperuser</id>
<label>Disable Superuser</label>
<type>checkbox</type>
<help><![CDATA[Run this service as "www" user and group, instead of "root". This setting increases security, but comes with the hard restriction that the well-known port range can not be used anymore. After enabling and saving this setting, the service has to be totally restarted. For this, please disable Caddy and press Apply. Afterwards enable Caddy and press Apply. This setting is reversible by following the same steps.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>caddy.general.TlsEmail</id>
<label>ACME Email</label>
@@ -211,6 +211,61 @@ class Caddy extends BaseModel
}
}
/**
* 6. Check that when Superuser is disabled, all ports are 1024 and above.
* In General settings where this triggers, a validation dialog will show the hidden validation of the domain ports.
* The default HTTP and HTTPS ports are not allowed to be empty, since then they are 80 and 443.
* Domain ports are allowed to be empty, since then they have the same value as the HTTP and HTTPS default ports.
* Any value that is below 1024 will trigger the validation.
*/
private function checkSuperuserPorts($messages)
{
if ((string)$this->general->DisableSuperuser === '1') {
$httpPort = !empty((string)$this->general->HttpPort) ? (string)$this->general->HttpPort : 80;
$httpsPort = !empty((string)$this->general->HttpsPort) ? (string)$this->general->HttpsPort : 443;
// Check default HTTP port
if ($httpPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, HTTP port must not be empty and must be 1024 or above.'
),
"general.HttpPort"
));
}
// Check default HTTPS port
if ($httpsPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, HTTPS port must not be empty and must be 1024 or above.'
),
"general.HttpsPort"
));
}
// Check ports under domain configurations
foreach ($this->reverseproxy->reverse->iterateItems() as $item) {
$fromPort = !empty((string)$item->FromPort) ? (string)$item->FromPort : null;
if ($fromPort !== null && $fromPort < 1024) {
$messages->appendMessage(new Message(
gettext(
'Superuser is disabled, port must be empty or must be 1024 or above.'
),
$item->__reference . ".FromPort"
));
$messages->appendMessage(new Message(
gettext(
'Ports in "Reverse Proxy - Domains" must be empty or must be 1024 or above.'
),
"general.DisableSuperuser"
));
}
}
}
}
// Perform the actual validation
public function performValidation($validateFullModel = false)
{
@@ -234,9 +289,13 @@ class Caddy extends BaseModel
// 4. Check for ACME Email requirement
$this->checkAcmeEmailAutoHttps($messages);
// 5. Check for TLS conflicts in Domain
$this->checkDisableTlsConflicts($messages);
// 6. Check DisableSuperuser Port conflicts
$this->checkSuperuserPorts($messages);
return $messages;
}
}
@@ -79,6 +79,7 @@
</Model>
</accesslist>
<abort type="BooleanField"/>
<DisableSuperuser type="BooleanField"/>
<GracePeriod type="IntegerField">
<Default>10</Default>
<MinimumValue>1</MinimumValue>
@@ -1,26 +1,71 @@
#!/bin/sh
#
# 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.
#
# The directories are created as root:www with rwx permissions for both,
# so the user can change in the GUI if caddy runs as root or www
# If only ports 1024 and above are used, caddy can run as www user and group.
# Define directories
CADDY_DIR="/usr/local/etc/caddy"
CADDY_CERTS_DIR="/var/db/caddy/data/caddy/certificates/temp"
CADDY_LOG_DIR="/var/log/caddy/access"
CADDY_CONF_DIR="${CADDY_DIR}/caddy.d"
# Create custom directories with appropriate permissions
mkdir -p "${CADDY_CERTS_DIR}"
chown -R root:wheel "${CADDY_CERTS_DIR}"
chmod -R 600 "${CADDY_CERTS_DIR}"
mkdir -p "${CADDY_LOG_DIR}"
chown -R root:wheel "${CADDY_LOG_DIR}"
chmod -R 750 "${CADDY_LOG_DIR}"
CADDY_CONF_DIR="/usr/local/etc/caddy"
CADDY_DATA_DIR="/var/db/caddy"
CADDY_LOG_DIR="/var/log/caddy"
CADDY_RUN_DIR="/var/run/caddy"
CADDY_CONF_CUSTOM_DIR="${CADDY_CONF_DIR}/caddy.d"
CADDY_DATA_CUSTOM_DIR="${CADDY_DATA_DIR}/data/caddy/certificates/temp"
CADDY_LOG_CUSTOM_DIR="${CADDY_LOG_DIR}/access"
mkdir -p "${CADDY_CONF_DIR}"
chown -R root:wheel "${CADDY_CONF_DIR}"
chmod -R 750 "${CADDY_CONF_DIR}"
mkdir -p "${CADDY_DATA_DIR}"
mkdir -p "${CADDY_LOG_DIR}"
mkdir -p "${CADDY_RUN_DIR}"
mkdir -p "${CADDY_CONF_CUSTOM_DIR}"
mkdir -p "${CADDY_DATA_CUSTOM_DIR}"
mkdir -p "${CADDY_LOG_CUSTOM_DIR}"
# Format and overwrite the Caddyfile
(cd "${CADDY_DIR}" && /usr/local/bin/caddy fmt --overwrite)
chown -R root:www "${CADDY_CONF_DIR}"
chown -R root:www "${CADDY_DATA_DIR}"
chown -R root:www "${CADDY_LOG_DIR}"
chown -R root:www "${CADDY_RUN_DIR}"
# Write custom certs from the OPNsense Trust Store into a directory where Caddy can read them
# Directories need execute permissions to be accessible
find "${CADDY_CONF_DIR}" -type d -exec chmod 770 {} +
find "${CADDY_DATA_DIR}" -type d -exec chmod 770 {} +
find "${CADDY_LOG_DIR}" -type d -exec chmod 770 {} +
find "${CADDY_RUN_DIR}" -type d -exec chmod 770 {} +
# Files can have read/write permissions
find "${CADDY_CONF_DIR}" -type f -exec chmod 660 {} +
find "${CADDY_DATA_DIR}" -type f -exec chmod 660 {} +
find "${CADDY_LOG_DIR}" -type f -exec chmod 660 {} +
find "${CADDY_RUN_DIR}" -type f -exec chmod 660 {} +
# Format and overwrite the Caddyfile, this makes whitespace control in jinja2 unnecessary
(cd "${CADDY_CONF_DIR}" && /usr/local/bin/caddy fmt --overwrite)
# Write custom certs from the OPNsense Trust Store to CADDY_DATA_CUSTOM_DIR
/usr/local/opnsense/scripts/OPNsense/Caddy/caddy_certs.php
@@ -28,6 +28,13 @@
{% set generalSettings = helpers.getNodeByTag('Pischem.caddy.general') %}
{# Print as comments if Caddy runs as root or www user, as information in support cases. #}
{% if generalSettings.DisableSuperuser|default("0") == "1" %}
# caddy_user=www
{% else %}
# caddy_user=root
{% endif %}
# Global Options
{
{#
@@ -43,7 +50,7 @@
{% endif %}
{% endfor %}
{% endif %}
output net unixgram//var/caddy/var/run/log {
output net unixgram//var/run/caddy/log.sock {
}
format json {
time_format rfc3339
@@ -1,8 +1,13 @@
# DO NOT EDIT THIS FILE -- OPNsense auto-generated file
{% set generalSettings = helpers.getNodeByTag('Pischem.caddy.general') %}
{% if generalSettings.enabled|default("0") == "1" %}
caddy_enable="YES"
caddy_setup="/usr/local/opnsense/scripts/OPNsense/Caddy/setup.sh"
{% if generalSettings.DisableSuperuser|default("0") == "1" %}
caddy_user=www
caddy_group=www
{% endif %}
{% else %}
caddy_enable="NO"
{% endif %}