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:
@@ -1280,8 +1280,78 @@ func declarationWith(ctx context.Context, inv *inventory.Inventory, node string,
|
||||
needed[m.Module][name] = sealed
|
||||
}
|
||||
}
|
||||
// And a certificate for this machine's name inside the mesh, when anything on it asks. Issued
|
||||
// rather than stored: the node's key does not change, so signing again produces an equally
|
||||
// valid certificate and there is nothing to keep in step.
|
||||
var certificate, authority string
|
||||
for _, m := range plan.Modules {
|
||||
if m.Certificate == nil {
|
||||
continue
|
||||
}
|
||||
issued, meshCA, err := certificateFor(ctx, inv, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate, authority = issued, meshCA
|
||||
break
|
||||
}
|
||||
|
||||
return plan.Declaration(catalogue.Rendering{
|
||||
Settings: settings, Generators: gens, Grants: grants, Needed: needed})
|
||||
Settings: settings, Generators: gens, Grants: grants, Needed: needed,
|
||||
Certificate: certificate, Authority: authority})
|
||||
}
|
||||
|
||||
// certificateFor is what the mesh certifies about one machine's internal name.
|
||||
//
|
||||
// It reaches across two contexts and reads neither one's store from the other: `inventory` knows
|
||||
// the machine and whether it is on the private network, `identity` holds the authority and the
|
||||
// key that machine reported. The process holding both grants asks each for its part
|
||||
// (novox/hq ADR 0008).
|
||||
func certificateFor(ctx context.Context, inv *inventory.Inventory, node string) (string, string, error) {
|
||||
ident, err := openIdentity(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer ident.Close()
|
||||
|
||||
record, err := inv.NodeByName(ctx, node)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
serving, err := ident.ServingKeyOf(ctx, record.ID)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if serving == "" {
|
||||
// The machine joined before it had one, or never reported it. Said plainly, because the
|
||||
// remedy is on the machine and no amount of pushing from here will produce one.
|
||||
return "", "", fmt.Errorf(
|
||||
"%s wants a certificate and has never told the mesh what key it serves with; it "+
|
||||
"joins again to report one", node)
|
||||
}
|
||||
|
||||
// The name it is certified for. Only a machine on the private network has one — a certificate
|
||||
// for a name nothing resolves is a certificate nothing can check.
|
||||
where, err := whereEveryoneIs(ctx, inv, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
name := where[node]
|
||||
if name == "" {
|
||||
return "", "", fmt.Errorf(
|
||||
"%s wants a certificate and is not on the private network, so it has no name inside "+
|
||||
"the mesh to be certified for", node)
|
||||
}
|
||||
|
||||
issued, err := ident.Certify(ctx, node, name, serving)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
authority, err := ident.EstablishAuthority(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return issued, authority.Certificate, nil
|
||||
}
|
||||
|
||||
// grantsFor is every credential this node must create, because something elsewhere uses it.
|
||||
|
||||
Reference in New Issue
Block a user