Issue 014: the node's serving key was stored in the host's own encoding, so every check that reads the file passed and no server could start. Same shape as 013 — two halves of one mechanism designed separately, each correct about its own half. Where a file exists so a third party can read it, the format is the interface.
49 lines
2.4 KiB
Markdown
49 lines
2.4 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-08-31
|
|
located-in: [mesh-host]
|
|
fixed-by: mesh-host — a node's serving key is stored in the format a server reads
|
|
amended-design:
|
|
---
|
|
|
|
# 014 — A node's serving key was present, correct, and unusable
|
|
|
|
## Symptom
|
|
|
|
A node generates the key it serves TLS with, the mesh certifies the public half, and the
|
|
certificate arrives on the machine as an ordinary file. Everything about that worked. But the host
|
|
stored the private half in its own encoding — base64 of the raw key — and **nothing that serves
|
|
TLS can read it**: not a web server's `ssl_certificate_key`, not Go's `LoadX509KeyPair`, not
|
|
`openssl s_server -key`.
|
|
|
|
The file was there, owned by root, mode 0600, holding the right key. The certificate beside it was
|
|
valid and chained to the mesh's authority. The server would not start.
|
|
|
|
## Why this matters
|
|
|
|
**Every check that reads the file passes.** The key exists, the certificate exists, the mesh
|
|
recorded the public half, the machine reports the declaration applied. The failure surfaces only
|
|
when something connects — the worst place to find out, and the place the certificate work was
|
|
specifically designed to move away from.
|
|
|
|
It is the same shape as [013](../013-a-file-arrives-after-the-service-that-needs-it/00-report.md)
|
|
and worth naming as a class: **two halves of one mechanism designed separately, each correct
|
|
about its own half.** The control plane issues PEM because that is what a certificate is. The host
|
|
stored the key in whatever was convenient, because nothing in the host reads it back — the whole
|
|
point of the file is that *something else* does, and that something else was not in view.
|
|
|
|
**The generalisation:** where a file exists so a third party can read it, the format is not an
|
|
implementation detail of whoever writes it. It is the interface, and it needs a check that reads
|
|
it the way that third party will.
|
|
|
|
## What was done
|
|
|
|
PKCS#8 PEM, which is what every TLS server reads. A key in the old encoding is refused **by name**
|
|
rather than reported as corrupt — it is intact, and the remedy is to enrol again, which is a
|
|
different action from repairing a damaged file.
|
|
|
|
*Checked by writing a key, decoding the file as PEM, parsing it as PKCS#8, and asserting it is the
|
|
same key — and, in the lab, by a real handshake from a second machine that verifies against the
|
|
mesh's authority and nothing else. A key that parses is not a key a server can use, which is why
|
|
the lab check connects.*
|