The mesh certifies names inside it

08-connectivity keeps two authorities apart on purpose: a public one for
names the outside world reaches, and the mesh's own for names only the
mesh knows. Nothing implemented the second, so anything between machines
was plaintext or trust-on-first-use — which the design refuses everywhere
else.

A node now generates a fourth key at enrolment and reports the public
half. A fourth, because a key used for two purposes is one rotation away
from breaking the other: the identity key signs messages to the mesh and
would do for TLS, and reusing it would mean rotating a node's identity
every time its certificate is replaced.

**Nothing secret travels and nothing is sealed.** A certificate authority
says "this name belongs to the holder of this key", so the mesh signs a
public half it cannot use, and the certificate it issues is public. A
module asks for one and is given the certificate and, if it wants,
the mesh's own — the private key is a path to a file the machine already
has, the same arrangement the private network's key uses.

Asserted by verifying rather than inspecting, because a certificate that
parses and does not chain fails at the moment something connects:

- what the mesh issues verifies against the mesh, for the name asked for
- the name is in the subject alternative names, since a certificate
  carrying it only in the common name is refused by every modern client
- it certifies the key the node generated and no other
- another mesh's certificate does not verify, which is the whole point of
  two authorities being separate
- the authority cannot sign another authority — one that could is one
  that can be delegated without anybody deciding to
- two control planes starting together agree on one authority, or a mesh
  has certificates half its machines refuse

Certificates last ten years, which is a choice: a short life needs
something to renew it, and a renewal that fails silently is a mesh that
stops trusting itself on a date nobody wrote down. What makes one
replaceable is that the mesh reissues on demand, not that it expires.
This commit is contained in:
2026-08-31 00:09:13 +02:00
parent 0262873254
commit 646609c1b2
8 changed files with 570 additions and 1 deletions
+36
View File
@@ -212,6 +212,18 @@ type Manifest struct {
// module, in a file anybody can read, for ever.
Needs map[string]string `json:"needs,omitempty"`
// Certificate is where this module wants a certificate for its machine's name inside the
// mesh, and where the key that goes with it can be found.
//
// **The key is named, not delivered.** The node generated it at enrolment and keeps it; the
// mesh only ever signs the public half. So what arrives is a certificate, which is public,
// and a path to a file the machine already has.
//
// Two authorities are kept apart on purpose (novox/hq 08-connectivity): this is the mesh's,
// for names only the mesh knows. A name the outside world reaches is a different authority
// and a different problem.
Certificate *Certificate `json:"certificate,omitempty"`
// Grants is a directory this module wants the credentials of its consumers written into, per
// provision it offers — one file per consumer, named for it, holding the value alone.
//
@@ -262,6 +274,19 @@ const (
ArtifactUpstream = "upstream"
)
// Certificate says where a module wants what the mesh issued for its machine.
type Certificate struct {
// Into is where the certificate is written.
Into string `json:"into"`
// Authority is where the mesh's own certificate is written, so something connecting to this
// machine can be told what to believe. Optional: a module that only serves does not need it.
Authority string `json:"authority,omitempty"`
}
// CertificateID and AuthorityID are the resource identities of what the mesh issued.
func CertificateID() string { return "certificate" }
func AuthorityID() string { return "certificate-authority" }
// NeedID is the resource identity of the file a module's own secret lands in.
func NeedID(name string) string { return "needs-" + name }
@@ -404,6 +429,17 @@ func ParseManifest(raw []byte) (Manifest, error) {
"%s binds %q and does not require it", m.Module, to))
}
}
if c := m.Certificate; c != nil {
if !strings.HasPrefix(c.Into, "/") {
problems = append(problems, fmt.Sprintf(
"%s wants its certificate at %q, which is not an absolute path", m.Module, c.Into))
}
if c.Authority != "" && !strings.HasPrefix(c.Authority, "/") {
problems = append(problems, fmt.Sprintf(
"%s wants the authority at %q, which is not an absolute path",
m.Module, c.Authority))
}
}
for name, where := range m.Needs {
if !strings.HasPrefix(where, "/") {
problems = append(problems, fmt.Sprintf(