furtka/furtka
Daniel Maksymilian Syrnicki e68ed279cc
All checks were successful
Build ISO / build-iso (push) Successful in 17m23s
CI / lint (push) Successful in 27s
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 11m34s
fix(https): make HTTPS opt-in to stop the BAD_SIGNATURE trap on fresh installs
Every Furtka since 26.5 shipped a Caddyfile with a
`__FURTKA_HOSTNAME__.local { tls internal }` site block, so every
first boot auto-generated a fresh self-signed CA + intermediate +
leaf. That worked for the first-ever Furtka user, but every reinstall
(or second box on the same LAN) produced a new CA whose intermediate
shared the fixed CN `Caddy Local Authority - ECC Intermediate` with
the previous one. Firefox caches intermediates by CN across profiles
— even private windows share cert9.db — so any visitor who had
trusted an older Furtka's CA got a cached intermediate with
mismatched keys when they hit the new box, producing
`SEC_ERROR_BAD_SIGNATURE`. Unlike UNKNOWN_ISSUER, Firefox has NO
"Advanced → Accept Risk" bypass for BAD_SIGNATURE, so fresh-install
boxes were effectively unreachable over HTTPS in any browser that
had ever seen a previous Furtka.

Validated live on the .46 test VM: fresh 26.14 ISO install → Firefox
hits BAD_SIGNATURE on https://furtka.local/ (even in private mode).
Chromium bypasses it via mDNS failure but the issue is the same.
openssl verify on the box confirms the chain is internally valid —
this is purely client-side cache pollution across boxes.

Fix:
- assets/Caddyfile: removed the hostname site block. Default install
  serves :80 only — https://furtka.local connection-refuses, which is
  a normal error every browser handles instead of the unbypassable
  crypto fault. Added top-level import of
  /etc/caddy/furtka-https.d/*.caddyfile so the /settings HTTPS toggle
  can drop a listener snippet there when a user explicitly opts in.
- furtka/https.py: set_force_https now writes TWO snippets atomically
  — the top-level hostname + tls internal block (enables :443) and
  the :80-scoped redirect (forces HTTP→HTTPS). Disable removes both.
  Reload failure rolls both back. Added _read_hostname + _https_snippet_content
  helpers with `/etc/hostname` → 'furtka' fallback so a missing
  hostname file doesn't produce an empty site block Caddy rejects.
- furtka/https.py::status: force_https now reads the listener
  snippet (was reading the redirect snippet). A redirect without a
  listener isn't actually HTTPS being served, so the listener is the
  authoritative "HTTPS is on" signal.
- furtka/updater.py: new _maybe_migrate_preserve_https hook runs
  inside _refresh_caddyfile on the 26.14 → 26.15 transition. If the
  box had the redirect snippet on disk (user had opted into HTTPS
  under the old regime), it writes the new listener snippet too so
  HTTPS keeps working after the Caddyfile swap removes the hostname
  block.
- webinstaller/app.py: post-install creates /etc/caddy/furtka-https.d/
  alongside /etc/caddy/furtka.d/ so the glob import can't trip an
  older Caddy on a missing path during the first reload.

Live-tested on .46: set_force_https(True) writes both snippets, Caddy
reloads, :443 listener comes up with fresh CA, curl -k returns 302,
HTTP 301-redirects. set_force_https(False) removes both snippets
atomically, :443 goes back to connection-refused.

Tests: test_https.py expanded from 13 to 15 cases. Toggle-on asserts
both snippets written + hostname substituted. Toggle-off asserts
both removed. Rollback cases verify BOTH snippets restore on reload
failure. New test_https_snippet_content_has_tls_internal_and_routes
locks the exact shape of the listener block.
test_webinstaller_assets.py: updated two old asserts that assumed
hostname block was in Caddyfile; new test_post_install_creates_https_snippet_dir
guards the new directory.

276 tests pass, ruff check + format clean.

Known remaining wart (documented in CHANGELOG): a browser that
trusted a prior Furtka CA still hits BAD_SIGNATURE on this box's
HTTPS after enabling it, because the fixed intermediate CN is a
Caddy-side limitation. Workaround: clear cert9.db or visit in a
fresh profile. Won't affect end users with one Furtka box ever.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:30:04 +02:00
..
__init__.py feat(furtka): resource-manager skeleton — manifest, scanner, CLI 2026-04-15 09:59:41 +02:00
_release_common.py feat(catalog): on-box apps catalog synced independently of core version 2026-04-20 14:16:02 +02:00
api.py fix: auth-guard / and /settings, add Logout link to static navs 2026-04-21 18:16:42 +02:00
auth.py fix: unbreak upgrade path + install-lock race 2026-04-21 17:03:28 +02:00
catalog.py feat(catalog): on-box apps catalog synced independently of core version 2026-04-20 14:16:02 +02:00
cli.py feat(install): async background install with progress polling 2026-04-21 15:50:49 +02:00
dockerops.py feat(furtka): per-app image updates via POST /api/apps/<name>/update 2026-04-16 12:45:47 +02:00
https.py fix(https): make HTTPS opt-in to stop the BAD_SIGNATURE trap on fresh installs 2026-04-21 19:30:04 +02:00
install_runner.py feat(install): async background install with progress polling 2026-04-21 15:50:49 +02:00
installer.py feat(manifest): add 'path' setting type with server-side validation 2026-04-21 11:39:15 +02:00
manifest.py feat(manifest): add 'path' setting type with server-side validation 2026-04-21 11:39:15 +02:00
passwd.py fix: unbreak upgrade path + install-lock race 2026-04-21 17:03:28 +02:00
paths.py fix: auth-guard / and /settings, add Logout link to static navs 2026-04-21 18:16:42 +02:00
reconciler.py fix(furtka): audit follow-ups — placeholder secrets, isolate reconcile, .env perms 2026-04-15 10:17:00 +02:00
scanner.py fix(furtka): audit follow-ups — placeholder secrets, isolate reconcile, .env perms 2026-04-15 10:17:00 +02:00
sources.py feat(catalog): on-box apps catalog synced independently of core version 2026-04-20 14:16:02 +02:00
updater.py fix(https): make HTTPS opt-in to stop the BAD_SIGNATURE trap on fresh installs 2026-04-21 19:30:04 +02:00