Files
hq/04-ISSUES/129-nothing-makes-a-machine-trust-the-meshs-authority/01-diagnosis.md
T
jschoubben e41eed0852 Issue 129 is live and reproduced, and needs three steps rather than one
The certificate is genuine, from Mesh Internal CA, and nothing on the
workstation trusts it — verbatim the error the report gives. The public
name on the same proxy verifies cleanly, which puts the fault exactly
where the report puts it.

What is in the way is not an assignment. `ca-trust` is merged in the
catalogue and has never been registered with the mesh — 39 of 76
manifests are — so there is no module to assign. It dry-runs clean and
needs no artifact built.

Two findings from reproducing it, both their own issues:

157 — every routed name is published with an `.internal` alias that
nothing serves. The hosts file says keycloak.novox.be.internal; the proxy
serves keycloak.novox.internal and refuses the other by name. The first
three names I tried came from the hosts file and failed with a TLS alert
rather than a verification error, which pointed at a regression that had
not happened.

158 — the proxy re-logs all 52 routes every two seconds, 31 times a
minute. The one line that explained 157 sat between two of them.

Also recorded, because it was nearly filed as a defect and is not one:
step-ca publishes roots as /roots.pem, which is PEM, so ca-trust's fetch
and its refuse-a-non-certificate guard are both right. Its other endpoint
/roots returns JSON that contains the text the guard greps for, so the
guard is sound only because of which path is published.
2026-09-30 01:30:24 +02:00

3.9 KiB

129 — diagnosis

2026-09-30, from the workstation the issue was opened on.

Still live, and reproduced exactly

The certificate is genuine, the authority is the mesh's, and nothing on the machine trusts it:

$ openssl s_client -connect keycloak.novox.internal:443 -servername keycloak.novox.internal
subject=CN=keycloak.novox.internal
issuer=O=Mesh Internal CA, CN=Mesh Internal CA Intermediate CA
Verify return code: 20 (unable to get local issuer certificate)

$ curl https://keycloak.novox.internal/
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)

trust list holds no entry for the mesh. The anchors present are two mkcert development roots and the predecessor's lab root — the report's account of the trust store is unchanged.

The public name on the same proxy verifies cleanly (CN=keycloak.novox.be, Let's Encrypt, return code 0), which places the fault exactly where the report puts it: not in the proxy, not in the authority, and not in the certificate.

The name matters, and the report's "every HTTPS name the mesh serves internally" is too broad. The served internal name is <label>.<node>.internal. The hosts file also carries <label>.<public-domain>.internal, which nothing serves and which fails differently — that is issue 157, found while reproducing this, and it cost the first several minutes of this diagnosis.

The authority serves what the module needs

step-ca is up and healthy, and publishes roots: /roots.pem for both acme-ca and internal-acme-ca. That endpoint returns PEM:

$ curl -sk https://127.0.0.1:9000/roots.pem
-----BEGIN CERTIFICATE-----
MIIBvzCCAWWgAwIBAgIQYa2CkdJk16JyG/dVy2qoEzAKBggqhkjOPQQDAjA+…

So ${bound:internal-acme-ca:roots} in the ca-trust module composes to a URL that returns a certificate, and the module's own check — refuse a body that is not one — is checking the right thing.

Worth recording because it was nearly filed as a defect: step-ca also serves /roots, which returns {"crts":["-----BEGIN CERTIFICATE-----\n…"]}. That body contains the literal text the module greps for, so had the module used /roots it would have installed JSON into the anchors directory and reported success. It does not use it. The guard is sound only because the published path is the PEM one, which is worth knowing before anybody changes either.

What is actually in the way

The module is not registered. The report and the work plan both say it exists and is merged, which it does — mesh-catalog modules/ca-trust, on main. But the mesh has never been told about it:

$ mesh-controller module list | grep -iE 'ca-trust|step-ca'
step-ca            1        built 67f5f4cf          on novox

39 of the catalogue's 76 manifests are registered. ca-trust is one of the 37 that are not, so it cannot be assigned to anything — "assign it to one machine" has no module to name.

A dry run confirms it registers cleanly and needs no artifact built: it declares a directory, a script, a unit and a service, and no image.

$ mesh-controller build <catalogue> --path modules/ca-trust --dry-run
… the manifest, parsed and validated

So the remaining work is three steps, not one

  1. Register it — build it from the catalogue, which pins nothing because it has no artifacts.
  2. Assign it to a machine. The workstation this was observed on is the honest first choice: it is where a person meets the fault, and it is where the check can be made with a plain client.
  3. Verify curl https://<label>.<node>.internal/ with no flags, and trust list naming the mesh.

Then removal, which the module declares and nothing has exercised: unassigning must take the anchor away and refresh the bundles (ADR 0147), and that is the half most likely to be wrong, because it is the half nobody reaches by accident.