Skip to content

Certificates page: make forcing netboot HTTPS a switch, and give it a way back to derived - #1732

Merged
fog-workflows[bot] merged 2 commits into
working-1.6from
fix/netboot-force-https-switch
Sep 8, 2026
Merged

fog-workflows[bot] merged 2 commits into
working-1.6from
fix/netboot-force-https-switch

Conversation

@darksidemilk

Copy link
Copy Markdown
Member

Draft. Replaces #1731, which proposed radios — wrong control, and it missed
the one-way-door bug below. See Not verified locally before merging.

Touches tests/pki-admin-helper.test.sh, which draft #1730 also touches —
whichever lands second will need a trivial merge.

The row today

[ON ]  The web certificate chains to a public CA        …
[   ]  Redirect HTTP to HTTPS                           …
[   ]  Rebuild iPXE with this server's CA embedded      …
┌────┐
│http│  Netboot fetches boot.php over                   …
└────┘

It does not read as a control. A form-select-sm in the narrow first column
renders as a bare http chip beside three switches that are obviously
interactive, and the title Netboot fetches boot.php over is a dangling phrase
the select was there to complete.

And http|https is not the question this row asks. _resolveNetbootProto()
derives the transport on every run from the two settings above it:

[[ ${BOOT_url_proto_forced} == yes && -n ${BOOT_url_proto} ]] && return 0
if [[ ${PKI_web_cert_publicly_trusted} == yes || ${BOOT_rebuild_ipxe_with_my_ca} == yes ]]; then
    BOOT_url_proto="https"   # else http

Both real transports set BOOT_url_proto_forced=yes when the installer
reads them. So the control offered two values, both of which pinned the server,
and none that released it — a one-way door. A server deriving https from a
public certificate was pinned by the first interaction and never derived again,
whatever its certificate later became. Nothing said so: the row went on
displaying the same word it displayed before.

The row now

[   ]  Force netboot over HTTPS
       BOOT_url_proto  ·  currently https (derived from the public CA setting above)

A fourth switch, beside the three it belongs with, asking the yes/no it actually
means: forced to https, or left to derive.

Off posts auto — a third member of the domain that is not a transport. The
helper turns it into BOOT_url_proto_forced=no and writes nothing else, so the
next run derives again.

That writes a key deliberately absent from PREF_KEYS, and I do not think it is
a hole in ADR 0036's refusal. That refusal was about forcing HTTPS with
neither steering key set — "not a thing a misclick should reach." auto only
ever writes no, and clearing a force can do nothing but return the value to
what derivation already says. Reaching forced=yes still means posting https
to a control that says so, and set-preference still refuses
BOOT_url_proto_forced as a key in either direction. This is the judgement
call most worth a second opinion.

The row says what is in effect and why(forced) vs (derived from the public CA setting above) vs (derived). A derived https and a forced https
are the same word and a different fact, and the switch cannot render correctly
without telling them apart. That needs the flag reported, so status loops over
a new REPORT_KEYS. Two lists on purpose: PREF_KEYS stays the write allowlist
and the security boundary; REPORT_KEYS is only what the page may see.

Forcing plain http leaves the page. Rare case, the direction that breaks
netboot rather than repairs it, and installfog.sh --netboot-proto http still
does it. The row still renders a CLI-forced http truthfully.

The bespoke JS handler goes away with the select — the switch posts the same
flag as the other three, and the page maps that flag to https/auto.

Not changed, because it is already true and pinned: netboot is excluded from the
HTTP→HTTPS redirect (vhost-netboot-exclusion 13/13).

Testing

suite
certificate-table 33 → 42, all passing
certificate-management-permission 19 → 22, all passing
vhost-netboot-exclusion 13/13
install-settings-resolution 50/50
boolean-encoding 42/42

All four states are pinned separately — checked-state and displayed-state
are different facts that coincide in only one of them.

Mutation-tested rather than merely green:

