feat: crew requires TLS material and verifies the fleet CA on outbound calls (#9) #10
Loading…
Reference in a new issue
No description provided.
Delete branch "devel"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
feat: crew requires TLS material and verifies the fleet CA on outbound calls
What this is
Part of the fleet app-tier TLS change. Design and plan live in the
starfleetdocs repo:docs/superpowers/specs/2026-10-17-app-tier-tls-design.mddocs/superpowers/plans/2026-10-17-app-tier-tls.mddocs/superpowers/plans/2026-10-17-app-tier-tls-runbook.md(the live cutover)Crew already served TLS in production (its live config sets both paths), but
the code made TLS optional:
server_kwargsomitted the ssl kwargs unless bothpaths were set, so an empty configuration silently served plain HTTP and a
partial one degraded differently. Meanwhile every outbound call — to
computer, toroster— was made with noverifyargument, so it verifiedagainst the system trust store, which does not contain the fleet's private CA.
computerhas been TLS-required since the fleet-TLS-only change, so those callswere already unverifiable.
This branch removes the silent fallback and gives every outbound client the
fleet CA.
Change
TLS is required for serving.
server_kwargs(cfg)now raisesConfigErrornaming
tls_cert(both empty) ortls_key(partial) instead of omitting thessl kwargs. In production this is a no-op — crew already serves TLS — it closes
the silent path.
serve()computes the kwargs before resolving the CA, so aconfig missing both reports the primary error. Whitespace-only values are
stripped for the emptiness test, so
" "produces the named error rather than auvicorn file error.
Outbound trust. New
src/crew/trust.py::resolve_ca_bundle— byte-identicalto roster's apart from the docstring, deliberately, so the two services fail the
same way. Resolution order
--ca-bundle→$STARFLEET_CA_BUNDLE→[trust] ca_bundle→ConfigErrornaming all three, and the resolved bundle isvalidated with
load_verify_locations, so a missing path or a non-PEM filefails at resolution naming the setting and the path rather than at the first
request. Threaded into the four call sites:
_enroll_pcm,_computer_get_devices,_computer_capture, and the roster provisioning client(
prompting/roster_client.py, which gained aca_bundleand a shared_http()helper).
The CLI can verify crew's own API.
--ca-bundleis available to the clientsubcommands (not just
serve), so the documentedcrew list --url https://<crew-host>:4030 --ca-bundle <ca.pem>works — crew isTLS-only now, so that is the correct example. The default
--urlflipped tohttps://127.0.0.1:4030.main()catchesConfigErrorfor every command, so amissing or unreadable CA prints a clean
error: …and exits 1 rather than atraceback.
Docs and examples. README admin-UI and CLI guidance corrected (they said
http://127.0.0.1:4030, which is wrong twice over — wrong scheme, and a hostthat only works if the browser runs on the server); the TLS section documents
required material and the CA order;
crew.toml.examplegained[trust]and its[computer]URLs now point athttps://, because computer is TLS-required andthe file's own comment claims CA verification for those calls.
makedoc.shwasrepaired — it could no longer start the server, since it generated a config with
no TLS material.
Verification
Sandbox suite at the branch head: 228 passed,
ruff check/ruff format --check/mypy src/clean.Five
test_api_firmware.pyfailures appear without theenv -u …prefix;they are
ALL_PROXY=socks5hdemanding the absentsocksiopackage, unrelated tothis change. Verification is proven by real TLS: a test stands up a self-signed
TLS peer and asserts the positive control (trusting the peer's cert succeeds)
against the negative (a different bundle refuses the handshake). The spy tests
put a different but valid bundle in the config tier and assert the explicit one
wins, so a dropped argument cannot be masked by the config fallback.
Tests that asserted plaintext behaviour were inverted, not deleted — the
no-TLS kwargs test now requires both paths, and both partial directions are
covered.
Cross-repo / ops
rosterandcommunicator-esp32carry the sibling pieces.starfleetdocs repo. Note for the operator:crew serveresolves the CAeagerly at startup, so the redeploy is not a no-op —
[trust] ca_bundlemust exist first (the runbook says so).
tools/starfleet_trust.py(esp32) now also emits arosterleaf; adding filesto
DEV_FILESmakes an existing shared.dev-trustincomplete, so the nextensure()regenerates it with a new CA and a flashed dev device needs areflash.
Deferred minors (triaged ship)
--urlstill demands a resolvable CA(the plan's "all outbound clients", no scheme qualifier).
once in
create_appwould do._recording_httpx_clientis duplicated verbatim across three test files.makedoc.sh's SAN probe greps127\.0\.0\.1as a substring, so127.0.0.10would be accepted and then fail later as "the server never became healthy".
verify=<str>is deprecated in httpx 0.28.1 — a plan flaw (the planmandated the string form). Verification is on today; a future httpx bump
removes the form. Migration is to pass an
ssl.SSLContext.Co-authored-by: Troed Sångberg github@troed.se
Reviewed-on: #9