Skip to content

ci+fix: Validate RedHat/Rocky builds, and fix the CA bundle they exposed - #1405

Merged
mickem merged 6 commits into
mainfrom
test/checknet-external-connectivity
Aug 14, 2026
Merged

ci+fix: Validate RedHat/Rocky builds, and fix the CA bundle they exposed#1405
mickem merged 6 commits into
mainfrom
test/checknet-external-connectivity

Conversation

@mickem

@mickem mickem commented Aug 13, 2026

Copy link
Copy Markdown
Owner

The jest integration harness only ever ran on Debian builds. This turns it on for the RPM jobs too — and fixes the bug that immediately surfaced.

Why RHEL was untested

The harness has been gated if: inputs.package-type == 'deb' since b83d6bf7, from when it still needed a docker daemon. NSCP_SKIP_DOCKER=1 removed that need — it is how the deb job runs today — but the condition stayed. RPM packages were covered by acceptance-tests.sh alone, so anything that behaves differently on RHEL went untested. Nothing else was missing: the RPM base dependencies already install Node 20 from nodesource.

What it caught

${ca-path} was one hardcoded path for every non-Windows platform (service/path_manager.cpp):

{"ca-path", "/etc/ssl/certs/ca-certificates.crt"},

That is the Debian/Ubuntu location. On RHEL-family the bundle is /etc/pki/tls/certs/ca-bundle.crt, on SUSE /etc/ssl/ca-bundle.pem — so the token named a file that does not exist, and every TLS check that did not carry its own ca= failed.

It was never only a public-internet problem: make_context loads the CA whenever ca is non-empty, before the verify mode is considered, so a check against a local self-signed server with verify=none failed too. Turning the suite on for RPM failed five tests, four of them pre-existing tests that had simply never run on RHEL:

Failing test New?
check_http over TLS exposes the certificate expiry via ssl_expiry_days no
check_http reports no certificate when a redirect lands on plain http no
check_nsclient_web_online reports a reachable REST API no
check_nsclient_web_online passes a remote check result through no
check_http validates a public HTTPS site using the platform CA bundle yes

It also took nscp enroll with it: 0.16.0 defaults --ca to the same token, so fleet enrollment was impossible on RHEL-family without naming a bundle by hand.

The fix

Detect the bundle at configure time and pass it through config.h like the other path defaults, rather than hardcoding one per #ifdef. A package is built in a container of the distribution it targets, so the build host has the right answer; a packager who knows better can override with -DCONFIG_CA_PATH=…. This also removes the WIN32/else pair for this key.

path_manager_test.cpp now asserts the bundle exists, not merely that it lives under /etc/ — so a regression to a hardcoded value fails on the build host instead of in the field. That is the hermetic regression test that was missing all along.

Verified locally, in the CI image

Everything below was run in rockylinux/rockylinux:10 rather than by pushing and waiting:

  • Detection resolves to /etc/pki/tls/certs/ca-bundle.crt on Rocky 10 and /etc/ssl/certs/ca-certificates.crt on ubuntu:24.04; -DCONFIG_CA_PATH=/opt/custom/bundle.pem overrides both; the generated config.h carries the value.
  • Configuring the real project with and without this patch produces the same pre-existing CMake error (file COPY cannot find …/check_nsclient, an artefact of the local invocation), so the change adds none.
  • With ca-path resolving to a real bundle, checknet-commands on Rocky goes from 5 failed, 48 passed to 53 passed, 0 failed.
  • The full RPM job locally: Test Suites: 32 passed, Tests: 433 passed before the fix, with the 20 docker-gated suites skipping exactly as on deb. The existing python acceptance tests pass on Rocky (OK: 14 test(s) successfull).

Also added

check_ping against a public host, opt-in behind NSCP_EXTERNAL_ICMP=1. Nothing in the suite has ever sent an ICMP packet off the machine. It is opt-in because GitHub-hosted runners drop outbound ICMP — it returns CRITICAL with 100% loss on the Ubuntu containers and the Windows runners alike, while the identical query is OK from an Azure VM on both distros and from a Windows desktop.

