All checks were successful
Build ISO / build-iso (push) Successful in 17m14s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 1m2s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Successful in 15s
Release / release (push) Successful in 11m26s
Since 26.11 shipped login, two of the three nav pages were secretly
unauthenticated. The Caddyfile only reverse-proxied /api/*, /apps*,
/login*, /logout* to the Python auth-gated handler. Everything else —
including / (landing page) and /settings/ — fell through to Caddy's
catch-all file_server straight out of assets/www/, skipping the
session check entirely.
LAN visitor effect: they could read the box's hostname, IP, Furtka
version, uptime, and see all the Update-now / Reboot / HTTPS-toggle
buttons on /settings/. The API calls those buttons fired were
themselves 401-gated so nothing actually happened — but the info leak
plus "looks open" UX was real. Caught in the 26.13 SSH test session
when the user noticed Logout only appeared in the nav on /apps, and
not on / or /settings/.
Fix:
- Caddyfile: new `handle /settings*` and `handle /` blocks in the
shared `(furtka_routes)` snippet reverse-proxy to localhost:7000,
so both hit the Python auth-guard before the HTML goes out.
- api.py: new `_serve_static_www(relative_path)` helper reads
assets/www/{index.html, settings/index.html} with a path-traversal
clamp (resolved path must stay under static_www_dir). `do_GET`
routes `/` and `/settings[/]` to it. Removed the `/` branch from
the old combined-with-/apps line — those are different pages now.
- paths.py: new `static_www_dir()` helper with `FURTKA_STATIC_WWW`
env override for tests.
- assets/www/*.html: both nav bars get the Logout link + a shared
`doLogout()` inline script matching the _HTML pattern. Users never
see the link unauthed (the Python handler 302s them before the
page renders), but authed users get consistent navigation across
all three pages.
Tests: 5 new cases in test_api.py — unauth / redirects, unauth
/settings redirects (both trailing-slash and not), authed / serves
index.html, authed /settings serves settings/index.html,
regression guard that / and /apps serve different content.
Existing test updated (the one that used / as a proxy for /apps).
Static /style.css, /rootCA.crt, /status.json, /furtka.json,
/update-state.json stay served by Caddy's catch-all — those are
public by design (login page needs style.css, fresh users need the
CA to trust HTTPS, runtime JSON is metadata not creds).
272 tests pass, ruff check + format clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
3.4 KiB
Caddyfile
100 lines
3.4 KiB
Caddyfile
# Serves the Furtka landing page + live JSON on :80 (plain HTTP) and on
|
|
# HTTPS via Caddy's built-in `tls internal` — locally-issued certs signed
|
|
# by a root CA that Caddy generates on first start and stores under
|
|
# /var/lib/caddy/pki/authorities/local/. Static pages are read from
|
|
# /opt/furtka/current/ — updates flip the symlink and everything picks up
|
|
# the new content without a Caddy restart (a `systemctl reload caddy` is
|
|
# still triggered post-swap to flush the file-server's handle cache).
|
|
# /apps and /api are reverse-proxied to the resource-manager API
|
|
# (furtka serve, bound to 127.0.0.1:7000).
|
|
#
|
|
# Hostname templating: __FURTKA_HOSTNAME__ gets substituted with the
|
|
# install-time hostname by webinstaller/app.py on first install and by
|
|
# furtka.updater._refresh_caddyfile on every self-update. A bare `:443
|
|
# { tls internal }` (no hostname) never triggers leaf-cert issuance, so
|
|
# SNI-based handshakes die with `SSL_ERROR_INTERNAL_ERROR_ALERT` — the
|
|
# 26.4-alpha regression this file exists to cure.
|
|
#
|
|
# Force-HTTPS: /etc/caddy/furtka.d/*.caddyfile gets imported into the :80
|
|
# block. The /api/furtka/https/force endpoint creates or removes
|
|
# redirect.caddyfile there to toggle the HTTP→HTTPS redirect, then reloads
|
|
# Caddy. Glob imports silently no-op on an empty/missing directory, so the
|
|
# toggle-off state is "no file present" rather than "empty file".
|
|
{
|
|
# Named-hostname :443 blocks would otherwise make Caddy add its own
|
|
# HTTP→HTTPS redirect — but we already serve our own `:80` block and
|
|
# the opt-in /settings toggle owns the redirect. Disable the built-in
|
|
# to keep a single source of truth.
|
|
auto_https disable_redirects
|
|
}
|
|
|
|
(furtka_routes) {
|
|
handle /api/* {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
handle /apps* {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
handle /login* {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
handle /logout* {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
# /settings and / — these previously served as static HTML straight
|
|
# from the catch-all file_server, which meant the auth-guard was
|
|
# bypassed: a LAN visitor could see the box's version, IP, and
|
|
# reach the Update-now / Reboot buttons (the API calls behind them
|
|
# are auth-gated, but the page itself rendered without a redirect
|
|
# to /login). Route them through the Python handler which checks
|
|
# the session cookie and either serves the static HTML from
|
|
# assets/www/ or redirects to /login.
|
|
handle /settings* {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
handle / {
|
|
reverse_proxy localhost:7000
|
|
}
|
|
# Runtime JSON lives under /var/lib/furtka/ so it survives self-updates
|
|
# (which only swap /opt/furtka/current).
|
|
handle /status.json {
|
|
root * /var/lib/furtka
|
|
file_server
|
|
}
|
|
handle /furtka.json {
|
|
root * /var/lib/furtka
|
|
file_server
|
|
}
|
|
handle /update-state.json {
|
|
root * /var/lib/furtka
|
|
file_server
|
|
}
|
|
# Download the local root CA cert Caddy generated for `tls internal`.
|
|
# Available on both :80 and :443 so users can grab it before they've
|
|
# trusted it. The private key next to it stays 0600 / caddy-owned.
|
|
handle /rootCA.crt {
|
|
root * /var/lib/caddy/pki/authorities/local
|
|
rewrite * /root.crt
|
|
file_server
|
|
header Content-Type "application/x-x509-ca-cert"
|
|
header Content-Disposition "attachment; filename=furtka-local-rootCA.crt"
|
|
}
|
|
handle {
|
|
root * /opt/furtka/current/assets/www
|
|
file_server
|
|
encode gzip
|
|
}
|
|
log {
|
|
output stdout
|
|
}
|
|
}
|
|
|
|
:80 {
|
|
import /etc/caddy/furtka.d/*.caddyfile
|
|
import furtka_routes
|
|
}
|
|
|
|
__FURTKA_HOSTNAME__.local, __FURTKA_HOSTNAME__ {
|
|
tls internal
|
|
import furtka_routes
|
|
}
|