A node generates the key it serves TLS with

A fourth key, reported at enrolment like the others. The reasoning is the
one this file's neighbours already give twice: a key used for two
purposes is one rotation away from breaking the other.

The private half never leaves the machine. The mesh is told the public
half and signs a certificate binding it to this node's name inside the
mesh — so there is nothing to seal, and a copy of what the mesh holds
certifies nothing it did not already certify.

It does not make one on demand, for the same reason the sealing key does
not: a key the mesh has never certified is a key nothing will trust, so a
node that quietly generated one would serve a certificate for a key it no
longer has and fail in a way that names neither.
This commit is contained in:
2026-08-31 00:09:15 +02:00
parent 08e91065b4
commit 5237944473
4 changed files with 124 additions and 3 deletions
+8 -2
View File
@@ -40,6 +40,11 @@ type EnrolRequest struct {
// nothing else can read, and it must never be able to read it either.
SealingKey string `json:"sealing_key,omitempty"`
// ServingKey is the public half of the key this node serves TLS with on its internal name.
// The mesh signs a certificate binding it; the private half never leaves the machine, so
// there is nothing to seal and nothing that could be stolen from the mesh's copy.
ServingKey string `json:"serving_key,omitempty"`
Profile map[string]any `json:"profile,omitempty"`
}
@@ -70,7 +75,8 @@ var ErrRefused = errors.New("the mesh refused this enrolment")
// says once it is in, and the secret travels again because the control plane must not have to ask
// the broker who connected.
func Enrol(ctx context.Context, address, pin, node, secret string, public []byte,
overlayKey, sealingKey string, profile map[string]any, timeout time.Duration) (EnrolReply, error) {
overlayKey, sealingKey, servingKey string, profile map[string]any,
timeout time.Duration) (EnrolReply, error) {
config, err := PinnedConfig(pin)
if err != nil {
@@ -116,7 +122,7 @@ func Enrol(ctx context.Context, address, pin, node, secret string, public []byte
}
request := EnrolRequest{Node: node, Secret: secret, PublicKey: public,
OverlayKey: overlayKey, SealingKey: sealingKey, Profile: profile}
OverlayKey: overlayKey, SealingKey: sealingKey, ServingKey: servingKey, Profile: profile}
body, err := json.Marshal(request)
if err != nil {
return EnrolReply{}, err
+5
View File
@@ -37,6 +37,10 @@ func TestWhatThisNodeSaysWhenItJoins(t *testing.T) {
if err != nil {
t.Fatal(err)
}
serving, err := identity.GenerateServingKey()
if err != nil {
t.Fatal(err)
}
request := EnrolRequest{
Node: "workstation",
@@ -44,6 +48,7 @@ func TestWhatThisNodeSaysWhenItJoins(t *testing.T) {
PublicKey: mine.Public,
OverlayKey: overlay.Public,
SealingKey: sealing.Public,
ServingKey: serving.Public,
Profile: map[string]any{"seat": true},
}
body, err := json.MarshalIndent(request, "", " ")