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
@@ -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{