281 lines
11 KiB
Go
281 lines
11 KiB
Go
package mesh
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFabricRegistryGossipRecordRequiresTrustedSignature(t *testing.T) {
|
|
now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC)
|
|
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
record := testFabricRegistryGossipRecord(now, 10)
|
|
issuer := FabricRegistryTrustedIssuer{
|
|
IssuerID: "authority-1",
|
|
Role: FabricRegistryAuthorityControl,
|
|
PublicKey: publicKey,
|
|
Scopes: []string{FabricRegistryScopeCluster},
|
|
Services: []string{FabricRegistryServiceControlAPI},
|
|
}
|
|
signed, err := SignFabricRegistryGossipRecord(record, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign record: %v", err)
|
|
}
|
|
if _, err := VerifyFabricRegistryGossipRecord(signed, FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{issuer},
|
|
RequiredSignatures: 1,
|
|
Now: now,
|
|
}); err != nil {
|
|
t.Fatalf("verify signed record: %v", err)
|
|
}
|
|
tampered := signed
|
|
tampered.Endpoints[0].Address = "quic://10.10.10.10:19443"
|
|
if _, err := VerifyFabricRegistryGossipRecord(tampered, FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{issuer},
|
|
RequiredSignatures: 1,
|
|
Now: now,
|
|
}); err == nil {
|
|
t.Fatal("tampered record verified")
|
|
}
|
|
}
|
|
|
|
func TestFabricRegistryRejectsDisallowedEndpointAndExpiredRecord(t *testing.T) {
|
|
now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC)
|
|
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
issuer := FabricRegistryTrustedIssuer{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey}
|
|
record := testFabricRegistryGossipRecord(now, 10)
|
|
record.Endpoints[0].Address = "https://control.example.test/api/v1"
|
|
signed, err := SignFabricRegistryGossipRecord(record, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign record: %v", err)
|
|
}
|
|
if _, err := VerifyFabricRegistryGossipRecord(signed, FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{
|
|
{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey},
|
|
},
|
|
Now: now,
|
|
}); err == nil {
|
|
t.Fatal("compat HTTP endpoint was accepted")
|
|
}
|
|
expired := testFabricRegistryGossipRecord(now.Add(-2*time.Hour), 11)
|
|
expired.ExpiresAt = now.Add(-time.Minute)
|
|
expiredSigned, err := SignFabricRegistryGossipRecord(expired, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign expired record: %v", err)
|
|
}
|
|
if _, err := VerifyFabricRegistryGossipRecord(expiredSigned, FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{
|
|
{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey},
|
|
},
|
|
Now: now,
|
|
}); err == nil {
|
|
t.Fatal("expired record was accepted")
|
|
}
|
|
}
|
|
|
|
func TestFabricRegistryKeepsActiveRecordUntilNewerVerified(t *testing.T) {
|
|
now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC)
|
|
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
issuer := FabricRegistryTrustedIssuer{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey}
|
|
policy := FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{issuer},
|
|
RequiredSignatures: 1,
|
|
Now: now,
|
|
}
|
|
registry := NewFabricRegistry()
|
|
active, err := SignFabricRegistryGossipRecord(testFabricRegistryGossipRecord(now, 10), issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign active: %v", err)
|
|
}
|
|
entry, changed, err := registry.ApplyGossipRecord(active, policy, true)
|
|
if err != nil || !changed || entry.State != FabricRegistryActive {
|
|
t.Fatalf("apply active entry changed=%t entry=%+v err=%v", changed, entry, err)
|
|
}
|
|
old := testFabricRegistryGossipRecord(now.Add(time.Minute), 9)
|
|
old.Endpoints[0].Address = "quic://192.0.2.9:19443"
|
|
oldSigned, err := SignFabricRegistryGossipRecord(old, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign old: %v", err)
|
|
}
|
|
entry, changed, err = registry.ApplyGossipRecord(oldSigned, policy, true)
|
|
if err != nil {
|
|
t.Fatalf("apply old: %v", err)
|
|
}
|
|
if changed || entry.Record.Epoch != 10 || entry.Record.Endpoints[0].Address != "quic://192.0.2.10:19443" {
|
|
t.Fatalf("older record replaced active entry: changed=%t entry=%+v", changed, entry)
|
|
}
|
|
newer := testFabricRegistryGossipRecord(now.Add(2*time.Minute), 11)
|
|
newer.Endpoints[0].Address = "quic://192.0.2.11:19443"
|
|
newerSigned, err := SignFabricRegistryGossipRecord(newer, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign newer: %v", err)
|
|
}
|
|
policy.Now = now.Add(2 * time.Minute)
|
|
entry, changed, err = registry.ApplyGossipRecord(newerSigned, policy, false)
|
|
if err != nil || !changed || entry.State != FabricRegistryCandidate {
|
|
t.Fatalf("apply newer candidate changed=%t entry=%+v err=%v", changed, entry, err)
|
|
}
|
|
activeRecord, ok := registry.Active("cluster-1", FabricRegistryServiceControlAPI, FabricRegistryScopeCluster, "", policy.Now)
|
|
if !ok || activeRecord.Endpoints[0].Address != "quic://192.0.2.10:19443" {
|
|
t.Fatalf("unverified newer candidate displaced active fallback: ok=%t record=%+v", ok, activeRecord)
|
|
}
|
|
if !registry.MarkLiveVerified("cluster-1", FabricRegistryServiceControlAPI, FabricRegistryScopeCluster, "", policy.Now.Add(time.Second)) {
|
|
t.Fatal("mark live verified failed")
|
|
}
|
|
activeRecord, ok = registry.Active("cluster-1", FabricRegistryServiceControlAPI, FabricRegistryScopeCluster, "", policy.Now.Add(time.Second))
|
|
if !ok || activeRecord.Endpoints[0].Address != "quic://192.0.2.11:19443" {
|
|
t.Fatalf("newer verified record not active: ok=%t record=%+v", ok, activeRecord)
|
|
}
|
|
}
|
|
|
|
func TestFabricRegistryResolveServicePrefersVerifiedScopedRegionalEndpoint(t *testing.T) {
|
|
now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC)
|
|
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
issuer := FabricRegistryTrustedIssuer{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey}
|
|
policy := FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{issuer},
|
|
RequiredSignatures: 1,
|
|
Now: now,
|
|
}
|
|
registry := NewFabricRegistry()
|
|
clusterRecord := testFabricRegistryGossipRecord(now, 10)
|
|
clusterRecord.Endpoints = []FabricRegistryEndpoint{
|
|
{EndpointID: "control-eu", Address: "quic://eu.example.test:19443", Transport: "direct_quic", Region: "eu", Priority: 10, Weight: 1},
|
|
{EndpointID: "control-us", Address: "quic://us.example.test:19443", Transport: "direct_quic", Region: "us", Priority: 10, Weight: 10},
|
|
}
|
|
signedCluster, err := SignFabricRegistryGossipRecord(clusterRecord, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign cluster record: %v", err)
|
|
}
|
|
if _, _, err := registry.ApplyGossipRecord(signedCluster, policy, true); err != nil {
|
|
t.Fatalf("apply cluster record: %v", err)
|
|
}
|
|
orgRecord := testFabricRegistryGossipRecord(now.Add(time.Minute), 11)
|
|
orgRecord.Scope = FabricRegistryScopeOrganization
|
|
orgRecord.OrganizationID = "org-1"
|
|
orgRecord.Endpoints = []FabricRegistryEndpoint{
|
|
{EndpointID: "control-org", Address: "quic://org.example.test:19443", Transport: "direct_quic", Region: "eu", Priority: 1, Weight: 1},
|
|
}
|
|
signedOrg, err := SignFabricRegistryGossipRecord(orgRecord, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign org record: %v", err)
|
|
}
|
|
policy.Now = now.Add(time.Minute)
|
|
if _, _, err := registry.ApplyGossipRecord(signedOrg, policy, false); err != nil {
|
|
t.Fatalf("apply org candidate: %v", err)
|
|
}
|
|
resolved := registry.ResolveService(FabricRegistryResolveRequest{
|
|
ClusterID: "cluster-1",
|
|
Service: FabricRegistryServiceControlAPI,
|
|
Scope: FabricRegistryScopeOrganization,
|
|
OrganizationID: "org-1",
|
|
PreferredRegion: "us",
|
|
Now: now.Add(time.Minute),
|
|
})
|
|
if !resolved.Found || resolved.Scope != FabricRegistryScopeCluster || resolved.Endpoints[0].EndpointID != "control-us" {
|
|
t.Fatalf("expected cluster fallback with preferred region endpoint, got %+v", resolved)
|
|
}
|
|
if !registry.MarkLiveVerified("cluster-1", FabricRegistryServiceControlAPI, FabricRegistryScopeOrganization, "org-1", now.Add(2*time.Minute)) {
|
|
t.Fatal("mark org live verified failed")
|
|
}
|
|
resolved = registry.ResolveService(FabricRegistryResolveRequest{
|
|
ClusterID: "cluster-1",
|
|
Service: FabricRegistryServiceControlAPI,
|
|
Scope: FabricRegistryScopeOrganization,
|
|
OrganizationID: "org-1",
|
|
Now: now.Add(2 * time.Minute),
|
|
})
|
|
if !resolved.Found || resolved.Scope != FabricRegistryScopeOrganization || resolved.Endpoints[0].EndpointID != "control-org" {
|
|
t.Fatalf("expected verified organization record, got %+v", resolved)
|
|
}
|
|
snapshot := registry.Snapshot(now.Add(2 * time.Minute))
|
|
if snapshot.Active != 2 || snapshot.Candidate != 0 {
|
|
t.Fatalf("unexpected snapshot: %+v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestFabricRegistryVerifyCandidatesPromotesAfterQUICPong(t *testing.T) {
|
|
now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC)
|
|
tlsConfig := testQUICTLSConfig(t)
|
|
listener := startQUICFabricEchoServerWithTLS(t, tlsConfig)
|
|
defer listener.Close()
|
|
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
issuer := FabricRegistryTrustedIssuer{IssuerID: "authority-1", Role: FabricRegistryAuthorityControl, PublicKey: publicKey}
|
|
policy := FabricRegistryVerificationPolicy{
|
|
LocalClusterID: "cluster-1",
|
|
TrustedIssuers: []FabricRegistryTrustedIssuer{issuer},
|
|
RequiredSignatures: 1,
|
|
Now: now,
|
|
}
|
|
record := testFabricRegistryGossipRecord(now, 12)
|
|
record.Endpoints[0].Address = "quic://" + listener.Addr().String()
|
|
record.Endpoints[0].PeerCertSHA256 = testQUICCertSHA256(t, tlsConfig)
|
|
signed, err := SignFabricRegistryGossipRecord(record, issuer, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("sign record: %v", err)
|
|
}
|
|
registry := NewFabricRegistry()
|
|
if entry, changed, err := registry.ApplyGossipRecord(signed, policy, false); err != nil || !changed || entry.State != FabricRegistryCandidate {
|
|
t.Fatalf("apply candidate changed=%t entry=%+v err=%v", changed, entry, err)
|
|
}
|
|
results := registry.VerifyCandidates(context.Background(), NewQUICFabricTransport(nil), FabricRegistryLiveProbeRequest{
|
|
ClusterID: "cluster-1",
|
|
Timeout: 3 * time.Second,
|
|
Now: now.Add(time.Second),
|
|
MaxCandidates: 1,
|
|
})
|
|
if len(results) != 1 || results[0].Status != "reachable" || !results[0].Promoted {
|
|
t.Fatalf("unexpected live probe results: %+v", results)
|
|
}
|
|
if _, ok := registry.Active("cluster-1", FabricRegistryServiceControlAPI, FabricRegistryScopeCluster, "", now.Add(time.Second)); !ok {
|
|
t.Fatal("candidate was not promoted to active")
|
|
}
|
|
}
|
|
|
|
func testFabricRegistryGossipRecord(now time.Time, epoch int64) FabricRegistryGossipRecord {
|
|
return FabricRegistryGossipRecord{
|
|
SchemaVersion: FabricRegistryGossipRecordSchema,
|
|
ClusterID: "cluster-1",
|
|
Service: FabricRegistryServiceControlAPI,
|
|
Scope: FabricRegistryScopeCluster,
|
|
Epoch: epoch,
|
|
Generation: "gen",
|
|
IssuedAt: now,
|
|
ExpiresAt: now.Add(10 * time.Minute),
|
|
IssuerNodeID: "authority-1",
|
|
IssuerRole: FabricRegistryAuthorityControl,
|
|
Endpoints: []FabricRegistryEndpoint{
|
|
{
|
|
EndpointID: "control-a",
|
|
Address: "quic://192.0.2.10:19443",
|
|
Transport: "direct_quic",
|
|
Reachability: "public",
|
|
ConnectivityMode: "direct",
|
|
Priority: 1,
|
|
},
|
|
},
|
|
}
|
|
}
|