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.
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package link
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/identity"
|
|
)
|
|
|
|
// What this node says when it joins, written out so the mesh's own suite can accept it.
|
|
//
|
|
// The two ends are separate structs in separate repositories. Every field here is one somebody
|
|
// could rename on one side, and the failure would be silent: enrolment succeeds, a key is simply
|
|
// absent, and the node looks joined until the first thing sealed to it cannot be opened. That is
|
|
// exactly the shape of fault this project keeps finding late.
|
|
//
|
|
// Skipped unless MESH_ENROL_OUT names a file, so this is a check somebody runs deliberately
|
|
// rather than a dependency between two repositories.
|
|
func TestWhatThisNodeSaysWhenItJoins(t *testing.T) {
|
|
path := os.Getenv("MESH_ENROL_OUT")
|
|
if path == "" {
|
|
t.Skip("set MESH_ENROL_OUT to write the enrolment request the mesh's suite reads")
|
|
}
|
|
|
|
// A real one. Generated the way enrolment generates them rather than typed as literals, so a
|
|
// key that stopped being a key would be caught here rather than travelling.
|
|
mine, err := identity.Generate("workstation")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
overlay, err := identity.GenerateOverlayKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sealing, err := identity.GenerateSealingKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serving, err := identity.GenerateServingKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
request := EnrolRequest{
|
|
Node: "workstation",
|
|
Secret: "a-one-time-secret",
|
|
PublicKey: mine.Public,
|
|
OverlayKey: overlay.Public,
|
|
SealingKey: sealing.Public,
|
|
ServingKey: serving.Public,
|
|
Profile: map[string]any{"seat": true},
|
|
}
|
|
body, err := json.MarshalIndent(request, "", " ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, append(body, '\n'), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The private half of the sealing key goes beside it, so the mesh's suite can prove what it
|
|
// sealed is openable rather than merely present.
|
|
if err := os.WriteFile(path+".sealing-private", []byte(sealing.Private), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("wrote %s", path)
|
|
}
|