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:
@@ -44,6 +44,11 @@ type Grant struct {
|
||||
|
||||
// Rendering is everything needed to turn a resolution into the declaration a node is sent.
|
||||
type Rendering struct {
|
||||
// Certificate is what the mesh issued for this machine's internal name, and the mesh's own
|
||||
// certificate. Both public — the key they belong to never left the machine.
|
||||
Certificate string
|
||||
Authority string
|
||||
|
||||
// Needed is each module's own secrets, sealed to this node, keyed by module and then by the
|
||||
// name the module gave it.
|
||||
Needed map[string]map[string]string
|
||||
@@ -78,6 +83,26 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
|
||||
var out []map[string]any
|
||||
for _, m := range r.Modules {
|
||||
resources := m.Resources
|
||||
if c := m.Certificate; c != nil {
|
||||
if with.Certificate == "" {
|
||||
// Asked for and not issued. Refused rather than skipped: a module that serves TLS
|
||||
// with no certificate does not start, and the reason is somewhere else entirely.
|
||||
return nil, fmt.Errorf(
|
||||
"%s wants a certificate for this machine and none was issued", m.Module)
|
||||
}
|
||||
resources = append(append([]map[string]any{}, resources...), map[string]any{
|
||||
"id": CertificateID(), "type": "file", "path": c.Into,
|
||||
// Public. It travels in the open like any other file, because it is a statement
|
||||
// about a key rather than the key.
|
||||
"content": with.Certificate, "mode": "0644",
|
||||
})
|
||||
if c.Authority != "" {
|
||||
resources = append(resources, map[string]any{
|
||||
"id": AuthorityID(), "type": "file", "path": c.Authority,
|
||||
"content": with.Authority, "mode": "0644",
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, name := range sortedKeys(m.Needs) {
|
||||
sealed := with.Needed[m.Module][name]
|
||||
if sealed == "" {
|
||||
|
||||
Reference in New Issue
Block a user