Skip to content

Remove the insecure TLS override (#983) - #984

Merged
AcoPiper merged 4 commits into
mainfrom
AcoPiper/issue-983
Sep 2, 2026
Merged

Remove the insecure TLS override (#983)#984
AcoPiper merged 4 commits into
mainfrom
AcoPiper/issue-983

Conversation

@AcoPiper

@AcoPiper AcoPiper commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Closes #983.

What changed

danger_accept_invalid_certs(true) in src/tls.rs, reached through bootroot-agent --insecure, is gone. That was CodeQL alert 178 (rust/disabled-certificate-check), and it is the one finding in this batch the issue asks to fix rather than dismiss — it conflicts directly with the repository's certificate-verification policy, which admits no temporary exception.

The flag is removed rather than narrowed, so there is no supported runtime mode that accepts a certificate the configured trust cannot anchor. build_http_client now has two modes: the configured CA bundle (with its optional pins) or the system roots. insecure_mode stops being threaded through AcmeClient, the issuance flow, the registrar surface's issuance and renewal, and the daemon.

Two consequences worth naming:

  • The oneshot path built an IssuanceRuntime for that single field and read nothing else off it, so the struct and the config_path it was built from leave run_oneshot — including the config_path parameter on the public bootroot::run_oneshot. The daemon path, which does reload the config per retry, keeps both.
  • Pin-only bootstrap trust is untouched. It is still verification: selected only for a bundle that is absent or holds no parseable certificate, and anchored on the configured pins.

Two adjacent spellings of --insecure are deliberately left alone, because neither is a certificate-verification decision: scripts/preflight/extra/cli-scenarios.sh uses curl --insecure for a step-ca readiness probe, which is harness liveness rather than a bootroot runtime mode, and step certificate create --insecure in the two init TLS steps is step's own flag for creating a key with no password.

The compose smoke path

agent.toml.compose was the only caller that ran with no trust material at all, which is why it needed --insecure. It now carries a [trust] section to fill in from the deployment's own secrets/certs/, and scripts/preflight/extra/agent-scenarios.sh builds the bundle and both pins from there for every runtime config it writes.

published_endpoint in that script also had to stop addressing the loopback and start naming it. step-ca's certificate carries localhost as a DNS SAN and no IP SAN, and the compose file binds these ports to 127.0.0.1, so an agent dialling https://127.0.0.1:<port> would fail hostname verification against the very certificate the run was told to trust.

The [trust] comment in that template also had to stop promising the placeholders would "fail the handshake closed". They never reach a handshake — they are not 64 hex characters, so validate_trust_settings rejects the config outright — and an operator who left them in would have gone looking for a TLS error and found a config error.

Tests

  • agent_args: --insecure no longer appears in the help text and is rejected by the parser as an unknown argument.
  • bootroot_agent_hardening: the old oneshot_insecure_override_allows_untrusted_server is replaced by oneshot_rejects_an_insecure_override_flag, which asserts on the flag itself. The other assertions in that file would all still pass if the flag came back — the untrusted-server run fails either way — so the flag is what has to be asserted on.
  • registrar_certs: the merge-gate test that ran under insecure_mode to keep the transport from reading the bundle is removed; that path is unreachable now, and the gate itself is covered directly by acme::flow's test_write_merged_ca_bundle_fails_when_existing_unreadable. insecure_mode_does_not_select_bootstrap_pins is replaced by bootstrap_pins_are_selected_only_for_a_repairable_bundle, which pins down all three bundle states.
  • acme::client: allows_insecure_when_disabled is removed; rejects_self_signed_without_trust is now the whole story for an unanchored server.
  • The shipped-template test now asserts both [trust] keys survive in every shipped template, agent.toml.compose included. Dropping that section again would otherwise fail no build and no test — the compose path would silently fall back to the system CA store, which cannot anchor a self-signed step-ca, and the breakage would surface only when an operator ran the scenarios by hand.

The other 55 alerts

Alerts 128-177 and 179-183 were already dismissed on main with the reasons and comments the issue's triage specifies, each comment naming #983. I verified all 55 via the code-scanning API: every one carries a non-empty dismissal comment, and the reasons match the triage line by line (false positive / used in tests / won't fix). No code-scanning query is disabled or excluded — the repository has no CodeQL config file at all, and this change adds none.

Alert 178 is the only open alert left, and it closes once CodeQL analyzes main after this merges.

Test plan

  • Alert 178's finding is fixed in code: no danger_accept_invalid_certs remains anywhere in the tree, and no runtime mode accepts an unanchored certificate. (The alert itself closes only after CodeQL analyzes main post-merge.)
  • The other 55 alerts (128-177, 179-183) are dismissed with the triage's reason and a non-empty auditable comment, verified via the code-scanning API.
  • No CodeQL security query is disabled or excluded; the repository carries no CodeQL config and this PR adds none.
  • cargo fmt -- --config group_imports=StdExternalCrate --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features — 1077 + 1287 + all integration targets pass, 0 failed
  • markdownlint-cli2 "**/*.md" "#node_modules" "#target" — 0 issues, 32 files
  • scripts/check-docs.shmkdocs build --strict and the theme assertions pass, both locales
  • cargo audit — exits 0 on the two allowed advisories that are the standing baseline
  • The three docker-free validate-e2e-* scripts
  • scripts/preflight/extra/agent-scenarios.sh happy against a live compose stack — all five scenarios issue over verified TLS with no --insecure
  • scripts/preflight/ci/e2e-matrix.sh — NOT run locally; its bootroot init step needs passwordless sudo, which this host cannot supply (sudo -n true exits 1 here). Gated instead by CI's 14 Docker E2E jobs, all green on this head SHA: local-hosts, local-no-hosts, remote-hosts, remote-no-hosts, rotation, reinit-recovery, stepca-san, two-instance, openbao-tls-reown, openbao-tls-no-delta, registrar-verbs, registrar-internal, registrar-internal-init, registrar-redteam.
  • CI green on 2e68ef6: 22 checks pass, 0 fail (Quality Check, Unit & CLI Smoke, all 14 Docker E2E arms, Analyze (rust)/(python)/(actions), Instructions / check, Change Filter). The one non-pass is Registrar Redteam (docs-only), a docs-only conditional job that correctly does not run for a code change.

commands::infra::tests::preflight_compose_published_ports_checks_openbao_localhost_during_install is worth naming as a known flake rather than a regression: it fails when host port 8200 is busy and passes when re-run alone. The full-suite run above was green on a quiet host, with no failures anywhere.

`danger_accept_invalid_certs(true)`, reached through `bootroot-agent
--insecure`, disabled certificate verification outright. The repository's
certificate-verification policy does not admit that: verification is
never disabled or weakened to make a handshake work, and there is no
temporary exception — only a permanent one introduced temporarily. So
the flag goes rather than being narrowed. No runtime mode now accepts a
certificate the configured trust cannot anchor, and `insecure_mode`
stops being threaded through the ACME client, the issuance flow, the
registrar surface and the daemon.

The oneshot path carried an `IssuanceRuntime` for that one field and
never read the rest of it, so it and the `config_path` it was built
from go with it; the daemon path, which does reload config per retry,
keeps both.

The compose smoke path was the only caller that ran without trust
material at all. `agent.toml.compose` now carries a `[trust]` section to
fill in from the deployment's own `secrets/certs/`, and the agent
scenarios build the bundle and both pins from there for every runtime
config they write. They also name the loopback rather than address it:
step-ca's certificate carries `localhost` as a DNS SAN and no IP SAN, so
dialling `127.0.0.1` would fail hostname verification against the very
certificate the run was told to trust.

Closes #983
The `[trust]` comment promised the placeholders would "fail the
handshake closed", but they never reach a handshake: they are not 64 hex
characters, so `validate_trust_settings` rejects the config outright with
`trust.trusted_ca_sha256 must be 64 hex chars`. An operator who left them
in would go looking for a TLS error and find a config error instead.

The installation manuals ended the paragraph before the trust
preparation with a colon, which now introduces prose rather than the
block it used to.

Part of #983
Removing `--insecure` left `agent.toml.compose` as the one template that
had to grow a `[trust]` section to stay runnable, and nothing asserts it
is still there. Dropping it again would not fail a build or a test: the
compose smoke path would simply fall back to the system CA store, which
cannot anchor a self-signed step-ca, and the breakage would surface only
when an operator ran the scenarios by hand.

The neighbouring template test already stages both shipped configs and
loads them, so assert the two trust keys there. The placeholder values
are not checked -- `validate_trust_settings` rejects those, and
`from_file` does not run it.

Part of #983
The trust-key assertion added beside the profile one repeated its whole
setup: read the template out of the manifest directory, stage a copy
under a `.toml` name the config loader can infer a format from, and load
it. Two copies of that is two places to fix when a third template ships
or the staging trick stops being needed.

Name the template list once and put the staging behind a helper, so each
test is the assertion it exists for.

Part of #983
@AcoPiper AcoPiper changed the title Remove the insecure TLS override Remove the insecure TLS override (#983) Sep 2, 2026
@AcoPiper

AcoPiper commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

[Reviewer Round 1]

Approved — no blocking findings.

The change fully removes the runtime TLS-bypass path: the flag is absent from parsing and invocation plumbing, and build_http_client now has only system-root and configured-trust modes. Registrar bundle repair still uses a pin-verified bootstrap path only for missing/unparseable bundles (registrar_certs.rs:913); it does not reintroduce arbitrary-certificate acceptance.

The tests meaningfully cover the regression: the binary rejects --insecure before issuance (bootroot_agent_hardening.rs:395), while the compose scenario now constructs and pins its actual CA trust material (agent-scenarios.sh:180). Documentation and the shipped compose template consistently describe the required trust setup.

I also verified the Code Scanning API: alert 178 is the only open alert, and all other alerts specified by the issue are dismissed with the requested reasons and non-empty audit comments. The PR body correctly uses Closes #983 and includes a test plan.

@AcoPiper

AcoPiper commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

[Review Verdict Round 1: APPROVED]

@AcoPiper

AcoPiper commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Suggested squash commit

Title

Remove the insecure TLS override

Body

`bootroot-agent --insecure` reached `danger_accept_invalid_certs(true)`,
which accepted any certificate at all for the ACME connection. CodeQL
reports it as `rust/disabled-certificate-check`, and it is the one
finding in that batch to fix rather than dismiss: the repository's
certificate-verification policy admits no temporary exception, only
permanent ones introduced temporarily.

Remove the flag rather than narrowing it, so no supported runtime mode
accepts a certificate the configured trust cannot anchor.
`build_http_client` keeps two modes, the configured CA bundle with its
optional pins or the system roots, and `insecure_mode` stops being
threaded through the ACME client, the issuance flow, the registrar
surface's issuance and renewal, and the daemon. The oneshot path built
an `IssuanceRuntime` for that single field and read nothing else off
it, so the struct and its `config_path` leave `run_oneshot`; the daemon
path reloads the config per retry and keeps both. Pin-only bootstrap
trust is untouched — it is still verification, selected only for a
bundle that is absent or unparseable and anchored on the configured
pins.

The compose smoke path was the only caller running with no trust
material, which is why it needed the flag. `agent.toml.compose` now
carries a `[trust]` section to fill in from the deployment's own
`secrets/certs/`, and the agent scenarios build the bundle and both
pins from there for every runtime config they write. Those runs also
have to name the loopback rather than address it: step-ca's certificate
carries `localhost` as a DNS SAN and no IP SAN, so an agent dialling
`https://127.0.0.1:<port>` would fail hostname verification against the
very certificate it was told to trust.

A test asserts both `[trust]` keys survive in every shipped template.
Dropping that section again would fail no build and no test, and the
compose path would silently fall back to a system CA store that cannot
anchor a self-signed step-ca.

The remaining 55 alerts in the batch are dismissed with the reviewed
reasons and auditable comments rather than changed in code. No
code-scanning query is disabled or excluded.

Closes #983

@AcoPiper
AcoPiper merged commit 20d4e6e into main Sep 2, 2026
23 checks passed
@AcoPiper
AcoPiper deleted the AcoPiper/issue-983 branch September 2, 2026 09:35
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.

Resolve CodeQL 2.26.4 alert churn

1 participant