A node generates its own key for the private network
Curve25519, which is what WireGuard uses. The private half never leaves the machine and is written to a file of its own, so the interface configuration the mesh composes can point at it without ever carrying it. Separate from the identity keypair on purpose. One signs messages to the mesh and the other encrypts traffic between nodes -- different things verified by different parties at different times, and a key used for two purposes is one rotation away from breaking the other.
This commit is contained in:
@@ -28,6 +28,9 @@ func Path(statePath string) string {
|
||||
return filepath.Join(filepath.Dir(statePath), FileName)
|
||||
}
|
||||
|
||||
// dirOf is where a node keeps everything it knows about itself.
|
||||
func dirOf(statePath string) string { return filepath.Dir(statePath) }
|
||||
|
||||
// Identity is this node's own keypair, the name the mesh knows it by, and what it needs to get
|
||||
// back to that mesh without a person.
|
||||
//
|
||||
@@ -44,6 +47,10 @@ type Identity struct {
|
||||
Private []byte `json:"private"`
|
||||
|
||||
Membership Membership `json:"membership"`
|
||||
|
||||
// Overlay is this node's key on the private network. Generated here, like the identity above,
|
||||
// and for the same reason: the mesh computes a graph it cannot impersonate.
|
||||
Overlay OverlayKey `json:"overlay"`
|
||||
}
|
||||
|
||||
// Membership is how this node reaches the mesh it belongs to, and who it believes.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// The node's key on the private network, which is a different key from the one that says who it
|
||||
// is — and deliberately so.
|
||||
//
|
||||
// novox/hq 08-connectivity: each node generates its own keypair, the private half never leaves
|
||||
// the machine, and the public half is published to the mesh. That means the control plane
|
||||
// computes a peer graph it cannot itself impersonate: it knows every public key and holds no
|
||||
// private one, so it can say who may talk to whom without being able to pretend to be any of them.
|
||||
//
|
||||
// Separate from the identity keypair because they are verified by different things at different
|
||||
// times — the identity signs messages to the mesh, this one encrypts traffic between nodes — and
|
||||
// a key used for two purposes is one rotation away from breaking the other.
|
||||
|
||||
// OverlayKey is a Curve25519 keypair, which is what WireGuard uses.
|
||||
type OverlayKey struct {
|
||||
// Public is what travels. Base64, which is the form WireGuard configuration files use, so it
|
||||
// is carried the way it will be written rather than converted at the last moment.
|
||||
Public string `json:"public"`
|
||||
|
||||
// Private never leaves this machine. It is written to a file of its own that the interface
|
||||
// configuration points at, so the control plane can compose that configuration without ever
|
||||
// holding this.
|
||||
Private string `json:"private"`
|
||||
}
|
||||
|
||||
// GenerateOverlayKey makes this node's keypair for the private network.
|
||||
func GenerateOverlayKey() (OverlayKey, error) {
|
||||
private, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return OverlayKey{}, fmt.Errorf("cannot generate this node's overlay key: %w", err)
|
||||
}
|
||||
return OverlayKey{
|
||||
Public: base64.StdEncoding.EncodeToString(private.PublicKey().Bytes()),
|
||||
Private: base64.StdEncoding.EncodeToString(private.Bytes()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OverlayKeyPath is where the private half lives: a file of its own, referenced by the interface
|
||||
// configuration rather than embedded in it.
|
||||
//
|
||||
// That separation is what lets the mesh compose the configuration. WireGuard's `PostUp` can set a
|
||||
// private key from a file, so the declaration the control plane sends names this path and carries
|
||||
// no secret — and the file it names was written by the node, from a key nothing else ever saw.
|
||||
func OverlayKeyPath(statePath string) string {
|
||||
return dirOf(statePath) + "/overlay.key"
|
||||
}
|
||||
Reference in New Issue
Block a user