Worth doing separately

  • Rocky 9 is commented out of the integration matrix (build-redhat.yml:177-182), so only Rocky 10 runs this. The bug reproduced on both.
  • include/net/http/client.hpp treats an empty ca as "trust nothing"; set_default_verify_paths() there would be a sensible second line of defence, and the three call sites that load a CA (client.hpp throws, check_tcp.cpp:233 returns a string, socket_helpers.cpp:230 collects and continues) could converge.

🤖 Generated with Claude Code

mickem added 3 commits August 13, 2026 21:54
Every target in the CheckNet suite is a listener on loopback, and every
TLS case passes `verify=none` or an explicit `ca=`. That keeps the suite
hermetic, but it means the one thing never exercised is the *default* CA
bundle: `${ca-path}`, which CheckNet::loadModuleEx resolves once at load
and hands to check_http whenever the caller does not override it.

That default is a single hardcoded path (service/path_manager.cpp), and
on every non-Windows platform it is the Debian/Ubuntu one, so on
RHEL-family - where the bundle is /etc/pki/tls/certs/ca-bundle.crt - any
HTTPS check fails before it opens a socket:

    CRITICAL: https://www.google.com -> 0 error: Failed to load CA
    /etc/ssl/certs/ca-certificates.crt: load_verify_file: asio.ssl error

The integration suite already runs inside rockylinux containers
(build-redhat.yml), so a single test that leaves the default alone
catches this there while passing on Debian. check_ping gets the same
treatment: nothing in the suite has ever sent an ICMP packet off the
machine, so "the check works" was never actually asserted.

Both tests assert reachability rather than latency - thresholds are
pinned wide, so a slow or busy runner cannot turn a working check into a
red build - and they are the only tests in the file that need egress,
which the header now says.

Verified by hand before adding: check_http is OK on Ubuntu 24.04 and
CRITICAL on Rocky 9 with the message above; check_ping is OK on both;
both are OK on Windows, where ${ca-path} points at the ROOT-store export
and is regenerated on demand when missing.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Michael Medin <michael@medin.name>
The first cut put both tests in the jest suite, on the assumption that it
runs everywhere. It does not: the jest steps in integration-tests-linux.yml
are gated `if: inputs.package-type == 'deb'`, so the RPM jobs never ran
them - and RHEL-family is the only place the hardcoded Debian CA path is
wrong. The run proved it: check_http passed on Ubuntu and Windows, and the
RedHat jobs reported green without executing either test.

Move the trust-store assertion into tests/acceptance-tests.sh, which runs
for both deb and rpm, so the RPM jobs fail on the bug they actually have.
It greps for an OK, so the failure carries the "Failed to load CA
/etc/ssl/certs/ca-certificates.crt" line with it.

check_ping becomes opt-in behind NSCP_EXTERNAL_ICMP=1. GitHub-hosted
runners drop outbound ICMP, so it came back CRITICAL with 100% loss on
Ubuntu and Windows alike, while the identical query is OK from an Azure VM
on both distros and from a Windows desktop. Deleting it would lose the only
test that proves check_ping puts a packet on the wire; skipping it by
default keeps CI honest without pretending the check is untested. It also
now asserts on the message, so a failure says which of the two it was
rather than only "expected 0, received 2".

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Michael Medin <michael@medin.name>
The jest harness has been gated `if: inputs.package-type == 'deb'` since
b83d6bf, from when it still needed a docker daemon. NSCP_SKIP_DOCKER=1
removed that need - it is how the deb job runs today - but the condition
stayed, so the rpm packages were covered by acceptance-tests.sh alone and
anything that behaves differently on RHEL went untested. The RPM base
dependencies already install Node 20 from nodesource, so nothing else was
missing.

Turning it on immediately finds a real bug. ${ca-path} is a single
hardcoded path (service/path_manager.cpp) and it is the Debian one on
every non-Windows platform, so on RHEL-family it names a file that is not
there. make_context loads the CA whenever `ca` is non-empty, before verify
mode is considered, so this is not limited to public hosts: `verify=none`
against a local self-signed server fails too.

Verified by running the whole RPM job locally in a rockylinux:10 container
(the CI image), against the rocky-10 artifact from the last build:

  Test Suites: 1 failed, 20 skipped, 32 passed, 33 of 53 total
  Tests:       5 failed, 148 skipped, 433 passed, 586 total