mutation result
upstream's select row 7 of 9 new certificate-table assertions fail
switch reads the transport, ignoring the forced flag exactly the derived-https assertion fails
auto falls through and writes itself as a transport exactly the auto-contract assertion fails
page maps off to http instead of auto exactly that assertion fails

Not verified locally

  • phpstan (both passes) did not run — no vendor/ here. The tests/ pass
    baselines occurrence counts, and this adds preg_match/preg_match_all and
    closures to two baselined files, so a count: may need bumping.
  • pki-admin-helper.test.sh SKIPs here — it needs unshare/root, so its
    new assertions are CI-verified only. The page-side and source-level contracts
    it pairs with do run locally and pass.
  • Not rendered in a browser. Markup and assertions are verified; how the row
    looks is not.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ps28eGTALgieR6TBUSafVg

The netboot transport row rendered its raw http|https domain as a select. Two
things were wrong with that, and the second is a bug rather than a look.

It did not read as a control. A form-select-sm in the table's narrow first
column comes out as a bare `http` chip beside three switches that are obviously
interactive, and its title -- "Netboot fetches boot.php over" -- was a dangling
phrase the select was there to complete.

More seriously, http|https is not the question this row asks.
_resolveNetbootProto() DERIVES the transport on every run from the two settings
above it, and stops deriving only once BOOT_url_proto_forced is yes. Both real
transports set that flag when the installer reads them. So the control offered
exactly two values, both of which pinned the server, and none that released it:
a server deriving https from a public certificate was pinned by the first
interaction and never derived again, whatever its certificate later became.
Nothing said so, and the row went on displaying the same word it had before.

So the row asks the yes/no it actually means -- "forced to https, or left to
derive" -- as a fourth switch beside the three it belongs with, and off posts
`auto`, a third member of the domain that is not a transport. The helper turns
it into BOOT_url_proto_forced=no and writes nothing else, so the next run
derives again.

That writes a key absent from PREF_KEYS, which is not a hole in ADR 0036's
refusal. The refusal was about FORCING https with neither steering key set --
"not a thing a misclick should reach". `auto` only ever writes `no`, and
clearing a force can do nothing but return the value to what derivation already
says. Reaching forced=yes still means posting https to a control that says so,
and set-preference still refuses BOOT_url_proto_forced as a key.

The row now also says what is in effect and why -- "currently https (derived
from the public CA setting above)" versus "(forced)" -- because a derived https
and a forced https are the same word and a different fact, and the switch
cannot render correctly without telling them apart. That needs the flag
reported, so status loops over a new REPORT_KEYS. Two lists deliberately:
PREF_KEYS stays the write allowlist and the security boundary, REPORT_KEYS is
only what the page may see.

Forcing plain http is no longer reachable from the page. It is the rare case, it
is the direction that breaks netboot rather than repairs it, and
installfog.sh --netboot-proto http still does it.

The bespoke JS handler goes away with the select -- the switch posts the same
flag as the other three, and the page maps that flag to https or auto on the
way to the helper.

Tests. certificate-table pins all four states separately, because checked-state
and displayed-state are different facts that coincide in only one of them; 7 of
the 9 fail against the old row, and making the switch read the transport
instead of the forced flag fails exactly the derived-https assertion.
certificate-management-permission pins both halves of the auto contract and
that the page never maps off to http. pki-admin-helper covers the behaviour:
auto clears the flag and leaves the transport alone, the flag is refused as a
key in either direction, and status reports it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps28eGTALgieR6TBUSafVg
@darksidemilk
darksidemilk marked this pull request as ready for review September 8, 2026 16:53
@fog-workflows
fog-workflows Bot added this pull request to the merge queue Sep 8, 2026
Merged via the queue into working-1.6 with commit 7de06bc Sep 8, 2026
11 checks passed
@fog-workflows
fog-workflows Bot deleted the fix/netboot-force-https-switch branch September 8, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant