This commit is contained in:
Mateusz Gruszczyński committed 2026-09-29 15:57:07 +02:00
1 parent 1a2a5f0faf
commit c0068d624e
13 files changed
+230 -17

No files matched your search

+14 -1
View File
@@ -69,9 +69,11 @@ dist/
This version intentionally produces an installable `.pkg`, not a full `pkg repo` catalogue. Installation/update is currently done with `pkg add -f`.
The Linux-side package builder validates the FreeBSD package payload before publishing it: payload paths must be absolute (`/usr/local/...`), no undeclared directory entries are archived, and every payload file must have an identical entry in `+MANIFEST`.
## OPNsense UI
The first version exposes only **Services -> RustProxy -> Enabled**. RustProxy runs as the unprivileged `rustproxy` user.
The OPNsense page **Services -> RustProxy** exposes enable/disable plus HTTP, HTTPS/TLS and administration listener settings. RustProxy runs as the unprivileged `rustproxy` user.
## Validation
@@ -80,3 +82,14 @@ The first version exposes only **Services -> RustProxy -> Enabled**. RustProxy r
```
See `docs/FIRST_RUN.md` for Debian setup.
## OPNsense listener settings
The OPNsense page **Services -> RustProxy** exposes the runtime listeners used by the rc.d service:
- HTTP listen: `0.0.0.0:8080`
- HTTPS/TLS listen: `0.0.0.0:8181` (toggleable)
- Admin listen: `127.0.0.1:9090`
- Allow remote admin: off by default; enable it when binding admin to a non-loopback address such as `0.0.0.0:9090`
The service runs as the unprivileged `rustproxy` user and keeps `/var/db/rustproxy` mode `0700`, as required by RustProxy. Package installation restarts `configd` so `configctl rustproxy ...` actions are immediately available even after direct `pkg add`.
+1 -1
View File
@@ -41,7 +41,7 @@ if [[ "$RUN_LINUX_TESTS" == "1" ]]; then
fi
echo "Cross-compiling RustProxy for x86_64-unknown-freebsd..."
docker run "${run_args[@]}" "$DOCKER_IMAGE" bash -lc '
docker run "${run_args[@]}" "$DOCKER_IMAGE" bash -c '
set -euo pipefail
cargo build --locked --release --target x86_64-unknown-freebsd
install -m 0555 /target/x86_64-unknown-freebsd/release/rust-proxy /out/rust-proxy
+60 -4
View File
@@ -89,7 +89,7 @@ common = {
"version": version,
"origin": "opnsense/os-rustproxy",
"comment": "RustProxy reverse proxy service",
"desc": "RustProxy packaged as an OPNsense plugin. The initial UI exposes Enabled/Disabled only.",
"desc": "RustProxy packaged as an OPNsense plugin with configurable HTTP, HTTPS/TLS and administration listeners.",
"maintainer": "local@localhost",
"www": "https://git.linuxiarz.pl/gru/RustProxy",
"prefix": "/usr/local",
@@ -120,11 +120,67 @@ manifest["scripts"] = {
PY
archive="$work/os-rustproxy.tar"
rm -f "$archive" "$package_file"
payload_list="$work/payload.list"
rm -f "$archive" "$package_file" "$payload_list"
# FreeBSD pkg expects payload paths to match +MANIFEST exactly. The manifest
# uses absolute paths (/usr/local/...), so the tar members must also be
# absolute. Do not archive directory entries: pkg creates parent directories
# while extracting files, and an undeclared directory entry makes pkg add fail.
(
cd "$stage"
find . \( -type f -o -type l \) -print \
| LC_ALL=C sort \
| sed 's#^\./##' > "$payload_list"
)
[[ -s "$payload_list" ]] || { echo "Package payload is empty" >&2; exit 1; }
tar --format=ustar --owner=0 --group=0 --numeric-owner \
-cf "$archive" -C "$meta" +COMPACT_MANIFEST +MANIFEST
tar --format=ustar --owner=0 --group=0 --numeric-owner \
-rf "$archive" -C "$stage" usr
tar -P --format=ustar --owner=0 --group=0 --numeric-owner --no-recursion \
--transform='s,^,/,S' \
-rf "$archive" -C "$stage" -T "$payload_list"
# Validate the raw package archive before compression. This catches the exact
# class of errors that pkg(8) reports as "not specified in the manifest".
python3 - "$archive" "$meta/+MANIFEST" <<'PYVERIFY'
import json
from pathlib import Path
import sys
import tarfile
archive = Path(sys.argv[1])
manifest = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
expected = set(manifest.get("files", {}))
with tarfile.open(archive, "r:") as tf:
members = tf.getmembers()
if [m.name for m in members[:2]] != ["+COMPACT_MANIFEST", "+MANIFEST"]:
raise SystemExit("Package metadata must be the first two tar members")
payload = members[2:]
directories = [m.name for m in payload if m.isdir()]
if directories:
raise SystemExit(f"Directory entries are not allowed in payload: {directories[:5]}")
actual = {m.name for m in payload if m.isfile() or m.issym() or m.islnk()}
relative = sorted(name for name in actual if not name.startswith("/"))
if relative:
raise SystemExit(f"Payload contains non-absolute paths: {relative[:5]}")
missing = sorted(expected - actual)
extra = sorted(actual - expected)
if missing or extra:
raise SystemExit(
"Package payload does not match +MANIFEST; "
f"missing={missing[:5]} extra={extra[:5]}"
)
print(f"Verified package payload: {len(actual)} files, manifest paths match")
PYVERIFY
zstd -q -f -19 -T0 "$archive" -o "$package_file"
# Convenience stable path for direct deployment.
+8 -3
View File
@@ -5,7 +5,7 @@ FROM ubuntu:24.04 AS sysroot
ARG FREEBSD_RELEASE=15.1-RELEASE
ARG FREEBSD_MIRROR=https://download.freebsd.org/releases/amd64/amd64
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates wget xz-utils \
ca-certificates wget xz-utils \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /tmp/freebsd
RUN set -eux; \
@@ -21,11 +21,16 @@ RUN set -eux; \
# RustProxy requires rust-version = 1.88.
FROM rust:1.88-slim-bookworm AS builder
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates clang lld llvm file git perl pkg-config python3 \
ca-certificates clang lld llvm file git perl pkg-config python3 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=sysroot /freebsd-sysroot /opt/freebsd-sysroot
RUN rustup target add x86_64-unknown-freebsd
ENV RUSTUP_TOOLCHAIN=1.88.0
RUN rustup target add \
--toolchain 1.88.0 \
x86_64-unknown-freebsd
ENV CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_LINKER=clang \
CC_x86_64_unknown_freebsd=clang \
+3
View File
@@ -4,8 +4,11 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CONFIG_FILE="${RUSTPROXY_BUILDER_CONFIG:-/etc/rustproxy-opnsense-builder.env}"
if [[ -r "$CONFIG_FILE" ]]; then
# Export all builder configuration to child build scripts.
set -a
# shellcheck disable=SC1090
source "$CONFIG_FILE"
set +a
fi
: "${BUILD_MODE:=docker-freebsd}"
+18
View File
@@ -87,6 +87,8 @@ file dist/docker-build/rust-proxy
zstd -dc dist/os-rustproxy-latest.pkg | tar -tf - | head -50
```
The package payload should contain files such as `/usr/local/sbin/rust-proxy` and must not contain directory entries such as `/usr/` or `/usr/local/`. The build fails automatically when payload paths differ from `+MANIFEST`.
The binary should identify as an x86-64 FreeBSD ELF executable, not a Linux executable.
## 7. Install on a test OPNsense
@@ -136,3 +138,19 @@ AUTO_DEPLOY_SSH_KEY=/root/.ssh/opnsense_test
```
Leave both empty for manual deployment.
## OPNsense runtime settings
After installation open **Services -> RustProxy**. Defaults are:
- HTTP listen: `0.0.0.0:8080`
- HTTPS/TLS enabled: yes
- HTTPS/TLS listen: `0.0.0.0:8181`
- Admin listen: `127.0.0.1:9090`
- Allow remote admin: no
If you want the RustProxy administration UI reachable from another host, set the admin listener to an address such as `0.0.0.0:9090` and enable **Allow remote admin**. Apply restarts/reloads the service with the corresponding RustProxy CLI flags.
Direct `pkg add` is supported: the package post-install script restarts OPNsense `configd`, flushes the plugin cache and renders `/usr/local/etc/rustproxy/opnsense.conf` so `configctl rustproxy status/start/stop` is available immediately.
RustProxy requires its data directory to be private. The plugin enforces owner `rustproxy:rustproxy` and mode `0700` on `/var/db/rustproxy` before every start.
+11 -1
View File
@@ -8,6 +8,16 @@ if ! /usr/sbin/pw usershow rustproxy >/dev/null 2>&1; then
/usr/sbin/pw useradd rustproxy -g rustproxy -c "RustProxy daemon" -d /var/db/rustproxy -s /usr/sbin/nologin
fi
/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/db/rustproxy
/usr/bin/install -d -o rustproxy -g rustproxy -m 0700 /var/db/rustproxy
/usr/sbin/chown rustproxy:rustproxy /var/db/rustproxy
/bin/chmod 0700 /var/db/rustproxy
/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/log/rustproxy
/usr/bin/install -d -o root -g wheel -m 0755 /usr/local/etc/rustproxy
# configd discovers actions only on startup/reload. A direct `pkg add` must
# therefore refresh configd before the MVC service controller can call them.
/usr/sbin/service configd restart >/dev/null 2>&1 || true
/usr/local/sbin/pluginctl -cq cache_flush >/dev/null 2>&1 || true
/usr/local/sbin/configctl template reload OPNsense/RustProxy >/dev/null 2>&1 || true
exit 0
+3 -3
View File
@@ -1,4 +1,4 @@
RustProxy packaged as an OPNsense plugin.
RustProxy packaged as an OPNsense service plugin.
The initial OPNsense UI intentionally exposes only one setting: Enabled.
Runtime proxy configuration remains managed by RustProxy itself.
The OPNsense UI exposes service enable/disable, HTTP and HTTPS/TLS listener
addresses, the administration listener and the remote-admin opt-in.
@@ -19,18 +19,46 @@ load_rc_config "$name"
: ${rustproxy_log_file:="${rustproxy_log_dir}/rustproxy.log"}
: ${rustproxy_command:="/usr/local/sbin/rust-proxy"}
: ${rustproxy_restart_delay:="5"}
: ${rustproxy_proxy_listen:="0.0.0.0:8080"}
: ${rustproxy_tls_enabled:="1"}
: ${rustproxy_tls_listen:="0.0.0.0:8181"}
: ${rustproxy_admin_listen:="127.0.0.1:9090"}
: ${rustproxy_allow_remote_admin:="0"}
enabled="0"
config_file="/usr/local/etc/rustproxy/opnsense.conf"
if [ -r "$config_file" ]; then
. "$config_file"
fi
if [ "$enabled" = "1" ]; then
rustproxy_enable="YES"
else
rustproxy_enable="NO"
fi
rustproxy_runtime_args="--data-dir ${rustproxy_data_dir} --admin-listen ${rustproxy_admin_listen} --update-check-interval-secs 0 serve --proxy-listen ${rustproxy_proxy_listen}"
if [ "$rustproxy_tls_enabled" = "1" ]; then
rustproxy_runtime_args="${rustproxy_runtime_args} --tls-listen ${rustproxy_tls_listen}"
fi
if [ "$rustproxy_allow_remote_admin" = "1" ]; then
rustproxy_runtime_args="${rustproxy_runtime_args} --allow-remote-admin"
fi
pidfile="/var/run/rustproxy-daemon.pid"
child_pidfile="/var/run/rustproxy.pid"
command="/usr/sbin/daemon"
command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} -u ${rustproxy_user} -o ${rustproxy_log_file} ${rustproxy_command} --data-dir ${rustproxy_data_dir} --update-check-interval-secs 0 serve"
command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} -u ${rustproxy_user} -o ${rustproxy_log_file} ${rustproxy_command} ${rustproxy_runtime_args}"
required_files="${rustproxy_command}"
start_precmd="rustproxy_prestart"
stop_postcmd="rustproxy_poststop"
rustproxy_prestart()
{
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_data_dir}"
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0700 "${rustproxy_data_dir}"
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_data_dir}"
/bin/chmod 0700 "${rustproxy_data_dir}"
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_log_dir}"
/usr/bin/touch "${rustproxy_log_file}"
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_log_file}"
@@ -1,8 +1,53 @@
<form>
<field>
<label>General</label>
<type>header</type>
<static>true</static>
</field>
<field>
<id>rustproxy.general.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Start RustProxy and keep it enabled after OPNsense restarts.</help>
</field>
<field>
<label>Proxy listeners</label>
<type>header</type>
<static>true</static>
</field>
<field>
<id>rustproxy.general.proxyListen</id>
<label>HTTP listen</label>
<type>text</type>
<help>HTTP listener passed as --proxy-listen. Default: 0.0.0.0:8080.</help>
</field>
<field>
<id>rustproxy.general.tlsEnabled</id>
<label>Enable HTTPS/TLS listener</label>
<type>checkbox</type>
<help>When enabled, pass --tls-listen to RustProxy.</help>
</field>
<field>
<id>rustproxy.general.tlsListen</id>
<label>HTTPS/TLS listen</label>
<type>text</type>
<help>HTTPS/TLS listener passed as --tls-listen. Default: 0.0.0.0:8181.</help>
</field>
<field>
<label>Administration</label>
<type>header</type>
<static>true</static>
</field>
<field>
<id>rustproxy.general.adminListen</id>
<label>Admin listen</label>
<type>text</type>
<help>RustProxy administration listener passed as --admin-listen. Default: 127.0.0.1:9090.</help>
</field>
<field>
<id>rustproxy.general.allowRemoteAdmin</id>
<label>Allow remote admin</label>
<type>checkbox</type>
<help>Required when Admin listen is not a loopback address, for example 0.0.0.0:9090. This passes --allow-remote-admin.</help>
</field>
</form>
@@ -1,13 +1,36 @@
<model>
<mount>//OPNsense/rustproxy</mount>
<version>1.0.0</version>
<version>1.1.0</version>
<description>RustProxy service settings</description>
<items>
<general>
<enabled type="BooleanField">
<default>0</default>
<Default>0</Default>
<Required>Y</Required>
</enabled>
<proxyListen type="IPPortField">
<Default>0.0.0.0:8080</Default>
<Required>Y</Required>
<ValidationMessage>Enter a valid HTTP listen address and port, for example 0.0.0.0:8080.</ValidationMessage>
</proxyListen>
<tlsEnabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</tlsEnabled>
<tlsListen type="IPPortField">
<Default>0.0.0.0:8181</Default>
<Required>Y</Required>
<ValidationMessage>Enter a valid HTTPS/TLS listen address and port, for example 0.0.0.0:8181.</ValidationMessage>
</tlsListen>
<adminListen type="IPPortField">
<Default>127.0.0.1:9090</Default>
<Required>Y</Required>
<ValidationMessage>Enter a valid administration listen address and port, for example 127.0.0.1:9090.</ValidationMessage>
</adminListen>
<allowRemoteAdmin type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</allowRemoteAdmin>
</general>
</items>
</model>
@@ -3,33 +3,40 @@ command:/usr/local/opnsense/scripts/rustproxy/service.sh start
parameters:
type:script
message:starting RustProxy
allowed_groups:wheel,wwwonly
[stop]
command:/usr/local/opnsense/scripts/rustproxy/service.sh stop
parameters:
type:script
message:stopping RustProxy
allowed_groups:wheel,wwwonly
[restart]
command:/usr/local/opnsense/scripts/rustproxy/service.sh restart
parameters:
type:script
message:restarting RustProxy
allowed_groups:wheel,wwwonly
[reload]
command:/usr/local/opnsense/scripts/rustproxy/service.sh restart
parameters:
type:script
message:reloading RustProxy
allowed_groups:wheel,wwwonly
[status]
command:/usr/local/opnsense/scripts/rustproxy/service.sh status
parameters:
type:script_output
errors:no
message:RustProxy status
allowed_groups:wheel,wwwonly
[boot]
command:/usr/local/opnsense/scripts/rustproxy/service.sh boot
parameters:
type:script
message:starting RustProxy at boot
allowed_groups:wheel,wwwonly
@@ -1 +1,6 @@
enabled="{% if not helpers.empty('OPNsense.rustproxy.general.enabled') %}1{% else %}0{% endif %}"
rustproxy_proxy_listen="{% if not helpers.empty('OPNsense.rustproxy.general.proxyListen') %}{{ OPNsense.rustproxy.general.proxyListen }}{% else %}0.0.0.0:8080{% endif %}"
rustproxy_tls_enabled="{% if not helpers.empty('OPNsense.rustproxy.general.tlsEnabled') %}1{% else %}0{% endif %}"
rustproxy_tls_listen="{% if not helpers.empty('OPNsense.rustproxy.general.tlsListen') %}{{ OPNsense.rustproxy.general.tlsListen }}{% else %}0.0.0.0:8181{% endif %}"
rustproxy_admin_listen="{% if not helpers.empty('OPNsense.rustproxy.general.adminListen') %}{{ OPNsense.rustproxy.general.adminListen }}{% else %}127.0.0.1:9090{% endif %}"
rustproxy_allow_remote_admin="{% if not helpers.empty('OPNsense.rustproxy.general.allowRemoteAdmin') %}1{% else %}0{% endif %}"