The 20 skips are the docker-gated scenarios, as on deb. All five failures
are in checknet-commands and all five are the same missing bundle - four
of them pre-existing tests that have never run on RHEL:

  ssl_expiry_days, redirect-lands-on-plain-http,
  check_nsclient_web_online x2, and the new public-HTTPS test

  CRITICAL: https://www.google.com -> 0 error: Failed to load CA
  /etc/ssl/certs/ca-certificates.crt: load_verify_file: No such file
  OK: https://www.google.com -> 200 ok        (with ca=/etc/pki/tls/certs/ca-bundle.crt)

The trust-store assertion therefore lives in the jest suite rather than in
acceptance-tests.sh, since the suite now runs where it matters.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Michael Medin <michael@medin.name>
@mickem mickem changed the title test: Check check_http and check_ping against the public internet ci: Validate RedHat/Rocky builds with the integration suite Aug 14, 2026
${ca-path} was one hardcoded path for every non-Windows platform,
/etc/ssl/certs/ca-certificates.crt, which is the Debian/Ubuntu location.
On RHEL-family the bundle is /etc/pki/tls/certs/ca-bundle.crt, on SUSE
/etc/ssl/ca-bundle.pem, so the token named a file that is not there and
every TLS check that did not carry its own ca= failed with

    Failed to load CA /etc/ssl/certs/ca-certificates.crt: No such file

That is not limited to public hosts: make_context loads the CA whenever
`ca` is non-empty, before the verify mode is considered, so a check
against a local self-signed server with verify=none failed too. It also
took nscp enroll with it, since 0.16.0 defaults --ca to this token - so
fleet enrollment was impossible on RHEL without naming a bundle by hand.

Detect it at configure time instead. A package is built in a container of
the distribution it targets, so the build host has the right answer, and
a packager who knows better can override with -DCONFIG_CA_PATH=... The
value now reaches path_manager through config.h like the other path
defaults, which also removes the WIN32/else pair for this key.

The unit test now asserts the bundle exists rather than only that it sits
under /etc/, so a regression to a hardcoded value fails on the build host
instead of in the field.

Verified in a rockylinux:10 container (the CI image): detection resolves
to /etc/pki/tls/certs/ca-bundle.crt there and to
/etc/ssl/certs/ca-certificates.crt on ubuntu:24.04, -DCONFIG_CA_PATH wins
over both, and the generated config.h carries the value. Configuring the
real project with and without this patch produces the same (pre-existing,
invocation-specific) CMake error, so it adds none. Pointing ca-path at the
real bundle takes checknet-commands on Rocky from 5 failed / 48 passed to
53 passed.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Michael Medin <michael@medin.name>
@mickem mickem changed the title ci: Validate RedHat/Rocky builds with the integration suite ci+fix: Validate RedHat/Rocky builds, and fix the CA bundle they exposed Aug 14, 2026
mickem added 2 commits August 14, 2026 10:59
The codespell workflow runs with only_warn, so its ten annotations ride
along on every pull request without ever failing a build - and stay
there. Clear them: enviornment, dependecies, migh, stil, configurtion,
lik, iniating and the `contect` parameter of switch_context (renamed in
both the declaration and the definition).

Two the dictionary does not know, fixed while here: "securoty
(certificates) folder" in both config.h generators, and the matching
"uniniating COM" next to the "iniating COM" codespell did flag.

Comments, cache doc strings and one log message only - no behaviour
change.

Signed-off-by: Michael Medin <michael@medin.name>
Assisted-by: Claude Code:claude-opus-5
It was there to work out whether check_ping actually puts a packet on
the wire when GitHub-hosted runners report 100% loss; that question is
answered, and an opt-in test nothing sets the variable for is dead
weight. The check_http case stays - it is what exercises the default
${ca-path} bundle this branch fixes.

Signed-off-by: Michael Medin <michael@medin.name>
Assisted-by: Claude Code:claude-opus-5
@mickem
mickem merged commit 860c3e1 into main Aug 14, 2026
29 checks passed
@mickem
mickem deleted the test/checknet-external-connectivity branch August 14, 2026 12:58
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