Initial server source import

This commit is contained in:
sashatrask
2026-09-30 20:30:56 +03:00
commit 170dd941b9
498 changed files with 261563 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
{
admin unix//run/caddy-admin.sock
auto_https disable_redirects
skip_install_trust
}
(admin_security) {
header {
Cache-Control "no-store"
Pragma "no-cache"
Referrer-Policy "same-origin"
Content-Security-Policy "default-src 'none'; style-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'"
X-Content-Type-Options "nosniff"
}
}
(admin_gate) {
import {$TRUF_ADMIN_DENYLIST_FILE:/etc/caddy/denylist/admin-denylist.caddy}
basic_auth bcrypt "truf-admin" {
{$TRUF_ADMIN_USER} {$TRUF_ADMIN_PASSWORD_HASH}
}
}
{$TRUF_EDGE_HOST} {
import {$TRUF_EDGE_TLS_INCLUDE:/etc/caddy/tls/automatic.caddy}
import admin_security
header Strict-Transport-Security "max-age=63072000; includeSubDomains"
log routine_access {
output discard
}
log admin_auth_failures {
no_hostname
output file {$TRUF_ADMIN_AUTH_LOG_FILE:/var/log/caddy/admin-auth-failures.json} {
roll_size 8MiB
roll_keep 10
roll_keep_for 240h
}
format filter {
request delete
bytes_read delete
user_id delete
duration delete
size delete
resp_headers delete
wrap json
}
}
@worker path /api/v1/worker/*
handle @worker {
reverse_proxy 127.0.0.1:8766 {
header_up -X-Truf-Admin-Edge
header_up -X-Truf-Admin-Operator
header_up -Forwarded
header_up -X-Real-IP
}
}
@admin_root path /{$TRUF_ADMIN_PREFIX}
handle @admin_root {
route {
import admin_security
import admin_gate
redir * /{$TRUF_ADMIN_PREFIX}/ 308
}
}
@admin path /{$TRUF_ADMIN_PREFIX}/*
handle @admin {
route {
import admin_security
request_header -X-Truf-Admin-Edge
request_header -X-Truf-Admin-Operator
import admin_gate
uri strip_prefix /{$TRUF_ADMIN_PREFIX}
uri path_regexp ^ /admin-internal
reverse_proxy 127.0.0.1:8766 {
header_up -Authorization
header_up X-Truf-Admin-Edge {$TRUF_ADMIN_EDGE_MARKER}
header_up X-Truf-Admin-Operator {http.auth.user.id}
header_up -Forwarded
header_up -X-Real-IP
header_down -Strict-Transport-Security
}
}
}
handle {
respond "" 404
}
handle_errors {
@bad_admin_credentials {
path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
header Authorization *
expression {err.status_code} == 401
}
handle @bad_admin_credentials {
route {
import admin_security
log_name admin_auth_failures
log_append event admin_auth_failure
log_append remote_ip {http.request.remote.host}
header WWW-Authenticate "Basic realm=\"truf-admin\""
respond "" 401
}
}
@admin_unauthorized {
path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
expression {err.status_code} == 401
}
handle @admin_unauthorized {
route {
import admin_security
header WWW-Authenticate "Basic realm=\"truf-admin\""
respond "" 401
}
}
@admin_error path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
handle @admin_error {
import admin_security
respond "" {err.status_code}
}
handle {
respond "" {err.status_code}
}
}
}
+147
View File
@@ -0,0 +1,147 @@
{
admin unix//run/caddy-admin.sock
auto_https off
skip_install_trust
servers {
trusted_proxies static 127.0.0.1/32 ::1/128
trusted_proxies_strict
client_ip_headers X-Forwarded-For
}
}
(admin_security) {
header {
Cache-Control "no-store"
Pragma "no-cache"
Referrer-Policy "same-origin"
Content-Security-Policy "default-src 'none'; style-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'"
X-Content-Type-Options "nosniff"
}
}
(admin_gate) {
import {$TRUF_ADMIN_DENYLIST_FILE:/etc/caddy/denylist/admin-denylist.caddy}
basic_auth bcrypt "truf-admin" {
{$TRUF_ADMIN_USER} {$TRUF_ADMIN_PASSWORD_HASH}
}
}
http://:18766 {
bind 127.0.0.1
log routine_access {
output discard
}
log admin_auth_failures {
no_hostname
output file {$TRUF_ADMIN_AUTH_LOG_FILE:/var/log/caddy/admin-auth-failures.json} {
roll_size 8MiB
roll_keep 10
roll_keep_for 240h
}
format filter {
request delete
bytes_read delete
user_id delete
duration delete
size delete
resp_headers delete
wrap json
}
}
route {
@invalid_ingress not header X-Truf-Shared-Ingress {$TRUF_SHARED_INGRESS_MARKER}
respond @invalid_ingress "" 403
request_header -X-Truf-Shared-Ingress
import admin_security
header Strict-Transport-Security "max-age=63072000; includeSubDomains"
@worker path /api/v1/worker/*
handle @worker {
reverse_proxy 127.0.0.1:8766 {
header_up -X-Truf-Admin-Edge
header_up -X-Truf-Admin-Operator
header_up -X-Truf-Shared-Ingress
header_up -Forwarded
header_up -X-Real-IP
}
}
@admin_root path /{$TRUF_ADMIN_PREFIX}
handle @admin_root {
route {
import admin_security
import admin_gate
redir * /{$TRUF_ADMIN_PREFIX}/ 308
}
}
@admin path /{$TRUF_ADMIN_PREFIX}/*
handle @admin {
route {
import admin_security
request_header -X-Truf-Admin-Edge
request_header -X-Truf-Admin-Operator
import admin_gate
uri strip_prefix /{$TRUF_ADMIN_PREFIX}
uri path_regexp ^ /admin-internal
reverse_proxy 127.0.0.1:8766 {
header_up -Authorization
header_up X-Truf-Admin-Edge {$TRUF_ADMIN_EDGE_MARKER}
header_up X-Truf-Admin-Operator {http.auth.user.id}
header_up -X-Truf-Shared-Ingress
header_up -Forwarded
header_up -X-Real-IP
header_down -Strict-Transport-Security
}
}
}
handle {
respond "" 404
}
}
handle_errors {
@bad_admin_credentials {
path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
header Authorization *
expression {err.status_code} == 401
}
handle @bad_admin_credentials {
route {
import admin_security
log_name admin_auth_failures
log_append event admin_auth_failure
log_append remote_ip {http.request.client_ip}
header WWW-Authenticate "Basic realm=\"truf-admin\""
respond "" 401
}
}
@admin_unauthorized {
path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
expression {err.status_code} == 401
}
handle @admin_unauthorized {
route {
import admin_security
header WWW-Authenticate "Basic realm=\"truf-admin\""
respond "" 401
}
}
@admin_error path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
handle @admin_error {
import admin_security
respond "" {err.status_code}
}
handle {
respond "" {err.status_code}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
FROM caddy:2.10.2-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d AS edge
USER 0:0
RUN setcap -r /usr/bin/caddy \
&& install -d -o 10001 -g 10001 -m 0700 /data /config /var/log/caddy /etc/caddy/denylist /etc/caddy/tls
COPY --chown=0:0 --chmod=0444 deploy/edge/Caddyfile /etc/caddy/Caddyfile
COPY --chown=0:0 --chmod=0444 deploy/edge/Caddyfile.shared-host /etc/caddy/Caddyfile.shared-host
COPY --chown=0:0 --chmod=0444 deploy/edge/admin-denylist.caddy /etc/caddy/denylist/admin-denylist.caddy
COPY --chown=0:0 --chmod=0444 deploy/edge/automatic-tls.caddy /etc/caddy/tls/automatic.caddy
COPY --chown=0:0 --chmod=0555 deploy/edge/entrypoint.sh /usr/local/bin/truf-edge-entrypoint
USER 10001:10001
ENTRYPOINT ["/usr/local/bin/truf-edge-entrypoint"]
CMD []
FROM edge AS edge-e2e
USER 0:0
RUN chmod 0644 /etc/caddy/Caddyfile \
&& sed -i 's#^[[:space:]]*admin unix//run/caddy-admin.sock$#\tadmin 127.0.0.1:2019#' /etc/caddy/Caddyfile \
&& chmod 0444 /etc/caddy/Caddyfile \
&& grep -Fx ' admin 127.0.0.1:2019' /etc/caddy/Caddyfile >/dev/null
USER 10001:10001
+253
View File
@@ -0,0 +1,253 @@
# Production edge deployment
This opt-in deployment keeps PostgreSQL, supervisor control, the standalone dashboard,
the Worker API, and its typed admin backend on the runtime container's loopback. The
root-owned deployment profile selects one of two exact Caddy topologies. Both keep the
private Caddy admin API on an unpublished Unix socket and preserve the same Worker API,
admin authentication, operator attribution, denylist, and header contract.
## Deployment profiles
If `/etc/truf/deployment-profile` is absent, `standalone-edge-v1` is selected. The
standalone profile publishes runtime TCP 443 and gives the managed edge only
`NET_BIND_SERVICE`.
For a host whose existing root-owned Caddy must remain the sole owner of ports 80/443,
install the shared profile before running the host-agent installer:
```sh
printf '%s\n' shared-host-edge-v1 | sudo install -m 0444 -o root -g root /dev/stdin /etc/truf/deployment-profile
```
`shared-host-edge-v1` runs the runtime in the host network namespace with no Docker
published ports. The managed Truf edge shares that namespace, has no capabilities, and
binds plain HTTP only at `127.0.0.1:18766`. The existing host Caddy imports the fixed
route-only `deploy/edge/host-caddy-shared.caddy` snippet inside the reviewed public site.
Install that import before any catch-all handler. It handles only `/api/v1/worker/*` and
the exact random admin prefix; it does not define a listener, TLS policy, global option,
or route for another application. The host agent never restarts or reconfigures host
Caddy or X-UI.
Profile changes are maintenance operations: stop the host agent first, require no active
apply or failed hold, install the exact root-owned mode-0444 value, validate the selected
Compose projection and host-Caddy configuration, then restart the agent. Never expose
profile selection through the admin or host-agent request.
### Choosing a topology
Use `standalone-edge-v1` on a dedicated host where the managed edge can own public TCP
443. The request path is:
```text
Internet -> managed Caddy :443 -> private runtime :8766
```
Use `shared-host-edge-v1` only when an existing root-owned Caddy must remain the sole
owner of public ports and TLS. The request path is:
```text
Internet -> host Caddy :443 -> 127.0.0.1:18766 -> managed Caddy -> private runtime :8766
```
Shared-host mode adds a one-time integration boundary, not a second public edge. The
operator installs the fixed route snippet, places its import before every catch-all,
supplies the independent ingress marker, and validates the complete host Caddy
configuration. After that bootstrap, runtime restart and document apply use the same
host-agent lifecycle as standalone mode. The host agent never owns the host Caddy
configuration or service lifecycle.
These are the only supported production topologies. Nginx, Traefik, an arbitrary Caddy
layout, or an ad-hoc Compose override is not equivalent to either profile. Add and test a
new exact deployment profile instead of translating private headers approximately. The
host agent validates the selected fixed Compose projection and rejects metadata drift.
The shared-host projection currently carries the constrained-host runtime limits declared
in `compose.shared-host.yaml`. A materially different CPU or memory envelope also requires
a reviewed profile change; do not hide it in an unvalidated local override.
### End-to-end host bootstrap
The repository provides fixed deployment components, not a universal VPS installer,
Ansible role, public image registry, or infrastructure module. Bootstrap a new host from
one reviewed release checkout as follows:
1. Install the reviewed Linux, Docker Engine and Compose plugin, systemd, Python 3, and
fail2ban prerequisites; provision DNS and the selected TLS ownership boundary.
2. Install the release checkout root-owned at `/opt/truf` and choose exactly one deployment
profile before installing the host agent.
3. In shared-host mode, install the fixed host-Caddy import, validate the complete host
configuration, and prove an unavailable loopback edge cannot fall through to another
application.
4. Create the protected edge directories, denylist state, and mode-0600 edge environment
described below. Generate independent admin, edge, and shared-ingress values rather
than copying values from another host.
5. Install and validate the fixed host agent. Its first install seeds an absent active
config from `app/config.linux.yaml` and an absent secrets document as an empty mapping;
repeat installation never replaces active documents.
6. Install every trusted worker-package manifest referenced by the runtime config beneath
`/etc/truf/worker-packages` with the exact ownership and mode described below.
7. Review the private active config and secrets, then build `runtime` and `edge` from the
same checkout with the exact base and selected profile Compose files.
8. Start the stack, explicitly enable Worker API and admin only after their private
configuration is complete, and verify PostgreSQL, runtime, edge, HTTPS, admin, Worker
API, host-agent, and unrelated host applications.
Initial host bootstrap is therefore intentionally more manual than later operation.
Normal config apply, restart, rollback, status, and audit are performed through the typed
control plane and fixed host agent after this trust boundary is established.
## Host agent and fixed runtime paths
Install the root-owned checkout at `/opt/truf`. Before invoking the host-agent installer,
prepare the edge state below and create the complete protected environment file that its
fixed combined-Compose validation consumes.
UID/GID 10001 is the numeric edge identity. The denylist directory is mounted, rather than
its file, so atomic replacement remains visible in the container.
```sh
sudo install -d -o 10001 -g 10001 -m 0700 /var/log/truf-edge
sudo install -d -o root -g 10001 -m 2750 /etc/truf-edge/denylist
sudo install -m 0640 -o root -g 10001 deploy/edge/admin-denylist.caddy /etc/truf-edge/denylist/admin-denylist.caddy
sudo install -d -o root -g root -m 0700 /var/lib/truf-edge
sudo install -m 0750 -o root -g root deploy/fail2ban/truf_caddy_admin_denylist.py /usr/local/sbin/truf-caddy-admin-denylist
```
Create `/etc/truf-edge/edge.env` as root with mode 0600. Generate a new admin segment
with `openssl rand -hex 32`. It must be exactly 64 lowercase hex characters (256 random
bits). Generate the bcrypt value interactively with the pinned edge image's
`caddy hash-password` command; never put the plaintext password in a command, file, or
Compose variable.
```dotenv
TRUF_EDGE_HOST=edge.example.net
TRUF_ADMIN_PREFIX=replace_with_64_lowercase_hex_characters
TRUF_ADMIN_USER=operator
TRUF_ADMIN_PASSWORD_HASH='$2a$14$replace_with_a_real_caddy_bcrypt_hash'
TRUF_ADMIN_EDGE_MARKER=replace_with_a_second_independent_64_character_hex_secret
TRUF_SHARED_INGRESS_MARKER=replace_with_a_third_independent_64_character_hex_secret
TRUF_EDGE_AUTH_LOG_DIR=/var/log/truf-edge
TRUF_EDGE_DENYLIST_DIR=/etc/truf-edge/denylist
```
`TRUF_SHARED_INGRESS_MARKER` is required only by `shared-host-edge-v1`. Host Caddy strips
any inbound transit/private headers, injects this marker and its observed client address,
and proxies to loopback. The managed edge rejects a missing marker before trusting that
address and removes the marker before proxying to the application.
Now install and validate the fixed host agent. The installer creates the fixed candidate,
result, PostgreSQL socket, and active-document paths and enables
`/run/truf/host-agent.sock`; Compose refuses to create missing bind sources.
```sh
sudo /usr/bin/python3 -I -S -B /opt/truf/deploy/host-agent/truf_host_agent_install.py install
sudo /usr/bin/python3 -I -S -B /opt/truf/deploy/host-agent/truf_host_agent_install.py validate
```
The active `/etc/truf/runtime/config.yaml` and `secrets.yaml` are UID/GID 10001 mode 0600
documents and are never overwritten by repeat installation. Any package manifest referenced
by the config must be installed beneath `/etc/truf/worker-packages` as a root-owned,
root:root mode 0644 regular file before validation. The runtime maps that immutable authority
read-only at `/data/worker-packages`; do not place manifests in the private active-document
directory.
Automatic TLS remains the default. A deployment that must use operator-provided
certificates can mount a root-owned, non-link `*.caddy` file under `/etc/caddy/tls` and
set `TRUF_EDGE_TLS_INCLUDE` to that absolute container path in a reviewed Compose
override. The include should contain only the site's `tls CERT KEY` directive. Never use
the repository's localhost test certificate or key in a deployment.
The normal `compose.yaml` remains private and unchanged. Confirm the host-agent socket is
active and rerun installer validation immediately before starting production edge. Always
supply the base file, the exact selected profile file, and the protected environment file.
For standalone:
```sh
docker compose --env-file /etc/truf-edge/edge.env -f compose.yaml -f compose.edge.yaml build runtime edge
docker compose --env-file /etc/truf-edge/edge.env -f compose.yaml -f compose.edge.yaml up -d
```
For shared host:
```sh
docker compose --env-file /etc/truf-edge/edge.env -f compose.yaml -f compose.shared-host.yaml build runtime edge
docker compose --env-file /etc/truf-edge/edge.env -f compose.yaml -f compose.shared-host.yaml up -d
```
Before starting shared host, validate the complete existing host Caddy configuration with
the snippet import in place. A matching request must fail at that Truf route if the
loopback edge is unavailable; it must never fall through to X-UI or another upstream.
Provisioning creates the private `/data/managed-files` namespace in the named data volume.
Each configured writable root must be a reviewed immediate child such as
`/data/managed-files/exports`, created with UID/GID 10001 and mode 0700 while the runtime is
stopped. Arbitrary host bind paths are not managed-file roots.
Worker admission remains disabled by `app/config.linux.yaml`. Configure the private
runtime config's worker sources, compatibility profiles, and hashed device credentials
before explicitly enabling `supervisor.worker_api.enabled`. The edge does not enable it.
The typed admin backend is disabled independently under `supervisor.worker_api.admin`.
Set its exact `origin` to `https://TRUF_EDGE_HOST`, set `edge_marker` to the same independent
256-bit value as `TRUF_ADMIN_EDGE_MARKER`, then explicitly enable it. Both authenticated
surfaces share the private runtime loopback port 8766. Caddy strips any inbound
`X-Truf-Admin-Edge` and `X-Truf-Admin-Operator`, sets the configured marker and the
authenticated Basic-auth username only after authentication, and rewrites the public
random prefix to the private `/admin-internal` backend path. The backend accepts the
operator identity only together with the private marker.
Worker API requests receive neither private admin header.
Build and client bootstrap instructions for Windows and Linux remote workers are in
`docs/remote-worker-operations.md`. Worker executables and images must be produced from a
reviewed release checkout; operators must not assemble Python, Git, TruffleHog, detector
policy, or dependencies manually on each worker.
## Fail2ban
Install the host files under their conventional names and enable fail2ban plus the expiry
timer. The jail counts only redacted `admin_auth_failure` JSON records. An initial Basic
challenge without credentials, Worker API authentication failures, and unrelated 404s do
not enter that log. The action changes only the matcher imported inside the secret admin
route; it does not create firewall rules and therefore does not block workers sharing an IP.
```sh
sudo install -m 0644 deploy/fail2ban/filter.d-truf-admin-auth.conf /etc/fail2ban/filter.d/truf-admin-auth.conf
sudo install -m 0644 deploy/fail2ban/jail.d-truf-admin-auth.local /etc/fail2ban/jail.d/truf-admin-auth.local
sudo install -m 0644 deploy/fail2ban/action.d-truf-caddy-admin-denylist.conf /etc/fail2ban/action.d/truf-caddy-admin-denylist.conf
sudo install -m 0644 deploy/fail2ban/fail2ban.d-truf-persistence.local /etc/fail2ban/fail2ban.d/truf-persistence.local
sudo install -m 0644 deploy/systemd/truf-caddy-admin-denylist-expire.service /etc/systemd/system/
sudo install -m 0644 deploy/systemd/truf-caddy-admin-denylist-expire.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now fail2ban truf-caddy-admin-denylist-expire.timer
sudo fail2ban-client status truf-admin-auth
```
Fail2ban persists jail state in `/var/lib/fail2ban/fail2ban.sqlite3`. The updater persists
canonical IPs and expiry timestamps in private
`/var/lib/truf-edge/admin-denylist.json`. It validates the complete Caddyfile in the running
edge container, reloads it through the private admin endpoint, and restores/reloads the
previous state if a command fails. Reload uses Caddy's private `/run/caddy-admin.sock` inside the edge
container. The socket is not mounted or published. Shared mode renders denylist matchers
against the marker-authenticated client address; standalone mode uses the direct peer.
## SSH recovery
Use fail2ban's normal unban first so its database and Caddy agree:
```sh
sudo fail2ban-client set truf-admin-auth unbanip 203.0.113.10
sudo /usr/local/sbin/truf-caddy-admin-denylist status
sudo /usr/local/sbin/truf-caddy-admin-denylist expire
```
If fail2ban is unavailable, run the updater's explicit unban over SSH:
```sh
sudo /usr/local/sbin/truf-caddy-admin-denylist unban 203.0.113.10
```
For recovery from a damaged generated snippet, stop the expiry timer and fail2ban, restore
`deploy/edge/admin-denylist.caddy` to `/etc/truf-edge/denylist/admin-denylist.caddy`, then
run Caddy validation and reload through the private Unix admin socket only after validation
succeeds. Reconcile each
remaining address with the updater before re-enabling the services. Do not use a global
firewall ban as a shortcut.
+1
View File
@@ -0,0 +1 @@
# Managed by truf-caddy-admin-denylist. Admin-route import only.
+1
View File
@@ -0,0 +1 @@
# Empty by design: Caddy's automatic TLS remains the production default.
+71
View File
@@ -0,0 +1,71 @@
#!/bin/sh
set -eu
fail() {
echo "edge configuration rejected: $1" >&2
exit 64
}
host=${TRUF_EDGE_HOST:-}
prefix=${TRUF_ADMIN_PREFIX:-}
user=${TRUF_ADMIN_USER:-}
password_hash=${TRUF_ADMIN_PASSWORD_HASH:-}
edge_marker=${TRUF_ADMIN_EDGE_MARKER:-}
edge_mode=${TRUF_EDGE_MODE:-standalone-edge-v1}
ingress_marker=${TRUF_SHARED_INGRESS_MARKER:-}
tls_include=${TRUF_EDGE_TLS_INCLUDE:-}
case "$edge_mode" in
standalone-edge-v1) caddyfile=/etc/caddy/Caddyfile ;;
shared-host-edge-v1)
caddyfile=/etc/caddy/Caddyfile.shared-host
[ "${#ingress_marker}" -eq 64 ] || fail "TRUF_SHARED_INGRESS_MARKER must encode 256 random bits"
printf '%s' "$ingress_marker" | grep -Eq '^[0-9a-f]{64}$' \
|| fail "TRUF_SHARED_INGRESS_MARKER must encode 256 random bits"
;;
*) fail "TRUF_EDGE_MODE is unsupported" ;;
esac
if [ "$host" = localhost ]; then
[ -n "$tls_include" ] || fail "localhost requires an explicit static TLS include"
else
case "$host" in
''|*://*|*/*|*:*|.*|*..*|*.) fail "TRUF_EDGE_HOST must be one DNS hostname" ;;
esac
printf '%s' "$host" | awk -F. '
length($0) > 253 || NF < 2 { exit 1 }
{ for (i = 1; i <= NF; i++) if (length($i) > 63 || $i !~ /^[A-Za-z0-9-]+$/ || $i ~ /^-/ || $i ~ /-$/) exit 1 }
' \
|| fail "TRUF_EDGE_HOST must be one DNS hostname"
fi
if [ -n "$tls_include" ]; then
case "$tls_include" in
/etc/caddy/tls/*.caddy) ;;
*) fail "TRUF_EDGE_TLS_INCLUDE must be an absolute Caddy TLS include" ;;
esac
[ -f "$tls_include" ] && [ ! -L "$tls_include" ] \
|| fail "TRUF_EDGE_TLS_INCLUDE must be a regular non-link file"
fi
[ "${#prefix}" -eq 64 ] || fail "TRUF_ADMIN_PREFIX must encode 256 random bits"
printf '%s' "$prefix" | grep -Eq '^[0-9a-f]{64}$' \
|| fail "TRUF_ADMIN_PREFIX must encode 256 random bits"
printf '%s' "$user" | grep -Eq '^[A-Za-z0-9_.-]{1,64}$' \
|| fail "TRUF_ADMIN_USER has an unsupported form"
printf '%s' "$password_hash" | grep -Eq '^\$2[aby]\$(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{53}$' \
|| fail "TRUF_ADMIN_PASSWORD_HASH must be a supported bcrypt hash"
[ "${#edge_marker}" -eq 64 ] || fail "TRUF_ADMIN_EDGE_MARKER must encode 256 random bits"
printf '%s' "$edge_marker" | grep -Eq '^[0-9a-f]{64}$' \
|| fail "TRUF_ADMIN_EDGE_MARKER must encode 256 random bits"
[ -f /etc/caddy/denylist/admin-denylist.caddy ] \
|| fail "the managed admin denylist snippet is missing"
[ ! -L /etc/caddy/denylist/admin-denylist.caddy ] \
|| fail "the managed admin denylist snippet must not be a link"
[ -d /var/log/caddy ] && [ -w /var/log/caddy ] \
|| fail "the authentication log directory is not writable"
umask 077
exec caddy run --config "$caddyfile" --adapter caddyfile
+26
View File
@@ -0,0 +1,26 @@
# Import this route-only snippet inside the reviewed public site block.
@truf_worker path /api/v1/worker/*
handle @truf_worker {
reverse_proxy 127.0.0.1:18766 {
header_up X-Truf-Shared-Ingress {$TRUF_SHARED_INGRESS_MARKER}
header_up -X-Truf-Admin-Edge
header_up -X-Truf-Admin-Operator
header_up -Forwarded
header_up -X-Real-IP
header_up -X-Forwarded-For
header_up X-Forwarded-For {http.request.remote.host}
}
}
@truf_admin path /{$TRUF_ADMIN_PREFIX} /{$TRUF_ADMIN_PREFIX}/*
handle @truf_admin {
reverse_proxy 127.0.0.1:18766 {
header_up X-Truf-Shared-Ingress {$TRUF_SHARED_INGRESS_MARKER}
header_up -X-Truf-Admin-Edge
header_up -X-Truf-Admin-Operator
header_up -Forwarded
header_up -X-Real-IP
header_up -X-Forwarded-For
header_up X-Forwarded-For {http.request.remote.host}
}
}