3
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
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 TestFabricRegistryRejectsLegacyEndpointAndExpiredRecord(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("legacy 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user