Files
mesh-controller/internal/identity/authority_test.go
T
jschoubben 38d4e77cec A certificate is issued once and kept
Every signing carries a fresh random serial, so a mesh that signed per
composition composed a different declaration every time it was asked
what a machine should be. Every machine carrying a certificate then
stood eternally "waiting" — pushed seconds ago and already behind — and
the forge test, the first to wait for settledness on such a machine,
failed four runs in a row wearing three other faults' clothes.

Found live on a kept mesh, which is what settled it: two plans seconds
apart, identical to the byte but for one serial, in the certificate
file. Deduction had four theories; the diff had one line.

The keeping columns had existed since the serving key's migration —
"and what was issued for it" — and were written by nothing, the same
shape ReleasePorts was found in this morning.

Kept beside the serving key it certifies, and it stands while the name,
the key and the clock agree: a node rejoining with a new key or renamed
gets a fresh signing, exactly as if nothing were kept, and so does one
whose certificate is into its last stretch of life. The port's rule and
the secret's, applied to the third thing composed fresh each time.
2026-09-01 22:26:50 +02:00

252 lines
7.7 KiB
Go

package identity
import (
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"strings"
"sync"
"testing"
)
// The authority that certifies names inside the mesh.
//
// Asserted by verifying, not by inspecting: a certificate that parses and does not chain is a
// certificate that fails at the moment something connects, which is the worst place to find out.
func aServingKey(t *testing.T) (public string, private ed25519.PrivateKey) {
t.Helper()
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
return base64.StdEncoding.EncodeToString(pub), priv
}
func parsed(t *testing.T, certificate string) *x509.Certificate {
t.Helper()
block, _ := pem.Decode([]byte(certificate))
if block == nil {
t.Fatal("not a certificate")
}
got, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
return got
}
func TestACertificateChainsToTheMeshsOwnAuthority(t *testing.T) {
ident := fresh(t)
ctx := context.Background()
public, _ := aServingKey(t)
certificate, err := ident.Certify(ctx, "workstation", "workstation.internal", public)
if err != nil {
t.Fatal(err)
}
authority, err := ident.EstablishAuthority(ctx)
if err != nil {
t.Fatal(err)
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM([]byte(authority.Certificate)) {
t.Fatal("the mesh's authority is not usable as a root")
}
if _, err := parsed(t, certificate).Verify(x509.VerifyOptions{
Roots: roots, DNSName: "workstation.internal",
}); err != nil {
t.Fatalf("what the mesh issued does not verify against the mesh: %v", err)
}
}
func TestTheNameIsWhereEverythingLooksForIt(t *testing.T) {
// A certificate carrying the name only in its common name is one every modern client refuses.
ident := fresh(t)
public, _ := aServingKey(t)
certificate, err := ident.Certify(context.Background(), "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
got := parsed(t, certificate)
if len(got.DNSNames) != 1 || got.DNSNames[0] != "a.internal" {
t.Fatalf("the name is not in the subject alternative names: %v", got.DNSNames)
}
}
func TestItCertifiesTheKeyTheNodeGeneratedAndNoOther(t *testing.T) {
// A certificate authority's whole job is to say "this name belongs to the holder of this
// key". One that made the key would be saying something about a key it also holds.
ident := fresh(t)
public, private := aServingKey(t)
certificate, err := ident.Certify(context.Background(), "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
inside, ok := parsed(t, certificate).PublicKey.(ed25519.PublicKey)
if !ok {
t.Fatalf("the certificate carries a %T", parsed(t, certificate).PublicKey)
}
if !inside.Equal(private.Public()) {
t.Fatal("the certificate is for a key the node does not hold")
}
}
func TestAnAuthorityIsEstablishedOnceAndKept(t *testing.T) {
// Two authorities and nothing says which certificate to believe.
ident := fresh(t)
ctx := context.Background()
first, err := ident.EstablishAuthority(ctx)
if err != nil {
t.Fatal(err)
}
second, err := ident.EstablishAuthority(ctx)
if err != nil {
t.Fatal(err)
}
if first.Certificate != second.Certificate {
t.Fatal("asking twice made a second authority")
}
}
func TestSomethingThatIsNotAServingKeyIsRefused(t *testing.T) {
ident := fresh(t)
for _, bad := range []string{"", "not-base64!", base64.StdEncoding.EncodeToString([]byte("short"))} {
if _, err := ident.Certify(context.Background(), "a", "a.internal", bad); err == nil {
t.Fatalf("%q was certified", bad)
}
}
}
func TestTheAuthorityCannotBeUsedToMakeAnotherAuthority(t *testing.T) {
// An authority that could sign another is one that can be delegated without anybody deciding
// to. The path length says it cannot.
ident := fresh(t)
authority, err := ident.EstablishAuthority(context.Background())
if err != nil {
t.Fatal(err)
}
got := parsed(t, authority.Certificate)
if !got.IsCA {
t.Fatal("the authority is not an authority")
}
if got.MaxPathLen != 0 || !got.MaxPathLenZero {
t.Fatalf("the authority may sign another authority: path length %d", got.MaxPathLen)
}
}
func TestACertificateFromAnotherMeshDoesNotVerify(t *testing.T) {
// The whole point of two authorities being separate: one mesh's certificate means nothing to
// another, and the check that says so is the one that must not be skipped.
one, two := fresh(t), fresh(t)
public, _ := aServingKey(t)
certificate, err := one.Certify(context.Background(), "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
other, err := two.EstablishAuthority(context.Background())
if err != nil {
t.Fatal(err)
}
roots := x509.NewCertPool()
roots.AppendCertsFromPEM([]byte(other.Certificate))
if _, err := parsed(t, certificate).Verify(x509.VerifyOptions{
Roots: roots, DNSName: "a.internal",
}); err == nil {
t.Fatal("another mesh's certificate verified")
} else if !strings.Contains(err.Error(), "authority") && !strings.Contains(err.Error(), "signed") {
t.Fatalf("refused for the wrong reason: %v", err)
}
}
func TestTwoProcessesStartingTogetherAgreeOnOneAuthority(t *testing.T) {
// A restart while another copy is coming up. Both find nothing and both generate; only one
// insert may survive, and the loser must read back the winner rather than return the
// authority it generated and did not store — a mesh with two authorities has certificates
// half its machines refuse.
ident := fresh(t)
var wg sync.WaitGroup
authorities := make([]Authority, 6)
errs := make([]error, 6)
for i := range authorities {
wg.Add(1)
go func(i int) {
defer wg.Done()
authorities[i], errs[i] = ident.EstablishAuthority(context.Background())
}(i)
}
wg.Wait()
for i, err := range errs {
if err != nil {
t.Fatalf("establish %d failed: %v", i, err)
}
}
for i, a := range authorities {
if a.Certificate != authorities[0].Certificate {
t.Errorf("establish %d has a different authority from establish 0", i)
}
}
var count int
if err := ident.store.Pool().QueryRow(t.Context(),
`select count(*) from authority`).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("%d authorities exist; exactly one may", count)
}
}
// Asking twice gives the same certificate, to the byte.
//
// Every signing carries a fresh random serial, so a mesh that signed per composition composed a
// different declaration each time it was asked what a machine should be — and every machine
// carrying a certificate stood eternally "waiting", pushed seconds ago and already behind. Found
// live on a kept mesh: two plans seconds apart, identical but for one serial.
func TestACertificateIsIssuedOnceAndKept(t *testing.T) {
ident := fresh(t)
ctx := context.Background()
public, _ := aServingKey(t)
if _, err := ident.RecordNodeKey(ctx, "1b7e0000-0000-4000-8000-000000000001", make(ed25519.PublicKey, ed25519.PublicKeySize)); err != nil {
t.Fatal(err)
}
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", public); err != nil {
t.Fatal(err)
}
first, err := ident.Certify(ctx, "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
second, err := ident.Certify(ctx, "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
if first != second {
t.Fatal("two askings gave two certificates; every composition then differs by a serial " +
"and a machine carrying one is eternally behind")
}
// And a new key is a new world: the kept answer must not outlive what it certifies.
fresh2, _ := aServingKey(t)
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", fresh2); err != nil {
t.Fatal(err)
}
third, err := ident.Certify(ctx, "a", "a.internal", fresh2)
if err != nil {
t.Fatal(err)
}
if third == first {
t.Fatal("the node rejoined with a new key and was handed the certificate of its old one")
}
}