Refactor RDP proxy handling and update related tests

This commit is contained in:
2026-05-17 20:38:35 +03:00
parent 8e9402580f
commit d551e57fd5
172 changed files with 22117 additions and 2509 deletions
+42 -19
View File
@@ -53,6 +53,10 @@ const (
FabricServiceClassRemoteWorkspace = "remote_workspace"
FabricServiceClassFileTransfer = "file_transfer"
FabricServiceClassVideo = "video"
FabricServiceClassPlatformAdmin = "platform_admin"
FabricServiceClassClusterAdmin = "cluster_admin"
FabricServiceClassOrganization = "organization_portal"
FabricServiceClassUserPortal = "user_portal"
FabricChannelControl = "control"
FabricChannelInteractive = "interactive"
@@ -62,16 +66,27 @@ const (
)
var allowedNodeRoles = map[string]struct{}{
"entry-node": {},
"relay-node": {},
"core-mesh": {},
"rdp-worker": {},
"vnc-worker": {},
"vpn-exit": {},
"vpn-connector": {},
"file-storage-cache": {},
"update-cache": {},
"video-relay": {},
"public-ingress": {},
"admin-ingress": {},
"global-admin-runtime": {},
"cluster-admin-runtime": {},
"organization-portal-runtime": {},
"user-portal-runtime": {},
"identity-runtime": {},
"policy-authority": {},
"audit-sink": {},
"entry-node": {},
"relay-node": {},
"core-mesh": {},
"rdp-worker": {},
"vnc-worker": {},
"vpn-exit": {},
"vpn-connector": {},
"vpn-client": {},
"ipv4-egress": {},
"file-storage-cache": {},
"update-cache": {},
"video-relay": {},
}
type Cluster struct {
@@ -353,6 +368,7 @@ type NodeUpdatePlan struct {
Artifact *ReleaseArtifact `json:"artifact,omitempty"`
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
AuthorityQuorum *QuorumEnvelope `json:"authority_quorum,omitempty"`
ProductionForwarding bool `json:"production_forwarding"`
}
@@ -373,14 +389,15 @@ type NodeUpdateStatus struct {
}
type NodeBootstrap struct {
NodeID string `json:"node_id"`
ClusterID string `json:"cluster_id"`
IdentityStatus string `json:"identity_status"`
Certificate map[string]any `json:"certificate"`
HeartbeatEndpoint string `json:"heartbeat_endpoint"`
ClusterAuthority *ClusterAuthorityDescriptor `json:"cluster_authority,omitempty"`
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
NodeID string `json:"node_id"`
ClusterID string `json:"cluster_id"`
IdentityStatus string `json:"identity_status"`
Certificate map[string]any `json:"certificate"`
HeartbeatEndpoint string `json:"heartbeat_endpoint"`
ClusterAuthority *ClusterAuthorityDescriptor `json:"cluster_authority,omitempty"`
ClusterAuthorityQuorum *QuorumDescriptor `json:"cluster_authority_quorum,omitempty"`
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
}
type NodeJoinRequest struct {
@@ -1531,6 +1548,8 @@ type ClusterAuthorityState struct {
}
type ClusterSignature = clusterauth.Signature
type QuorumEnvelope = clusterauth.QuorumEnvelope
type QuorumDescriptor = clusterauth.QuorumDescriptor
type ClusterAuthorityDescriptor struct {
SchemaVersion string `json:"schema_version"`
@@ -1545,7 +1564,9 @@ type ClusterAuthorityDescriptor struct {
type ClusterAuthorityKey struct {
ClusterAuthorityDescriptor
PrivateKey string `json:"-"`
PrivateKey string `json:"-"`
Metadata json.RawMessage `json:"metadata,omitempty"`
QuorumDescriptor *QuorumDescriptor `json:"quorum_descriptor,omitempty"`
}
type ClusterAdminSummary struct {
@@ -1808,6 +1829,8 @@ type VPNClientConnection struct {
AllowedNodeIDs []string `json:"allowed_node_ids"`
EntryNodeIDs []string `json:"entry_node_ids"`
ExitNodeID string `json:"exit_node_id,omitempty"`
ExitPoolID string `json:"exit_pool_id,omitempty"`
ExitPoolName string `json:"exit_pool_name,omitempty"`
ActiveLease *NodeVPNAssignmentLease `json:"active_lease,omitempty"`
RoutePolicies json.RawMessage `json:"route_policies"`
ClientConfig json.RawMessage `json:"client_config"`
+148 -162
View File
@@ -11,6 +11,7 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
@@ -33,6 +34,13 @@ type Module struct {
vpnClientDiagnosticHub *vpnClientDiagnosticHub
}
const (
adminRuntimeProjectionRequestSchema = "rap.web_ingress.control_api_projection_request.v1"
adminRuntimeProjectionResponseSchema = "rap.web_ingress.control_api_projection_response.v1"
adminRuntimeProjectionBodySchema = "rap.control_api.admin_runtime_projection.v1"
adminRuntimeManifestSchema = "rap.web_ingress.ui_manifest.v1"
)
func NewModule(deps module.Dependencies, verifiers ...*authority.Verifier) *Module {
store := NewPostgresStore(deps.Infra.DB, verifiers...)
if deps.Config.Secret.EncryptionKeyBase64 != "" {
@@ -52,6 +60,7 @@ func (m *Module) Name() string {
}
func (m *Module) RegisterRoutes(router chi.Router) {
router.Get("/downloads/{fileName}", m.downloadReleaseFile)
router.Route("/clusters", func(r chi.Router) {
r.Get("/", m.listClusters)
r.Post("/", m.createCluster)
@@ -90,6 +99,7 @@ func (m *Module) RegisterRoutes(router chi.Router) {
r.Put("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/desired", m.setDesiredWorkload)
r.Post("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/status", m.reportWorkloadStatus)
r.Get("/{clusterID}/nodes/{nodeID}/workloads/status", m.listWorkloadStatuses)
r.Post("/{clusterID}/nodes/{nodeID}/admin-runtime/projection", m.projectAdminRuntime)
r.Get("/{clusterID}/mesh/links", m.listMeshLinks)
r.Post("/{clusterID}/mesh/links", m.reportMeshLink)
r.Get("/{clusterID}/mesh/route-intents", m.listRouteIntents)
@@ -97,14 +107,6 @@ func (m *Module) RegisterRoutes(router chi.Router) {
r.Post("/{clusterID}/mesh/route-intents/{routeIntentID}/expire", m.expireRouteIntent)
r.Post("/{clusterID}/mesh/route-intents/{routeIntentID}/disable", m.disableRouteIntent)
r.Get("/{clusterID}/mesh/qos-policies", m.listQoSPolicies)
r.Get("/{clusterID}/fabric/entry-points", m.listFabricEntryPoints)
r.Post("/{clusterID}/fabric/entry-points", m.createFabricEntryPoint)
r.Get("/{clusterID}/fabric/entry-points/{entryPointID}/nodes", m.listFabricEntryPointNodes)
r.Put("/{clusterID}/fabric/entry-points/{entryPointID}/nodes/{nodeID}", m.setFabricEntryPointNode)
r.Get("/{clusterID}/fabric/egress-pools", m.listFabricEgressPools)
r.Post("/{clusterID}/fabric/egress-pools", m.createFabricEgressPool)
r.Get("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes", m.listFabricEgressPoolNodes)
r.Put("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes/{nodeID}", m.setFabricEgressPoolNode)
r.Get("/{clusterID}/fabric/service-channels/route-feedback", m.listFabricServiceChannelRouteFeedback)
r.Post("/{clusterID}/fabric/service-channels/route-feedback/expire", m.expireFabricServiceChannelRouteFeedback)
r.Get("/{clusterID}/fabric/service-channels/rebuild-attempts", m.listFabricServiceChannelRouteRebuildAttempts)
@@ -172,6 +174,24 @@ func (m *Module) RegisterRoutes(router chi.Router) {
router.Put("/fabric/testing-flags", m.upsertFabricTestingFlag)
}
func (m *Module) downloadReleaseFile(w http.ResponseWriter, r *http.Request) {
fileName := filepath.Base(strings.TrimSpace(chi.URLParam(r, "fileName")))
if fileName == "" || fileName == "." || fileName != strings.TrimSpace(chi.URLParam(r, "fileName")) {
http.NotFound(w, r)
return
}
releaseDir := strings.TrimSpace(os.Getenv("RAP_RELEASE_DIR"))
if releaseDir == "" {
releaseDir = "/tmp/rap-release"
}
path := filepath.Join(releaseDir, fileName)
if _, err := os.Stat(path); err != nil {
http.NotFound(w, r)
return
}
http.ServeFile(w, r, path)
}
func (m *Module) listClusters(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusters(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
@@ -239,6 +259,126 @@ func (m *Module) updateCluster(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster": item})
}
func (m *Module) projectAdminRuntime(w http.ResponseWriter, r *http.Request) {
clusterID := chi.URLParam(r, "clusterID")
nodeID := chi.URLParam(r, "nodeID")
var payload struct {
SchemaVersion string `json:"schema_version"`
Method string `json:"method"`
Path string `json:"path"`
Query string `json:"query"`
Host string `json:"host"`
Scope string `json:"scope"`
ServiceClass string `json:"service_class"`
ObservedAt string `json:"observed_at"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid admin runtime projection payload")
return
}
if strings.TrimSpace(payload.SchemaVersion) != adminRuntimeProjectionRequestSchema {
httpx.WriteError(w, http.StatusBadRequest, "invalid admin runtime projection schema")
return
}
method := strings.ToUpper(strings.TrimSpace(payload.Method))
path := strings.TrimSpace(payload.Path)
if method != http.MethodGet && method != http.MethodHead {
httpx.WriteJSON(w, http.StatusOK, adminRuntimeProjectionResponse(http.StatusForbidden, "blocked", "control_api_mutation_rejected", nil))
return
}
if path == "" {
path = "/"
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
scope := strings.TrimSpace(payload.Scope)
serviceClass := normalizeFabricServiceClass(payload.ServiceClass)
if !isAllowedAdminRuntimeProjectionScope(scope, serviceClass) {
httpx.WriteJSON(w, http.StatusOK, adminRuntimeProjectionResponse(http.StatusForbidden, "blocked", "control_api_projection_scope_rejected", nil))
return
}
body := map[string]any{
"schema_version": adminRuntimeProjectionBodySchema,
"cluster_id": clusterID,
"node_id": nodeID,
"scope": scope,
"service_class": serviceClass,
"path": path,
"query": payload.Query,
"host": payload.Host,
"projection": "read_only",
"audit_required": true,
}
if path == "/ui-manifest" || strings.HasSuffix(path, "/ui-manifest") {
body["manifest"] = adminRuntimeManifest(scope, serviceClass)
httpx.WriteJSON(w, http.StatusOK, adminRuntimeProjectionResponse(http.StatusOK, "ready", "ui_manifest_ready", body))
return
}
if path == "/healthz" || path == "/readyz" {
httpx.WriteJSON(w, http.StatusOK, adminRuntimeProjectionResponse(http.StatusOK, "ready", "admin_runtime_projection_ready", body))
return
}
httpx.WriteJSON(w, http.StatusOK, adminRuntimeProjectionResponse(http.StatusNotImplemented, "blocked", "control_api_projection_not_implemented", body))
}
func adminRuntimeProjectionResponse(statusCode int, status string, reason string, body map[string]any) map[string]any {
raw, _ := json.Marshal(body)
return map[string]any{
"schema_version": adminRuntimeProjectionResponseSchema,
"status": status,
"reason": reason,
"status_code": statusCode,
"headers": map[string]string{
"Content-Type": "application/json",
},
"body": json.RawMessage(raw),
}
}
func isAllowedAdminRuntimeProjectionScope(scope string, serviceClass string) bool {
switch serviceClass {
case FabricServiceClassPlatformAdmin:
return scope == "platform"
case FabricServiceClassClusterAdmin:
return scope == "cluster"
case FabricServiceClassOrganization:
return scope == "organization"
case FabricServiceClassUserPortal:
return scope == "user" || scope == "organization"
default:
return false
}
}
func adminRuntimeManifest(scope string, serviceClass string) map[string]any {
sections := []string{"status"}
actions := []string{"read_status"}
switch strings.TrimSpace(serviceClass) {
case FabricServiceClassPlatformAdmin:
sections = []string{"clusters", "nodes", "roles", "fabric", "workloads", "audit"}
actions = []string{"read_platform_summary", "read_cluster_summaries", "read_node_status"}
case FabricServiceClassClusterAdmin:
sections = []string{"cluster", "nodes", "fabric", "workloads", "audit"}
actions = []string{"read_cluster_summary", "read_node_status"}
case FabricServiceClassOrganization:
sections = []string{"organization", "sessions", "resources", "audit"}
actions = []string{"read_organization_summary", "read_sessions"}
case FabricServiceClassUserPortal:
sections = []string{"profile", "sessions", "resources"}
actions = []string{"read_profile", "read_sessions"}
}
return map[string]any{
"schema_version": adminRuntimeManifestSchema,
"scope": scope,
"service_class": serviceClass,
"sections": sections,
"allowed_actions": actions,
"mutation_enabled": false,
"projection_binding": "control_api_read_only",
}
}
func (m *Module) listClusterNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusterNodes(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
@@ -1073,160 +1213,6 @@ func (m *Module) listQoSPolicies(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, map[string]any{"qos_policies": items})
}
func (m *Module) listFabricEntryPoints(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPoints(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_points": items})
}
func (m *Module) createFabricEntryPoint(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
EndpointType string `json:"endpoint_type"`
PublicEndpoint *string `json:"public_endpoint"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point payload")
return
}
item, err := m.service.CreateFabricEntryPoint(r.Context(), CreateFabricEntryPointInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
EndpointType: payload.EndpointType,
PublicEndpoint: payload.PublicEndpoint,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"entry_point": item})
}
func (m *Module) setFabricEntryPointNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point node payload")
return
}
item, err := m.service.SetFabricEntryPointNode(r.Context(), SetFabricEntryPointNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EntryPointID: chi.URLParam(r, "entryPointID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_node": item})
}
func (m *Module) listFabricEntryPointNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPointNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "entryPointID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_nodes": items})
}
func (m *Module) listFabricEgressPools(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPools(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pools": items})
}
func (m *Module) createFabricEgressPool(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
Description *string `json:"description"`
RouteScope json.RawMessage `json:"route_scope"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool payload")
return
}
item, err := m.service.CreateFabricEgressPool(r.Context(), CreateFabricEgressPoolInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
Description: payload.Description,
RouteScope: payload.RouteScope,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"egress_pool": item})
}
func (m *Module) setFabricEgressPoolNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool node payload")
return
}
item, err := m.service.SetFabricEgressPoolNode(r.Context(), SetFabricEgressPoolNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EgressPoolID: chi.URLParam(r, "egressPoolID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_node": item})
}
func (m *Module) listFabricEgressPoolNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPoolNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "egressPoolID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_nodes": items})
}
func (m *Module) issueFabricServiceChannelLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
@@ -0,0 +1,229 @@
package cluster
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
)
func TestProjectAdminRuntimeReturnsReadOnlyManifest(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
"method":"GET",
"path":"/platform-admin/ui-manifest",
"scope":"platform",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var response struct {
SchemaVersion string `json:"schema_version"`
Status string `json:"status"`
Reason string `json:"reason"`
StatusCode int `json:"status_code"`
Headers map[string]string `json:"headers"`
Body json.RawMessage `json:"body"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if response.SchemaVersion != "rap.web_ingress.control_api_projection_response.v1" ||
response.Status != "ready" ||
response.Reason != "ui_manifest_ready" ||
response.StatusCode != http.StatusOK ||
response.Headers["Content-Type"] != "application/json" {
t.Fatalf("response = %+v", response)
}
var body struct {
SchemaVersion string `json:"schema_version"`
ClusterID string `json:"cluster_id"`
NodeID string `json:"node_id"`
Manifest map[string]any `json:"manifest"`
}
if err := json.Unmarshal(response.Body, &body); err != nil {
t.Fatalf("decode body: %v", err)
}
if body.ClusterID != "cluster-1" ||
body.NodeID != "node-1" ||
body.Manifest["projection_binding"] != "control_api_read_only" ||
body.Manifest["mutation_enabled"] != false {
t.Fatalf("body = %+v", body)
}
}
func TestProjectAdminRuntimeRejectsMutations(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
"method":"POST",
"path":"/platform-admin/nodes",
"scope":"platform",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var response struct {
Status string `json:"status"`
Reason string `json:"reason"`
StatusCode int `json:"status_code"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if response.Status != "blocked" || response.Reason != "control_api_mutation_rejected" || response.StatusCode != http.StatusForbidden {
t.Fatalf("response = %+v", response)
}
}
func TestProjectAdminRuntimeReturnsHealthProjection(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
"method":"GET",
"path":"/readyz",
"scope":"platform",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var response struct {
Status string `json:"status"`
Reason string `json:"reason"`
StatusCode int `json:"status_code"`
Body json.RawMessage `json:"body"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if response.Status != "ready" || response.Reason != "admin_runtime_projection_ready" || response.StatusCode != http.StatusOK {
t.Fatalf("response = %+v", response)
}
var body struct {
Projection string `json:"projection"`
AuditRequired bool `json:"audit_required"`
}
if err := json.Unmarshal(response.Body, &body); err != nil {
t.Fatalf("decode body: %v", err)
}
if body.Projection != "read_only" || !body.AuditRequired {
t.Fatalf("body = %+v", body)
}
}
func TestProjectAdminRuntimeBlocksUnknownReadProjection(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
"method":"GET",
"path":"/platform-admin/nodes",
"scope":"platform",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var response struct {
Status string `json:"status"`
Reason string `json:"reason"`
StatusCode int `json:"status_code"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if response.Status != "blocked" ||
response.Reason != "control_api_projection_not_implemented" ||
response.StatusCode != http.StatusNotImplemented {
t.Fatalf("response = %+v", response)
}
}
func TestProjectAdminRuntimeRejectsScopeClassMismatch(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
"method":"GET",
"path":"/platform-admin/ui-manifest",
"scope":"organization",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var response struct {
Status string `json:"status"`
Reason string `json:"reason"`
StatusCode int `json:"status_code"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if response.Status != "blocked" ||
response.Reason != "control_api_projection_scope_rejected" ||
response.StatusCode != http.StatusForbidden {
t.Fatalf("response = %+v", response)
}
}
func TestProjectAdminRuntimeRejectsInvalidSchema(t *testing.T) {
router := chi.NewRouter()
module := &Module{}
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
"schema_version":"wrong.schema",
"method":"GET",
"path":"/readyz",
"scope":"platform",
"service_class":"platform_admin"
}`)))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
}
@@ -5,6 +5,8 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"strings"
"time"
@@ -190,7 +192,7 @@ func (s *PostgresStore) UpdateCluster(ctx context.Context, input UpdateClusterIn
func (s *PostgresStore) GetClusterAuthority(ctx context.Context, clusterID string) (ClusterAuthorityKey, error) {
row := s.db.QueryRow(ctx, `
SELECT cluster_id::text, authority_state, key_algorithm, public_key,
public_key_fingerprint, private_key, created_at, updated_at
public_key_fingerprint, private_key, created_at, updated_at, metadata
FROM cluster_authorities
WHERE cluster_id = $1::uuid
`, clusterID)
@@ -3497,7 +3499,7 @@ func (s *PostgresStore) CheckVPNLeaseOwnerEligibility(ctx context.Context, clust
WHERE nra.cluster_id = vc.cluster_id
AND nra.node_id = $3::uuid
AND nra.status = 'active'
AND nra.role IN ('vpn-exit', 'vpn-connector')
AND nra.role IN ('vpn-exit', 'vpn-connector', 'ipv4-egress')
AND (nra.organization_id IS NULL OR nra.organization_id = vc.organization_id)
) AS has_authorized_role
FROM vpn_connections vc
@@ -3582,7 +3584,7 @@ func (s *PostgresStore) ListNodeVPNAssignments(ctx context.Context, clusterID, n
WHERE nra.cluster_id = vc.cluster_id
AND nra.node_id = $2::uuid
AND nra.status = 'active'
AND nra.role IN ('vpn-exit', 'vpn-connector')
AND nra.role IN ('vpn-exit', 'vpn-connector', 'ipv4-egress')
AND (nra.organization_id IS NULL OR nra.organization_id = vc.organization_id)
) AS has_authorized_role,
EXISTS (
@@ -3769,13 +3771,33 @@ func scanClusterAuthority(row scanner) (ClusterAuthorityKey, error) {
&item.PrivateKey,
&item.CreatedAt,
&item.UpdatedAt,
&item.Metadata,
); err != nil {
return ClusterAuthorityKey{}, err
}
item.SchemaVersion = clusterauth.AuthoritySchemaVersion
ensureRaw(&item.Metadata, `{}`)
item.QuorumDescriptor = clusterAuthorityQuorumDescriptorFromMetadata(item.Metadata)
return item, nil
}
func clusterAuthorityQuorumDescriptorFromMetadata(metadata json.RawMessage) *QuorumDescriptor {
if len(metadata) == 0 || !json.Valid(metadata) {
return nil
}
var envelope struct {
QuorumDescriptor *QuorumDescriptor `json:"quorum_descriptor"`
Quorum *QuorumDescriptor `json:"quorum"`
}
if err := json.Unmarshal(metadata, &envelope); err != nil {
return nil
}
if envelope.QuorumDescriptor != nil {
return envelope.QuorumDescriptor
}
return envelope.Quorum
}
func scanNodeGroup(row scanner) (ClusterNodeGroup, error) {
var item ClusterNodeGroup
if err := row.Scan(
@@ -4517,6 +4539,8 @@ func (s *PostgresStore) GetVPNClientProfile(
), '[]'::jsonb) AS allowed_node_ids,
COALESCE(vc.placement_policy->'entry_node_ids', '[]'::jsonb) AS entry_node_ids,
COALESCE(vc.placement_policy->>'exit_node_id', '') AS exit_node_id,
COALESCE(pool.id::text, '') AS exit_pool_id,
COALESCE(pool.name, vc.name) AS exit_pool_name,
CASE WHEN l.id IS NULL THEN NULL ELSE jsonb_build_object(
'lease_id', l.id::text,
'owner_node_id', l.owner_node_id::text,
@@ -4576,6 +4600,34 @@ func (s *PostgresStore) GetVPNClientProfile(
'runtime_observed_at', gateway_status.observed_at
)) END AS client_config
FROM vpn_connections vc
LEFT JOIN LATERAL (
SELECT ep.id, ep.name
FROM fabric_egress_pools ep
WHERE ep.cluster_id = vc.cluster_id
AND ep.status = 'active'
AND (
ep.id::text = COALESCE(vc.placement_policy->>'exit_pool_id', '')
OR ep.name = COALESCE(vc.placement_policy->>'exit_pool_name', '')
OR EXISTS (
SELECT 1
FROM fabric_egress_pool_nodes epn
WHERE epn.egress_pool_id = ep.id
AND epn.cluster_id = vc.cluster_id
AND epn.status = 'active'
AND epn.node_id::text = ANY (
SELECT jsonb_array_elements_text(COALESCE(vc.placement_policy->'exit_node_ids', '[]'::jsonb))
)
)
)
ORDER BY
CASE
WHEN ep.id::text = COALESCE(vc.placement_policy->>'exit_pool_id', '') THEN 0
WHEN ep.name = COALESCE(vc.placement_policy->>'exit_pool_name', '') THEN 1
ELSE 2
END,
ep.name
LIMIT 1
) pool ON TRUE
LEFT JOIN vpn_connection_leases l
ON l.cluster_id = vc.cluster_id
AND l.vpn_connection_id = vc.id
@@ -4620,6 +4672,8 @@ func (s *PostgresStore) GetVPNClientProfile(
&allowedRaw,
&entryRaw,
&item.ExitNodeID,
&item.ExitPoolID,
&item.ExitPoolName,
&activeLeaseRaw,
&item.RoutePolicies,
&item.ClientConfig,
@@ -4641,6 +4695,15 @@ func (s *PostgresStore) GetVPNClientProfile(
ensureRaw(&item.PlacementPolicy, `{}`)
ensureRaw(&item.RoutePolicies, `[]`)
ensureRaw(&item.ClientConfig, `{}`)
if item.ExitPoolName != "" || item.ExitPoolID != "" {
item.ClientConfig = mergeJSONObjects(item.ClientConfig, map[string]any{
"exit_pool": map[string]any{
"id": item.ExitPoolID,
"name": firstNonEmptyMetadataString(item.ExitPoolName, item.Name),
"kind": "virtual_pool",
},
})
}
item.ClientConfig = enrichVPNClientFabricRoute(item, preferredEntryNodeID, preferredExitNodeID)
profile.Connections = append(profile.Connections, item)
}
@@ -4651,8 +4714,13 @@ func (s *PostgresStore) GetVPNClientProfile(
if err != nil {
return VPNClientProfile{}, err
}
exitEndpoints, err := s.vpnEntryEndpointCandidates(ctx, clusterID, vpnProfileExitNodeIDs(profile))
if err != nil {
return VPNClientProfile{}, err
}
for i := range profile.Connections {
profile.Connections[i].ClientConfig = enrichVPNClientEntryEndpointCandidates(profile.Connections[i], entryEndpoints)
profile.Connections[i].ClientConfig = enrichVPNClientExitEndpointCandidates(profile.Connections[i], exitEndpoints)
}
return profile, nil
}
@@ -4733,6 +4801,18 @@ func vpnProfileEntryNodeIDs(profile VPNClientProfile) []string {
return dedupeStrings(out)
}
func vpnProfileExitNodeIDs(profile VPNClientProfile) []string {
var out []string
for _, connection := range profile.Connections {
route := vpnFabricRouteFromClientConfig(connection.ClientConfig)
out = append(out, route.SelectedExitNodeID)
out = append(out, route.ExitPoolNodeIDs...)
out = append(out, connection.ExitNodeID)
out = append(out, connection.AllowedNodeIDs...)
}
return dedupeStrings(out)
}
func (s *PostgresStore) vpnEntryEndpointCandidates(ctx context.Context, clusterID string, entryNodeIDs []string) (map[string][]map[string]any, error) {
entryNodeIDs = dedupeStrings(entryNodeIDs)
out := make(map[string][]map[string]any, len(entryNodeIDs))
@@ -4778,13 +4858,12 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
if len(metadata) == 0 || json.Unmarshal(metadata, &payload) != nil {
return nil
}
certByCandidate := endpointCandidateCertsFromHeartbeatMetadata(metadata)
report := payload.MeshEndpointReport
var out []map[string]any
seen := map[string]struct{}{}
for _, candidate := range report.EndpointCandidates {
address := strings.TrimSpace(candidate.Address)
if address == "" {
continue
}
candidateNodeID := strings.TrimSpace(candidate.NodeID)
if candidateNodeID == "" {
candidateNodeID = nodeID
@@ -4793,6 +4872,9 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
if transport == "" {
transport = strings.TrimSpace(report.Transport)
}
if !usableVPNFabricPeerEndpoint(address, transport) {
continue
}
connectivityMode := strings.TrimSpace(candidate.ConnectivityMode)
if connectivityMode == "" {
connectivityMode = strings.TrimSpace(report.ConnectivityMode)
@@ -4813,6 +4895,11 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
if endpointID == "" {
endpointID = "mesh-" + candidateNodeID
}
key := candidateNodeID + "\x00" + strings.ToLower(transport) + "\x00" + strings.ToLower(address)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
item := map[string]any{
"node_id": candidateNodeID,
"endpoint_id": endpointID,
@@ -4826,6 +4913,15 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
"status": "reported",
"source": "node_latest_heartbeat.mesh_endpoint_report.endpoint_candidates",
}
if certSHA256 := firstNonEmptyMetadataString(
endpointCandidateMetadataString(candidate.Metadata, "tls_cert_sha256", "peer_cert_sha256"),
certByCandidate[endpointID],
certByCandidate[address],
certByCandidate[candidateNodeID+"\x00"+address],
); certSHA256 != "" {
item["tls_cert_sha256"] = certSHA256
item["peer_cert_sha256"] = certSHA256
}
if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" {
item["api_base_url"] = apiBaseURL
}
@@ -4833,7 +4929,7 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
}
if len(out) == 0 {
address := strings.TrimSpace(report.PeerEndpoint)
if address != "" {
if usableVPNFabricPeerEndpoint(address, strings.TrimSpace(report.Transport)) {
item := map[string]any{
"node_id": nodeID,
"endpoint_id": "mesh-peer-endpoint-" + nodeID,
@@ -4856,6 +4952,107 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
return out
}
func endpointCandidateCertsFromHeartbeatMetadata(metadata json.RawMessage) map[string]string {
out := map[string]string{}
var payload map[string]any
if len(metadata) == 0 || json.Unmarshal(metadata, &payload) != nil {
return out
}
report, _ := payload["mesh_endpoint_report"].(map[string]any)
candidates, _ := report["endpoint_candidates"].([]any)
for _, raw := range candidates {
candidate, _ := raw.(map[string]any)
if candidate == nil {
continue
}
meta, _ := candidate["metadata"].(map[string]any)
cert := strings.TrimSpace(metadataAnyString(meta["tls_cert_sha256"]))
if cert == "" {
cert = strings.TrimSpace(metadataAnyString(meta["peer_cert_sha256"]))
}
if cert == "" {
continue
}
endpointID := strings.TrimSpace(metadataAnyString(candidate["endpoint_id"]))
address := strings.TrimSpace(metadataAnyString(candidate["address"]))
nodeID := strings.TrimSpace(metadataAnyString(candidate["node_id"]))
if endpointID != "" {
out[endpointID] = cert
}
if address != "" {
out[address] = cert
}
if nodeID != "" && address != "" {
out[nodeID+"\x00"+address] = cert
}
}
return out
}
func metadataAnyString(value any) string {
switch typed := value.(type) {
case string:
return typed
default:
return ""
}
}
func firstNonEmptyMetadataString(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}
func usableVPNFabricPeerEndpoint(address string, transport string) bool {
address = strings.TrimSpace(address)
if address == "" {
return false
}
transport = strings.ToLower(strings.TrimSpace(transport))
if !strings.Contains(transport, "quic") {
return false
}
parsed, err := url.Parse(address)
if err != nil {
return false
}
if strings.ToLower(parsed.Scheme) != "quic" {
return false
}
host := parsed.Hostname()
if host == "" {
return false
}
ip := net.ParseIP(host)
if ip == nil {
return true
}
if ip.IsUnspecified() || ip.IsLoopback() {
return false
}
return true
}
func endpointCandidateMetadataString(metadata json.RawMessage, keys ...string) string {
if len(metadata) == 0 {
return ""
}
var values map[string]any
if json.Unmarshal(metadata, &values) != nil {
return ""
}
for _, key := range keys {
if value, ok := values[key].(string); ok && strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}
func heartbeatCapabilityEnabled(capabilities json.RawMessage, name string) bool {
var cfg map[string]any
if len(capabilities) == 0 || json.Unmarshal(capabilities, &cfg) != nil {
@@ -4921,6 +5118,44 @@ func enrichVPNClientEntryEndpointCandidates(connection VPNClientConnection, endp
return out
}
func enrichVPNClientExitEndpointCandidates(connection VPNClientConnection, endpoints map[string][]map[string]any) json.RawMessage {
var cfg map[string]any
if err := json.Unmarshal(connection.ClientConfig, &cfg); err != nil || cfg == nil {
cfg = map[string]any{}
}
route := vpnFabricRouteFromClientConfig(connection.ClientConfig)
exitIDs := dedupeStrings(append([]string{route.SelectedExitNodeID}, route.ExitPoolNodeIDs...))
exitIDs = dedupeStrings(append(exitIDs, connection.ExitNodeID))
exitIDs = dedupeStrings(append(exitIDs, connection.AllowedNodeIDs...))
var candidates []map[string]any
seen := map[string]struct{}{}
for _, nodeID := range exitIDs {
for _, candidate := range endpoints[nodeID] {
address, _ := candidate["address"].(string)
endpointID, _ := candidate["endpoint_id"].(string)
key := nodeID + "\x00" + endpointID + "\x00" + address
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
enriched := make(map[string]any, len(candidate)+2)
for k, v := range candidate {
enriched[k] = v
}
enriched["selected_exit"] = nodeID != "" && nodeID == route.SelectedExitNodeID
enriched["exit_pool_member"] = true
candidates = append(candidates, enriched)
}
}
cfg["vpn_exit_endpoint_candidates"] = candidates
cfg["vpn_exit_endpoint_candidate_count"] = len(candidates)
out, err := json.Marshal(cfg)
if err != nil {
return connection.ClientConfig
}
return out
}
func listVPNConnectionAllowedNodes(ctx context.Context, q rowQuerier, clusterID, vpnConnectionID string) ([]VPNConnectionAllowedNode, error) {
rows, err := q.Query(ctx, `
SELECT vpn_connection_id::text, cluster_id::text, node_id::text, role_preference,
@@ -5087,13 +5322,32 @@ func ensureRaw(raw *json.RawMessage, fallback string) {
}
}
func mergeJSONObjects(raw json.RawMessage, values map[string]any) json.RawMessage {
out := map[string]any{}
_ = json.Unmarshal(raw, &out)
if out == nil {
out = map[string]any{}
}
for key, value := range values {
out[key] = value
}
payload, err := json.Marshal(out)
if err != nil {
return raw
}
return payload
}
func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID, preferredExitNodeID string) json.RawMessage {
var cfg map[string]any
if err := json.Unmarshal(item.ClientConfig, &cfg); err != nil || cfg == nil {
cfg = map[string]any{}
}
entryPool := dedupeStrings(append([]string{}, item.EntryNodeIDs...))
if len(entryPool) == 0 {
placementPolicy := jsonObjectFromRaw(item.PlacementPolicy)
entrySelector, _ := placementPolicy["entry_selector"].(string)
clientNodeEntry := strings.EqualFold(strings.TrimSpace(entrySelector), "client_node") || placementPolicy["android_node_agent_target"] == true
if len(entryPool) == 0 && !clientNodeEntry {
entryPool = dedupeStrings(append([]string{}, item.AllowedNodeIDs...))
}
exitPool := []string{}
@@ -5107,7 +5361,10 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
exitPool = dedupeStrings(exitPool)
preferredEntryNodeID = strings.TrimSpace(preferredEntryNodeID)
selectedEntry := selectPreferredNode(entryPool, preferredEntryNodeID)
selectedEntry := ""
if !clientNodeEntry {
selectedEntry = selectPreferredNode(entryPool, preferredEntryNodeID)
}
selectedExit := selectPreferredNode(exitPool, preferredExitNodeID)
if selectedExit == "" && item.ActiveLease != nil && item.ActiveLease.OwnerNodeID != "" {
selectedExit = item.ActiveLease.OwnerNodeID
@@ -5116,6 +5373,8 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
switch {
case selectedEntry != "" && selectedExit != "":
status = "planned"
case clientNodeEntry && selectedExit != "":
status = "planned"
case selectedEntry == "":
status = "waiting_for_entry"
case selectedExit == "":
@@ -5129,8 +5388,10 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
"preferred_data_plane": "fabric_service_channel",
"fallback_data_plane": "none",
"backend_relay_fallback": false,
"selection_mode": "farm_authoritative_entry_to_exit",
"selection_mode": "farm_authoritative_client_node_to_exit_pool",
"route_authority": "fabric_farm",
"entry_selector": firstNonEmptyString(entrySelector, "entry-node"),
"client_node_entry": clientNodeEntry,
"vpn_builds_routes": false,
"vpn_builds_tunnels": false,
"farm_builds_routes": true,
@@ -5163,7 +5424,9 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
"diagnostics_only_protocol_summaries": true,
},
"route_selection": map[string]any{
"mode": "farm_authoritative_lowest_latency_healthy_route",
"mode": "farm_authoritative_lowest_latency_healthy_route_to_exit_pool",
"entry_selector": firstNonEmptyString(entrySelector, "entry-node"),
"client_node_entry": clientNodeEntry,
"selected_entry_node_id": selectedEntry,
"selected_exit_node_id": selectedExit,
"route_candidates": routeCandidates,
@@ -5175,7 +5438,7 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
"preserve_vpn_connection_id": true,
"alternate_route_count": alternateVPNRouteCount(routeCandidates, selectedEntry, selectedExit),
"reroute_triggers": []string{
"entry_unhealthy",
"client_node_mesh_path_unhealthy",
"exit_unhealthy",
"mesh_route_latency_regression",
"mesh_route_loss_regression",
@@ -5199,12 +5462,30 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
return out
}
func jsonObjectFromRaw(raw json.RawMessage) map[string]any {
var out map[string]any
if len(raw) == 0 || json.Unmarshal(raw, &out) != nil || out == nil {
return map[string]any{}
}
return out
}
func vpnFabricRouteCandidates(entryPool, exitPool []string, selectedEntry, selectedExit string) []map[string]any {
type pair struct {
entry string
exit string
}
pairs := make([]pair, 0, len(entryPool)*len(exitPool)+1)
if len(entryPool) == 0 && selectedExit != "" {
pairs = append(pairs, pair{exit: selectedExit})
}
if len(entryPool) == 0 {
for _, exit := range exitPool {
if exit != "" {
pairs = append(pairs, pair{exit: exit})
}
}
}
if selectedEntry != "" && selectedExit != "" {
pairs = append(pairs, pair{entry: selectedEntry, exit: selectedExit})
}
@@ -5219,6 +5500,9 @@ func vpnFabricRouteCandidates(entryPool, exitPool []string, selectedEntry, selec
seen := map[string]struct{}{}
out := make([]map[string]any, 0, len(pairs))
for _, pair := range pairs {
if pair.exit == "" {
continue
}
key := pair.entry + "\x00" + pair.exit
if _, ok := seen[key]; ok {
continue
@@ -5226,17 +5510,22 @@ func vpnFabricRouteCandidates(entryPool, exitPool []string, selectedEntry, selec
seen[key] = struct{}{}
priority := len(out) + 1
role := "alternate"
if pair.entry == selectedEntry && pair.exit == selectedExit {
if pair.exit == selectedExit && (pair.entry == selectedEntry || selectedEntry == "") {
role = "preferred"
priority = 0
}
out = append(out, map[string]any{
"entry_node_id": pair.entry,
"exit_node_id": pair.exit,
"role": role,
"priority": priority,
"status": "candidate",
})
candidate := map[string]any{
"exit_node_id": pair.exit,
"role": role,
"priority": priority,
"status": "candidate",
"source_role": "vpn-client",
"route_scope": "client_node_to_exit_pool",
}
if pair.entry != "" {
candidate["entry_node_id"] = pair.entry
}
out = append(out, candidate)
}
return out
}
@@ -137,7 +137,7 @@ func TestEnrichVPNClientFabricRouteUsesActiveLeaseWhenNoPolicyExit(t *testing.T)
}
}
func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T) {
func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedQUICEndpoint(t *testing.T) {
item := VPNClientConnection{
EntryNodeIDs: []string{"entry-1"},
ClientConfig: json.RawMessage(`{
@@ -150,16 +150,16 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T
}
heartbeatMetadata := json.RawMessage(`{
"mesh_endpoint_report": {
"transport": "direct_http",
"transport": "direct_quic",
"connectivity_mode": "direct",
"nat_type": "none",
"region": "test",
"peer_endpoint": "http://entry.example.test:19131",
"peer_endpoint": "quic://entry.example.test:19131",
"endpoint_candidates": [{
"endpoint_id": "public-http",
"endpoint_id": "public-quic",
"node_id": "entry-1",
"transport": "direct_http",
"address": "http://entry.example.test:19131",
"transport": "direct_quic",
"address": "quic://entry.example.test:19131",
"reachability": "public",
"priority": 0
}]
@@ -178,9 +178,12 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T
}
candidates := cfg["vpn_entry_endpoint_candidates"].([]any)
candidate := candidates[0].(map[string]any)
if candidate["node_id"] != "entry-1" || candidate["api_base_url"] != "http://entry.example.test:19131/api/v1" {
if candidate["node_id"] != "entry-1" || candidate["address"] != "quic://entry.example.test:19131" {
t.Fatalf("unexpected endpoint candidate: %#v", candidate)
}
if _, ok := candidate["api_base_url"]; ok {
t.Fatalf("QUIC dataplane candidate must not expose an API base URL: %#v", candidate)
}
if _, ok := candidate["local_gateway_shortcut"]; ok {
t.Fatalf("local gateway shortcut must not be advertised in farm-owned VPN mode: %#v", candidate)
}
@@ -188,3 +191,29 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T
t.Fatalf("unexpected endpoint metadata: %#v", candidate)
}
}
func TestVPNEntryEndpointCandidatesKeepsQUICEndpointsAndRejectsLegacyHTTP(t *testing.T) {
heartbeatMetadata := json.RawMessage(`{
"mesh_endpoint_report": {
"transport": "direct_quic",
"connectivity_mode": "direct",
"peer_endpoint": "quic://192.168.200.85:18080",
"endpoint_candidates": [
{"endpoint_id":"admin-web","node_id":"entry-1","transport":"direct_quic","address":"quic://192.168.200.85:18080","reachability":"private","priority":0},
{"endpoint_id":"http-old","node_id":"entry-1","transport":"direct_http","address":"http://192.168.200.85:19131","reachability":"private","priority":1},
{"endpoint_id":"mesh-quic","node_id":"entry-1","transport":"direct_quic","address":"quic://192.168.200.85:19131","reachability":"private","priority":2}
]
}
}`)
candidates := vpnEntryEndpointCandidatesFromHeartbeat("entry-1", nil, heartbeatMetadata)
if len(candidates) != 2 {
t.Fatalf("candidate count = %d, want two QUIC dataplane endpoints: %#v", len(candidates), candidates)
}
got := map[string]string{}
for _, candidate := range candidates {
got[candidate["endpoint_id"].(string)] = candidate["address"].(string)
}
if got["admin-web"] != "quic://192.168.200.85:18080" || got["mesh-quic"] != "quic://192.168.200.85:19131" {
t.Fatalf("unexpected candidates: %#v", candidates)
}
}
File diff suppressed because it is too large Load Diff
@@ -347,6 +347,71 @@ func TestAssignNodeRoleRejectsUnknownRole(t *testing.T) {
}
}
func TestAssignNodeRoleAllowsWebAdminPlacementRoles(t *testing.T) {
roles := []string{
"public-ingress",
"admin-ingress",
"global-admin-runtime",
"cluster-admin-runtime",
"organization-portal-runtime",
"user-portal-runtime",
"identity-runtime",
"policy-authority",
"audit-sink",
}
for _, role := range roles {
t.Run(role, func(t *testing.T) {
store := &fakeRepository{platformRole: PlatformRoleAdmin}
service := NewService(store)
item, err := service.AssignNodeRole(context.Background(), AssignNodeRoleInput{
ActorUserID: "admin-1",
ClusterID: "cluster-1",
NodeID: "node-1",
Role: role,
})
if err != nil {
t.Fatalf("assign role: %v", err)
}
if item.Role != role {
t.Fatalf("role = %q, want %q", item.Role, role)
}
})
}
}
func TestFabricAdminServiceClassesAreScopedToAdminRoles(t *testing.T) {
cases := []struct {
serviceClass string
requiredRole string
pathNeedle string
}{
{FabricServiceClassPlatformAdmin, "global-admin-runtime", "platform-admin"},
{FabricServiceClassClusterAdmin, "cluster-admin-runtime", "cluster-admin"},
{FabricServiceClassOrganization, "organization-portal-runtime", "organizations"},
{FabricServiceClassUserPortal, "user-portal-runtime", "users"},
}
for _, tc := range cases {
t.Run(tc.serviceClass, func(t *testing.T) {
if !isAllowedFabricServiceClass(tc.serviceClass) {
t.Fatalf("service class %q is not allowed", tc.serviceClass)
}
roles := normalizeFabricRequiredRoles(nil, tc.serviceClass)
if !containsString(roles, tc.requiredRole) || !containsString(roles, "identity-runtime") || !containsString(roles, "policy-authority") {
t.Fatalf("required roles = %+v", roles)
}
channels := normalizeFabricServiceChannels(nil, tc.serviceClass)
if !containsString(channels, FabricChannelControl) || !containsString(channels, FabricChannelInteractive) || !containsString(channels, FabricChannelReliable) {
t.Fatalf("channels = %+v", channels)
}
ingress := fabricServiceChannelHTTPIngress(tc.serviceClass)
if !strings.Contains(ingress.PathTemplate, tc.pathNeedle) {
t.Fatalf("path = %q, want %q", ingress.PathTemplate, tc.pathNeedle)
}
})
}
}
func TestAttachExistingNodeRequiresPlatformAdmin(t *testing.T) {
store := &fakeRepository{platformRole: "user"}
service := NewService(store)
@@ -567,6 +632,70 @@ func TestApproveJoinRequestReturnsBootstrapContract(t *testing.T) {
}
}
func TestApproveJoinRequestReturnsSignedQuorumDescriptor(t *testing.T) {
keys, err := clusterauth.GenerateKeyPair()
if err != nil {
t.Fatalf("generate key: %v", err)
}
quorum := &QuorumDescriptor{
SchemaVersion: clusterauth.QuorumSchemaVersion,
ClusterID: "cluster-1",
Epoch: "epoch-1",
Threshold: 1,
Members: []clusterauth.QuorumMember{
{
NodeID: "authority-1",
Role: "update-authority",
PublicKey: keys.PublicKeyB64,
PublicKeyFingerprint: keys.Fingerprint,
Scopes: []string{"update-authority"},
},
},
}
store := &fakeRepository{
platformRole: PlatformRoleAdmin,
clusterAuthority: ClusterAuthorityKey{
ClusterAuthorityDescriptor: ClusterAuthorityDescriptor{
SchemaVersion: clusterauth.AuthoritySchemaVersion,
ClusterID: "cluster-1",
AuthorityState: "active",
KeyAlgorithm: clusterauth.AlgorithmEd25519,
PublicKey: keys.PublicKeyB64,
PublicKeyFingerprint: keys.Fingerprint,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
PrivateKey: keys.PrivateKeyB64,
QuorumDescriptor: quorum,
},
}
service := NewService(store)
approved, err := service.ApproveJoinRequest(context.Background(), ApproveJoinRequestInput{
ActorUserID: "admin-1",
ClusterID: "cluster-1",
JoinRequestID: "join-request-1",
NodeKey: "node-key-1",
})
if err != nil {
t.Fatalf("approve join request: %v", err)
}
if approved.Bootstrap.ClusterAuthorityQuorum == nil {
t.Fatalf("bootstrap missing quorum descriptor: %+v", approved.Bootstrap)
}
var payload clusterNodeApprovalAuthorityPayload
if err := json.Unmarshal(approved.Bootstrap.AuthorityPayload, &payload); err != nil {
t.Fatalf("decode authority payload: %v", err)
}
quorumHash, err := clusterauth.QuorumDescriptorHash(*quorum)
if err != nil {
t.Fatalf("hash quorum: %v", err)
}
if payload.ClusterAuthorityQuorumSHA256 != quorumHash {
t.Fatalf("quorum hash = %q, want %q", payload.ClusterAuthorityQuorumSHA256, quorumHash)
}
}
func TestGetJoinRequestBootstrapReturnsSignedApproval(t *testing.T) {
nodeID := "node-1"
store := &fakeRepository{
@@ -694,7 +823,8 @@ func TestGetVPNClientProfileEnsuresFabricVPNPacketRouteIntents(t *testing.T) {
vpnClientProfile: VPNClientProfile{
SchemaVersion: "rap.vpn_client_profile.v1",
Connections: []VPNClientConnection{{
ID: "vpn-1",
ID: "vpn-1",
TargetEndpoint: json.RawMessage(`{"type":"fabric_ipv4_exit_pool","exit_pool_ids":["home-ipv4"]}`),
ClientConfig: json.RawMessage(`{
"vpn_fabric_route": {
"status": "planned",
@@ -735,6 +865,34 @@ func TestGetVPNClientProfileEnsuresFabricVPNPacketRouteIntents(t *testing.T) {
if session["preferred_transport"] != "fabric_service_channel_v1" || session["fallback_transport"] != "none" || session["backend_relay_allowed"] != false {
t.Fatalf("unexpected dataplane session transports: %#v", session)
}
request, ok := session["fabric_service_channel_request"].(map[string]any)
if !ok {
t.Fatalf("missing fabric service channel request in %#v", session)
}
if request["service_class"] != "vpn_packets" || request["source_role"] != "vpn-client" {
t.Fatalf("unexpected fabric service channel request: %#v", request)
}
target := request["target"].(map[string]any)
poolIDs := target["pool_ids"].([]any)
if target["kind"] != "pool" || target["service_role"] != "ipv4-egress" || len(poolIDs) != 1 || poolIDs[0] != "home-ipv4" {
t.Fatalf("unexpected fabric service channel target: %#v", target)
}
adapter := request["adapter_contract"].(map[string]any)
if adapter["adapter_may_select_endpoint"] != false || adapter["adapter_may_use_legacy_relay"] != false {
t.Fatalf("vpn adapter must not own transport decisions: %#v", adapter)
}
routeBundle, ok := session["fabric_route_bundle"].(map[string]any)
if !ok || routeBundle["legacy_visibility"] != "opaque_to_service_adapters" {
t.Fatalf("missing opaque route bundle: %#v", session["fabric_route_bundle"])
}
routeLease, ok := routeBundle["route_lease"].(map[string]any)
if !ok || routeLease["schema_version"] != "rap.fabric_route_lease.v1" || routeLease["service_visibility"] != "opaque_route_lease" {
t.Fatalf("missing route lease: %#v", routeBundle["route_lease"])
}
rebuildPolicy := routeLease["rebuild_policy"].(map[string]any)
if rebuildPolicy["owner"] != "fabric_farm" || rebuildPolicy["service_adapter_action"] != "keep_sending_packets_to_channel" {
t.Fatalf("unexpected route lease rebuild policy: %#v", rebuildPolicy)
}
if session["entry_node_id"] != "entry-1" || session["exit_node_id"] != "exit-1" {
t.Fatalf("unexpected dataplane session route: %#v", session)
}
@@ -920,6 +1078,88 @@ func TestNodeUpdatePlanSelectsMatchingReleaseArtifact(t *testing.T) {
}
}
func TestNodeUpdatePlanIncludesQuorumAuthorityWhenConfigured(t *testing.T) {
keys, err := clusterauth.GenerateKeyPair()
if err != nil {
t.Fatalf("generate key: %v", err)
}
store := &fakeRepository{
platformRole: PlatformRoleAdmin,
clusterAuthority: ClusterAuthorityKey{
ClusterAuthorityDescriptor: ClusterAuthorityDescriptor{
SchemaVersion: clusterauth.AuthoritySchemaVersion,
ClusterID: "cluster-1",
AuthorityState: "active",
KeyAlgorithm: clusterauth.AlgorithmEd25519,
PublicKey: keys.PublicKeyB64,
PublicKeyFingerprint: keys.Fingerprint,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
PrivateKey: keys.PrivateKeyB64,
QuorumDescriptor: &QuorumDescriptor{
SchemaVersion: clusterauth.QuorumSchemaVersion,
ClusterID: "cluster-1",
Epoch: "epoch-1",
Threshold: 1,
Members: []clusterauth.QuorumMember{
{
NodeID: "authority-1",
Role: "update-authority",
PublicKey: keys.PublicKeyB64,
PublicKeyFingerprint: keys.Fingerprint,
Scopes: []string{"update-authority"},
},
},
},
},
releaseVersions: []ReleaseVersion{
{
ID: "release-1",
ClusterID: "cluster-1",
Product: "rap-node-agent",
Version: "0.1.0-c17z26",
Channel: "dev",
Status: "active",
Artifacts: []ReleaseArtifact{
{ID: "docker", ClusterID: "cluster-1", Product: "rap-node-agent", Version: "0.1.0-c17z26", OS: "linux", Arch: "amd64", InstallType: "docker", Kind: "docker_image_tar", URL: "https://cache/agent.tar", SHA256: "docker-sha"},
},
},
},
nodeUpdatePolicies: map[string]NodeUpdatePolicy{
"node-1|rap-node-agent": {
ClusterID: "cluster-1",
NodeID: "node-1",
Product: "rap-node-agent",
Channel: "dev",
Strategy: "manual",
Enabled: true,
RollbackAllowed: true,
},
},
}
service := NewService(store)
plan, err := service.GetNodeUpdatePlan(context.Background(), GetNodeUpdatePlanInput{
ClusterID: "cluster-1",
NodeID: "node-1",
Product: "rap-node-agent",
CurrentVersion: "0.1.0-c17z25",
OS: "linux",
Arch: "amd64",
InstallType: "docker",
})
if err != nil {
t.Fatalf("update plan: %v", err)
}
if plan.AuthorityQuorum == nil {
t.Fatalf("update plan must include quorum envelope: %+v", plan)
}
if err := clusterauth.VerifyQuorumRaw(*store.clusterAuthority.QuorumDescriptor, plan.AuthorityPayload, *plan.AuthorityQuorum, "update-authority"); err != nil {
t.Fatalf("verify quorum authority: %v", err)
}
}
func TestNodeUpdatePlanAbsolutizesRelativeArtifactURLs(t *testing.T) {
store := &fakeRepository{
platformRole: PlatformRoleAdmin,
@@ -1914,6 +2154,7 @@ func TestGetNodeSyntheticMeshConfigScopesPrivateBootstrapPeersForOutboundOnlyNod
clusterNodes: []ClusterNode{
{ID: "node-local", RegistrationStatus: NodeRegistrationActive, HealthStatus: "healthy", MembershipStatus: "active", CreatedAt: now.Add(-2 * time.Hour), LastSeenAt: ptrTime(now)},
{ID: "node-peer", RegistrationStatus: NodeRegistrationActive, HealthStatus: "healthy", MembershipStatus: "active", CreatedAt: now.Add(-time.Hour), LastSeenAt: ptrTime(now.Add(-time.Second))},
{ID: "node-relay", RegistrationStatus: NodeRegistrationActive, HealthStatus: "healthy", MembershipStatus: "active", CreatedAt: now.Add(-90 * time.Minute), LastSeenAt: ptrTime(now.Add(-2 * time.Second))},
},
nodeRoles: map[string][]NodeRoleAssignment{
"node-local": {{NodeID: "node-local", Role: "core-mesh", Status: "active"}},
@@ -1954,8 +2195,8 @@ func TestGetNodeSyntheticMeshConfigScopesPrivateBootstrapPeersForOutboundOnlyNod
"endpoint_candidates": [{
"endpoint_id": "node-peer-lan",
"node_id": "node-peer",
"transport": "direct_http",
"address": "http://192.168.200.61:19133",
"transport": "direct_quic",
"address": "quic://192.168.200.61:19133",
"reachability": "private",
"connectivity_mode": "private_lan",
"priority": 1
@@ -1963,6 +2204,30 @@ func TestGetNodeSyntheticMeshConfigScopesPrivateBootstrapPeersForOutboundOnlyNod
}
}`),
}},
"node-relay": {{
ClusterID: "cluster-1",
NodeID: "node-relay",
ObservedAt: now,
Metadata: json.RawMessage(`{
"mesh_endpoint_report": {
"cluster_id": "cluster-1",
"node_id": "node-relay",
"peer_endpoint": "quic://relay.example.test:19131",
"transport": "direct_quic",
"connectivity_mode": "direct",
"region": "public",
"endpoint_candidates": [{
"endpoint_id": "node-relay-public",
"node_id": "node-relay",
"transport": "direct_quic",
"address": "quic://relay.example.test:19131",
"reachability": "public",
"connectivity_mode": "direct",
"priority": 1
}]
}
}`),
}},
},
})
service.now = func() time.Time { return now }
@@ -1982,7 +2247,7 @@ func TestGetNodeSyntheticMeshConfigScopesPrivateBootstrapPeersForOutboundOnlyNod
t.Fatalf("peer candidates = %+v, want relay-required candidate", cfg.PeerEndpointCandidates)
}
candidate := candidates[0]
if candidate.Transport != "relay" || candidate.Reachability != "relay" || candidate.ConnectivityMode != "relay_required" {
if candidate.Transport != "relay_quic" || candidate.Reachability != "relay" || candidate.ConnectivityMode != "relay_required" {
t.Fatalf("candidate not converted to relay required: %+v", candidate)
}
if !containsString(candidate.PolicyTags, "offsite-private-lan-blocked") || !containsString(candidate.PolicyTags, "relay-required") {
@@ -2002,10 +2267,10 @@ func TestGetNodeSyntheticMeshConfigScopesPrivateBootstrapPeersForOutboundOnlyNod
}
lease := cfg.RendezvousLeases[0]
if lease.PeerNodeID != "node-peer" ||
lease.RelayNodeID != "control-plane-relay" ||
lease.RelayEndpoint != "https://control.example.test" ||
lease.RelayNodeID != "node-relay" ||
lease.RelayEndpoint != "quic://relay.example.test:19131" ||
lease.Transport != "relay_control" ||
lease.Reason != "control_plane_bootstrap_relay" ||
lease.Reason != "farm_mesh_bootstrap_relay" ||
!lease.ControlPlaneOnly {
t.Fatalf("unexpected bootstrap rendezvous lease: %+v", lease)
}
@@ -2395,6 +2660,206 @@ func TestGetNodeSyntheticMeshConfigAppliesReplacementPathHintForExit(t *testing.
}
}
func TestRoutePathDecisionUsesRendezvousLeaseForPassiveNATRoute(t *testing.T) {
now := time.Date(2026, 5, 17, 3, 45, 0, 0, time.UTC)
route := SyntheticMeshRouteConfig{
RouteID: "route-a-b",
ClusterID: "cluster-1",
SourceNodeID: "node-a",
DestinationNodeID: "node-b",
Hops: []string{"node-a", "node-b"},
AllowedChannels: []string{"fabric_control", "route_control"},
ExpiresAt: now.Add(time.Hour),
}
decision := routePathDecisionForRoute(route, "node-a", []PeerRendezvousLease{{
LeaseID: "route-a-b-rv-node-b-via-node-r",
PeerNodeID: "node-b",
RelayNodeID: "node-r",
RelayEndpoint: "quic://node-r.example.test:19443",
Transport: "relay_control",
ConnectivityMode: "relay_required",
RouteIDs: []string{"route-a-b"},
Priority: 10,
ControlPlaneOnly: true,
IssuedAt: now,
ExpiresAt: now.Add(time.Hour),
Reason: "auto_rendezvous_required",
}}, newRendezvousRelayPolicy("node-a", nil, now), "generation-1", fabricServiceChannelRouteFeedback{})
if decision.DecisionSource != "rendezvous_relay_required" ||
decision.SelectedRelayID != "node-r" ||
decision.SelectedRelayEndpoint != "quic://node-r.example.test:19443" ||
decision.RendezvousPeerNodeID != "node-b" ||
decision.RendezvousLeaseID != "route-a-b-rv-node-b-via-node-r" ||
decision.RendezvousLeaseReason != "auto_rendezvous_required" ||
decision.NextHopID != "node-r" ||
decision.LocalRole != "entry" ||
strings.Join(decision.EffectiveHops, ",") != "node-a,node-r,node-b" ||
!decision.ControlPlaneOnly ||
decision.ProductionForwarding {
t.Fatalf("unexpected rendezvous route path decision: %+v", decision)
}
}
func TestScopedRendezvousLeasesKeepsOperatorPassiveNATLeaseWhenRelayFeedbackIsStale(t *testing.T) {
now := time.Date(2026, 5, 17, 5, 15, 0, 0, time.UTC)
route := SyntheticMeshRouteConfig{
RouteID: "route-a-b",
ClusterID: "cluster-1",
SourceNodeID: "node-a",
DestinationNodeID: "node-b",
Hops: []string{"node-a", "node-b"},
AllowedChannels: []string{"fabric_control", "route_control"},
ExpiresAt: now.Add(time.Hour),
}
lease := PeerRendezvousLease{
LeaseID: "route-a-b-rv-node-b-via-node-r",
PeerNodeID: "node-b",
RelayNodeID: "node-r",
RelayEndpoint: "quic://node-r.example.test:19443",
Transport: "relay_control",
ConnectivityMode: "relay_required",
RouteIDs: []string{"route-a-b"},
Priority: 10,
ControlPlaneOnly: true,
IssuedAt: now,
ExpiresAt: now.Add(time.Hour),
Reason: "operator_rendezvous_required_for_passive_nat",
}
relayPolicy := newRendezvousRelayPolicy("node-a", nil, now)
relayPolicy.addFeedback([]rendezvousRelayFeedbackEntry{{
RouteIDs: []string{"route-a-b"},
PeerNodeID: "node-b",
RelayNodeID: "node-r",
LeaseID: "route-a-b-rv-node-b-via-node-r",
ReporterNodeID: "node-a",
}})
leases := scopedRendezvousLeases([]PeerRendezvousLease{lease}, route, "node-a", relayPolicy, now)
if len(leases) != 1 || leases[0].LeaseID != lease.LeaseID {
t.Fatalf("operator passive NAT lease must remain scoped despite stale feedback: %+v", leases)
}
if report := relayPolicy.report(); report != nil && report.WithdrawnLeaseCount != 0 {
t.Fatalf("operator passive NAT lease must not be withdrawn: %+v", report)
}
}
func TestDerivedRendezvousLeaseCanSelectRelayOutsideOriginalPath(t *testing.T) {
now := time.Date(2026, 5, 17, 4, 30, 0, 0, time.UTC)
route := SyntheticMeshRouteConfig{
RouteID: "route-a-b",
ClusterID: "cluster-1",
SourceNodeID: "node-a",
DestinationNodeID: "node-b",
Hops: []string{"node-a", "node-b"},
AllowedChannels: []string{"fabric_control", "route_control"},
ExpiresAt: now.Add(time.Hour),
}
leases := derivedRendezvousLeases(route, map[string]string{}, map[string][]PeerEndpointCandidate{
"node-b": {
{
EndpointID: "node-b-private",
NodeID: "node-b",
Transport: "direct_quic",
Address: "quic://10.10.10.20:19131",
Reachability: "private",
ConnectivityMode: "private_lan",
Region: "remote-lan",
Priority: 5,
LastVerifiedAt: &now,
},
},
"node-r": {
{
EndpointID: "node-r-public",
NodeID: "node-r",
Transport: "direct_quic",
Address: "quic://203.0.113.10:19131",
Reachability: "public",
ConnectivityMode: "direct",
Region: "internet",
Priority: 10,
PolicyTags: []string{"fast-path"},
LastVerifiedAt: &now,
},
},
}, "node-a", endpointPerspective{Region: "home-lan"}, newRendezvousRelayPolicy("node-a", nil, now), now)
if len(leases) != 1 ||
leases[0].PeerNodeID != "node-b" ||
leases[0].RelayNodeID != "node-r" ||
leases[0].RelayEndpoint != "quic://203.0.113.10:19131" ||
leases[0].Reason != "auto_rendezvous_required" {
t.Fatalf("unexpected derived rendezvous leases: %+v", leases)
}
}
func TestGetNodeSyntheticMeshConfigIncludesRendezvousRelayOutsideOriginalHops(t *testing.T) {
now := time.Date(2026, 5, 17, 4, 15, 0, 0, time.UTC)
service := NewService(&fakeRepository{
testingFlags: EffectiveNodeTestingFlags{
Enabled: true,
SyntheticLinksEnabled: true,
},
routeIntents: []MeshRouteIntent{
{
ID: "route-a-b",
ClusterID: "cluster-1",
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
ServiceClass: "vpn_packets",
Status: "active",
Policy: json.RawMessage(`{
"synthetic_enabled": true,
"hops": ["node-a", "node-b"],
"allowed_channels": ["fabric_control", "route_control"],
"expires_at": "2026-05-17T05:15:00Z",
"rendezvous_leases": [
{
"lease_id": "route-a-b-rv-node-b-via-node-r",
"peer_node_id": "node-b",
"relay_node_id": "node-r",
"relay_endpoint": "quic://node-r.example.test:19443",
"transport": "relay_control",
"connectivity_mode": "relay_required",
"route_ids": ["route-a-b"],
"allowed_channels": ["fabric_control", "route_control"],
"priority": 10,
"control_plane_only": true,
"expires_at": "2026-05-17T05:15:00Z",
"reason": "auto_rendezvous_required"
}
]
}`),
UpdatedAt: now,
},
},
})
service.now = func() time.Time { return now }
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
ClusterID: "cluster-1",
NodeID: "node-r",
})
if err != nil {
t.Fatalf("get synthetic config: %v", err)
}
if len(cfg.Routes) != 1 || strings.Join(cfg.Routes[0].Hops, ",") != "node-a,node-r,node-b" {
t.Fatalf("relay scoped route missing effective hops: %+v", cfg.Routes)
}
if cfg.RoutePathDecisions == nil || len(cfg.RoutePathDecisions.Decisions) != 1 {
t.Fatalf("relay route path decision missing: %+v", cfg.RoutePathDecisions)
}
decision := cfg.RoutePathDecisions.Decisions[0]
if decision.SelectedRelayID != "node-r" ||
decision.LocalRole != "selected_relay" ||
decision.PreviousHopID != "node-a" ||
decision.NextHopID != "node-b" ||
strings.Join(decision.EffectiveHops, ",") != "node-a,node-r,node-b" {
t.Fatalf("unexpected relay scoped decision: %+v", decision)
}
}
func TestGetNodeSyntheticMeshConfigUsesRouteHealthDriftToReselectRelay(t *testing.T) {
now := time.Date(2026, 4, 28, 12, 30, 0, 0, time.UTC)
routeHealthMetadata, err := json.Marshal(map[string]any{