Refactor RDP proxy handling and update related tests
This commit is contained in:
@@ -2,10 +2,12 @@ package supervisor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/client"
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/webingress"
|
||||
)
|
||||
|
||||
type Supervisor interface {
|
||||
@@ -14,6 +16,8 @@ type Supervisor interface {
|
||||
|
||||
type StubSupervisor struct {
|
||||
Version string
|
||||
WebIngressRuntimeEnabled bool
|
||||
WebIngressManager *webingress.Manager
|
||||
RemoteWorkspaceRealAdapter RemoteWorkspaceRealAdapterConfig
|
||||
}
|
||||
|
||||
@@ -56,6 +60,9 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
}
|
||||
if desiredState != "enabled" {
|
||||
payload["reason"] = "desired_state_not_enabled"
|
||||
if (serviceType == "public-ingress" || serviceType == "admin-ingress") && s.WebIngressManager != nil {
|
||||
payload["listener_status"] = s.WebIngressManager.Stop(context.Background())
|
||||
}
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "stopped",
|
||||
RuntimeMode: runtimeMode,
|
||||
@@ -74,6 +81,57 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if serviceType == "public-ingress" || serviceType == "admin-ingress" {
|
||||
contract := s.webIngressContract(serviceType, workload.Config)
|
||||
for key, value := range contract {
|
||||
payload[key] = value
|
||||
}
|
||||
if contract["contract_valid"] == true {
|
||||
payload["reason"] = "web_ingress_contract_ready"
|
||||
payload["execution_mode"] = "contract_probe"
|
||||
payload["traffic"] = "https_edge_to_fabric_service_channel"
|
||||
if contract["real_listener_requested"] == true && contract["real_listener_runtime_enabled"] != true {
|
||||
payload["reason"] = "web_ingress_real_listener_gate_disabled"
|
||||
payload["traffic"] = "blocked"
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "degraded",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if contract["real_listener_start_allowed"] == true && s.WebIngressManager != nil {
|
||||
listenerStatus := s.WebIngressManager.Apply(context.Background(), webIngressListenerConfig(serviceType, workload.Config))
|
||||
payload["listener_status"] = listenerStatus
|
||||
payload["ports_opened_by_runtime"] = listenerStatus.Running
|
||||
payload["ports_opened_by_stub"] = false
|
||||
if !listenerStatus.HTTPSRunning {
|
||||
payload["reason"] = "web_ingress_listener_partial"
|
||||
payload["traffic"] = "blocked"
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "degraded",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "running",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
payload["reason"] = "web_ingress_contract_invalid"
|
||||
payload["traffic"] = "blocked"
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "degraded",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if serviceType == "synthetic.echo" && runtimeMode == "native" {
|
||||
payload["reason"] = "internal_synthetic_echo_ready"
|
||||
payload["execution_mode"] = "builtin"
|
||||
@@ -85,6 +143,23 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if (serviceType == "vpn-exit" || serviceType == "ipv4-egress" || serviceType == "vpn-client") && runtimeMode == "native" {
|
||||
for key, value := range vpnFabricOnlyContract(serviceType, workload.Config) {
|
||||
payload[key] = value
|
||||
}
|
||||
payload["execution_mode"] = "contract_probe"
|
||||
payload["fabric_transport"] = "quic_only"
|
||||
payload["fabric_service_channel_required"] = true
|
||||
payload["backend_relay_fallback"] = false
|
||||
payload["legacy_protocol_compatibility"] = false
|
||||
payload["traffic"] = "fabric_service_channel_only"
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "running",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if serviceType == "rdp-worker" && runtimeMode == "native" && boolConfig(workload.Config, "adapter_contract_probe") {
|
||||
payload["reason"] = "remote_workspace_adapter_contract_probe_ready"
|
||||
payload["execution_mode"] = "contract_probe"
|
||||
@@ -126,6 +201,173 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
}
|
||||
}
|
||||
|
||||
func vpnFabricOnlyContract(serviceType string, config map[string]any) map[string]any {
|
||||
role := "vpn-client"
|
||||
reason := "vpn_client_node_contract_ready"
|
||||
serviceClass := "vpn_packets"
|
||||
internetEgress := false
|
||||
if serviceType == "vpn-exit" || serviceType == "ipv4-egress" {
|
||||
role = "ipv4-egress"
|
||||
reason = "ipv4_egress_contract_ready"
|
||||
internetEgress = true
|
||||
}
|
||||
contract := map[string]any{
|
||||
"schema_version": "rap.vpn.fabric_node_contract.v1",
|
||||
"reason": reason,
|
||||
"role": role,
|
||||
"service_class": serviceClass,
|
||||
"internet_egress": internetEgress,
|
||||
"exit_pool_id": stringConfig(config, "pool_id", ""),
|
||||
"exit_region": stringConfig(config, "region", ""),
|
||||
"allowed_cidrs": stringSliceConfig(config, "allowed_cidrs"),
|
||||
"dns_servers": stringSliceConfig(config, "dns_servers"),
|
||||
"client_policy_source": stringConfig(config, "client_policy_source", "fabric_access_policy"),
|
||||
"android_node_supported": serviceType == "vpn-client",
|
||||
"ipv4_exit_supported": internetEgress,
|
||||
"fabric_service_channel_required": true,
|
||||
"packet_runtime_status": "fabric_channel_binding_pending_runtime",
|
||||
"service_binding": vpnServiceBindingContract(serviceType, config),
|
||||
}
|
||||
return contract
|
||||
}
|
||||
|
||||
func vpnServiceBindingContract(serviceType string, config map[string]any) map[string]any {
|
||||
if serviceType == "vpn-exit" || serviceType == "ipv4-egress" {
|
||||
return map[string]any{
|
||||
"type": "ipv4_egress",
|
||||
"accepts_service_class": "vpn_packets",
|
||||
"accepts_from_fabric_only": true,
|
||||
"legacy_protocol_listener": false,
|
||||
"exit_pool_id": stringConfig(config, "pool_id", ""),
|
||||
"region": stringConfig(config, "region", ""),
|
||||
"allowed_cidrs": stringSliceConfig(config, "allowed_cidrs"),
|
||||
"dns_servers": stringSliceConfig(config, "dns_servers"),
|
||||
"internet_egress": true,
|
||||
"requires_host_packet_runtime": true,
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"type": "local_ipv4_ingress",
|
||||
"accepts_from": []string{"android_vpnservice_tun", "linux_tun", "host_service_port"},
|
||||
"service_class": "vpn_packets",
|
||||
"exit_selection": "pool",
|
||||
"preferred_exit_pool_id": stringConfig(config, "exit_pool_id", ""),
|
||||
"listen_tcp_ports": intSliceConfig(config, "listen_tcp_ports"),
|
||||
"listen_udp_ports": intSliceConfig(config, "listen_udp_ports"),
|
||||
"tun_required": true,
|
||||
"route_authority": "fabric_farm",
|
||||
"legacy_protocol_listener": false,
|
||||
"requires_fabric_node_runtime": true,
|
||||
}
|
||||
}
|
||||
|
||||
func webIngressListenerConfig(serviceType string, config map[string]any) webingress.ListenerConfig {
|
||||
return webingress.ListenerConfig{
|
||||
RuntimeConfig: webingress.RuntimeConfig{
|
||||
ServiceType: serviceType,
|
||||
Scope: stringConfig(config, "scope", ""),
|
||||
ServiceClasses: stringSliceConfig(config, "service_classes"),
|
||||
TLSMode: stringConfig(config, "tls_mode", "terminate"),
|
||||
HTTPPort: intConfig(config, "listen_http_port", 80),
|
||||
HTTPSPort: intConfig(config, "listen_https_port", 443),
|
||||
},
|
||||
HTTPAddr: stringConfig(config, "listen_http_addr", ":80"),
|
||||
HTTPSAddr: stringConfig(config, "listen_https_addr", ":443"),
|
||||
TLSCertFile: stringConfig(config, "tls_cert_file", ""),
|
||||
TLSKeyFile: stringConfig(config, "tls_key_file", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func (s StubSupervisor) webIngressContract(serviceType string, config map[string]any) map[string]any {
|
||||
httpPort := intConfig(config, "listen_http_port", 80)
|
||||
httpsPort := intConfig(config, "listen_https_port", 443)
|
||||
tlsMode := strings.TrimSpace(stringConfig(config, "tls_mode", "terminate"))
|
||||
serviceClasses := stringSliceConfig(config, "service_classes")
|
||||
scope := strings.TrimSpace(stringConfig(config, "scope", ""))
|
||||
realListenerRequested := boolConfig(config, "real_listener_enabled")
|
||||
allowedClasses := webIngressAllowedServiceClasses(serviceType)
|
||||
missing := []string{}
|
||||
if httpPort != 80 {
|
||||
missing = append(missing, "listen_http_port_must_be_80")
|
||||
}
|
||||
if httpsPort != 443 {
|
||||
missing = append(missing, "listen_https_port_must_be_443")
|
||||
}
|
||||
if tlsMode != "terminate" && tlsMode != "passthrough-approved-terminator" {
|
||||
missing = append(missing, "tls_mode_invalid")
|
||||
}
|
||||
if scope == "" {
|
||||
missing = append(missing, "scope_required")
|
||||
}
|
||||
if len(serviceClasses) == 0 {
|
||||
missing = append(missing, "service_classes_required")
|
||||
}
|
||||
for _, serviceClass := range serviceClasses {
|
||||
if !containsString(allowedClasses, serviceClass) {
|
||||
missing = append(missing, "service_class_not_allowed:"+serviceClass)
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"schema_version": "rap.web_ingress.workload_contract.v1",
|
||||
"contract_valid": len(missing) == 0,
|
||||
"missing_checks": missing,
|
||||
"service_edge_only": true,
|
||||
"authority_service": false,
|
||||
"fabric_transport": "quic_only",
|
||||
"http_between_fabric_nodes": false,
|
||||
"listen_http_port": httpPort,
|
||||
"listen_https_port": httpsPort,
|
||||
"tls_mode": tlsMode,
|
||||
"scope": scope,
|
||||
"service_classes": serviceClasses,
|
||||
"allowed_service_classes": allowedClasses,
|
||||
"fabric_service_channel_required": true,
|
||||
"runtime_roles_required": webIngressRuntimeRoles(serviceClasses),
|
||||
"payload_forwarding": "contract_only",
|
||||
"real_listener_requested": realListenerRequested,
|
||||
"real_listener_runtime_enabled": s.WebIngressRuntimeEnabled,
|
||||
"real_listener_start_allowed": len(missing) == 0 && realListenerRequested && s.WebIngressRuntimeEnabled,
|
||||
"runtime_handler_ready": len(missing) == 0,
|
||||
"runtime_handler_contract": "rap.web_ingress.runtime_response.v1",
|
||||
"runtime_handler_payload_status": "fabric_service_channel_binding_not_implemented",
|
||||
"fabric_envelope_schema": webingress.FabricServiceChannelEnvelopeSchema,
|
||||
"fabric_runtime_response_schema": "rap.web_ingress.fabric_runtime_response.v1",
|
||||
"fabric_envelope_signer": "ed25519_available",
|
||||
"fabric_envelope_sender": "mesh_request_response_runtime_adapter_available",
|
||||
"fabric_quic_stream": "web_ingress_forward",
|
||||
"fabric_quic_stream_id": 2,
|
||||
"fabric_runtime_receiver": "signed_envelope_receiver_available",
|
||||
"admin_runtime_dispatcher": "read_only_manifest_and_health_available",
|
||||
"control_api_binding": "read_only_projection_skeleton_available",
|
||||
"runtime_receiver_policy": "trusted_keys_and_service_class_allow_list",
|
||||
"ports_opened_by_stub": false,
|
||||
}
|
||||
}
|
||||
|
||||
func webIngressAllowedServiceClasses(serviceType string) []string {
|
||||
if serviceType == "admin-ingress" {
|
||||
return []string{"platform_admin", "cluster_admin"}
|
||||
}
|
||||
return []string{"organization_portal", "user_portal"}
|
||||
}
|
||||
|
||||
func webIngressRuntimeRoles(serviceClasses []string) []string {
|
||||
roles := []string{}
|
||||
for _, serviceClass := range serviceClasses {
|
||||
switch serviceClass {
|
||||
case "platform_admin":
|
||||
roles = append(roles, "global-admin-runtime", "identity-runtime", "policy-authority", "audit-sink")
|
||||
case "cluster_admin":
|
||||
roles = append(roles, "cluster-admin-runtime", "identity-runtime", "policy-authority", "audit-sink")
|
||||
case "organization_portal":
|
||||
roles = append(roles, "organization-portal-runtime", "identity-runtime", "policy-authority", "audit-sink")
|
||||
case "user_portal":
|
||||
roles = append(roles, "user-portal-runtime", "identity-runtime", "policy-authority", "audit-sink")
|
||||
}
|
||||
}
|
||||
return dedupeStrings(roles)
|
||||
}
|
||||
|
||||
func boolConfig(values map[string]any, key string) bool {
|
||||
if values == nil {
|
||||
return false
|
||||
@@ -144,6 +386,157 @@ func boolConfig(values map[string]any, key string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func intConfig(values map[string]any, key string, fallback int) int {
|
||||
if values == nil {
|
||||
return fallback
|
||||
}
|
||||
switch value := values[key].(type) {
|
||||
case int:
|
||||
return value
|
||||
case int64:
|
||||
return int(value)
|
||||
case float64:
|
||||
return int(value)
|
||||
case string:
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
func stringConfig(values map[string]any, key string, fallback string) string {
|
||||
if values == nil {
|
||||
return fallback
|
||||
}
|
||||
value, ok := values[key]
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func stringSliceConfig(values map[string]any, key string) []string {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
value, ok := values[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case []string:
|
||||
return dedupeStrings(typed)
|
||||
case []any:
|
||||
out := []string{}
|
||||
for _, item := range typed {
|
||||
if text, ok := item.(string); ok {
|
||||
out = append(out, strings.TrimSpace(text))
|
||||
}
|
||||
}
|
||||
return dedupeStrings(out)
|
||||
case string:
|
||||
parts := strings.Split(typed, ",")
|
||||
for index := range parts {
|
||||
parts[index] = strings.TrimSpace(parts[index])
|
||||
}
|
||||
return dedupeStrings(parts)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func intSliceConfig(values map[string]any, key string) []int {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
value, ok := values[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
add := func(out []int, item any) []int {
|
||||
switch typed := item.(type) {
|
||||
case int:
|
||||
if typed > 0 {
|
||||
out = append(out, typed)
|
||||
}
|
||||
case int64:
|
||||
if typed > 0 {
|
||||
out = append(out, int(typed))
|
||||
}
|
||||
case float64:
|
||||
if typed > 0 {
|
||||
out = append(out, int(typed))
|
||||
}
|
||||
case string:
|
||||
if parsed := intConfig(map[string]any{"value": typed}, "value", 0); parsed > 0 {
|
||||
out = append(out, parsed)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
out := []int{}
|
||||
switch typed := value.(type) {
|
||||
case []int:
|
||||
out = append(out, typed...)
|
||||
case []any:
|
||||
for _, item := range typed {
|
||||
out = add(out, item)
|
||||
}
|
||||
case string:
|
||||
for _, part := range strings.Split(typed, ",") {
|
||||
out = add(out, strings.TrimSpace(part))
|
||||
}
|
||||
default:
|
||||
out = add(out, typed)
|
||||
}
|
||||
seen := map[int]struct{}{}
|
||||
cleaned := make([]int, 0, len(out))
|
||||
for _, port := range out {
|
||||
if port <= 0 || port > 65535 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[port]; ok {
|
||||
continue
|
||||
}
|
||||
seen[port] = struct{}{}
|
||||
cleaned = append(cleaned, port)
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func dedupeStrings(values []string) []string {
|
||||
out := []string{}
|
||||
seen := map[string]struct{}{}
|
||||
for _, value := range values {
|
||||
normalized := strings.TrimSpace(value)
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[normalized]; ok {
|
||||
continue
|
||||
}
|
||||
seen[normalized] = struct{}{}
|
||||
out = append(out, normalized)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func containsString(values []string, needle string) bool {
|
||||
for _, value := range values {
|
||||
if value == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func remoteWorkspaceAdapterChannels() []map[string]any {
|
||||
return []map[string]any{
|
||||
{"name": "input", "direction": "client_to_adapter", "reliability": "reliable_ordered", "priority": "critical", "droppable": true, "may_block_input": false},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/client"
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/webingress"
|
||||
)
|
||||
|
||||
func TestStubSupervisorReportsDegradedForEnabledWorkload(t *testing.T) {
|
||||
@@ -73,6 +74,245 @@ func TestStubSupervisorReportsBuiltinFabricServicesRunning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorReportsVPNFabricOnlyContractsRunning(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test"}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "ipv4-egress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"pool_id": "us-los-angeles-ipv4",
|
||||
"region": "us-los-angeles",
|
||||
"allowed_cidrs": []any{"0.0.0.0/0"},
|
||||
"dns_servers": []any{"192.168.200.210"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ServiceType: "vpn-client",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"exit_pool_id": "us-los-angeles-ipv4",
|
||||
"listen_tcp_ports": []any{443, "8443"},
|
||||
"listen_udp_ports": "443,51820",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if len(statuses) != 2 {
|
||||
t.Fatalf("statuses length = %d", len(statuses))
|
||||
}
|
||||
for _, status := range statuses {
|
||||
if status.ReportedState != "running" {
|
||||
t.Fatalf("ReportedState = %q", status.ReportedState)
|
||||
}
|
||||
if status.StatusPayload["execution_mode"] != "contract_probe" {
|
||||
t.Fatalf("execution_mode = %v", status.StatusPayload["execution_mode"])
|
||||
}
|
||||
if status.StatusPayload["fabric_transport"] != "quic_only" {
|
||||
t.Fatalf("fabric_transport = %v", status.StatusPayload["fabric_transport"])
|
||||
}
|
||||
if status.StatusPayload["backend_relay_fallback"] != false {
|
||||
t.Fatalf("backend_relay_fallback = %v", status.StatusPayload["backend_relay_fallback"])
|
||||
}
|
||||
if status.StatusPayload["legacy_protocol_compatibility"] != false {
|
||||
t.Fatalf("legacy_protocol_compatibility = %v", status.StatusPayload["legacy_protocol_compatibility"])
|
||||
}
|
||||
}
|
||||
if statuses[0].StatusPayload["role"] != "ipv4-egress" || statuses[0].StatusPayload["internet_egress"] != true {
|
||||
t.Fatalf("ipv4 egress payload = %#v", statuses[0].StatusPayload)
|
||||
}
|
||||
if statuses[1].StatusPayload["role"] != "vpn-client" || statuses[1].StatusPayload["android_node_supported"] != true {
|
||||
t.Fatalf("vpn client payload = %#v", statuses[1].StatusPayload)
|
||||
}
|
||||
exitBinding := statuses[0].StatusPayload["service_binding"].(map[string]any)
|
||||
if exitBinding["type"] != "ipv4_egress" || exitBinding["accepts_from_fabric_only"] != true || exitBinding["exit_pool_id"] != "us-los-angeles-ipv4" {
|
||||
t.Fatalf("ipv4 egress binding = %#v", exitBinding)
|
||||
}
|
||||
clientBinding := statuses[1].StatusPayload["service_binding"].(map[string]any)
|
||||
if clientBinding["type"] != "local_ipv4_ingress" || clientBinding["preferred_exit_pool_id"] != "us-los-angeles-ipv4" || clientBinding["legacy_protocol_listener"] != false {
|
||||
t.Fatalf("vpn client binding = %#v", clientBinding)
|
||||
}
|
||||
if got := clientBinding["listen_tcp_ports"].([]int); len(got) != 2 || got[0] != 443 || got[1] != 8443 {
|
||||
t.Fatalf("listen_tcp_ports = %#v", got)
|
||||
}
|
||||
if got := clientBinding["listen_udp_ports"].([]int); len(got) != 2 || got[0] != 443 || got[1] != 51820 {
|
||||
t.Fatalf("listen_udp_ports = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorReportsWebIngressContractReady(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test"}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "admin-ingress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"listen_http_port": 80,
|
||||
"listen_https_port": 443,
|
||||
"tls_mode": "terminate",
|
||||
"scope": "platform",
|
||||
"service_classes": []any{"platform_admin", "cluster_admin"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "running" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
if payload["reason"] != "web_ingress_contract_ready" ||
|
||||
payload["fabric_transport"] != "quic_only" ||
|
||||
payload["http_between_fabric_nodes"] != false ||
|
||||
payload["authority_service"] != false ||
|
||||
payload["real_listener_start_allowed"] != false ||
|
||||
payload["runtime_handler_ready"] != true ||
|
||||
payload["runtime_handler_payload_status"] != "fabric_service_channel_binding_not_implemented" ||
|
||||
payload["ports_opened_by_stub"] != false {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
roles, ok := payload["runtime_roles_required"].([]string)
|
||||
if !ok || !containsString(roles, "global-admin-runtime") || !containsString(roles, "policy-authority") {
|
||||
t.Fatalf("runtime roles = %#v", payload["runtime_roles_required"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorBlocksWebIngressRealListenerWithoutRuntimeGate(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test"}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "admin-ingress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"listen_http_port": 80,
|
||||
"listen_https_port": 443,
|
||||
"tls_mode": "terminate",
|
||||
"scope": "platform",
|
||||
"service_classes": []any{"platform_admin"},
|
||||
"real_listener_enabled": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "degraded" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
if payload["reason"] != "web_ingress_real_listener_gate_disabled" ||
|
||||
payload["real_listener_requested"] != true ||
|
||||
payload["real_listener_runtime_enabled"] != false ||
|
||||
payload["real_listener_start_allowed"] != false ||
|
||||
payload["ports_opened_by_stub"] != false {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorAllowsWebIngressRealListenerGateButDoesNotOpenPorts(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test", WebIngressRuntimeEnabled: true}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "admin-ingress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"listen_http_port": 80,
|
||||
"listen_https_port": 443,
|
||||
"tls_mode": "terminate",
|
||||
"scope": "platform",
|
||||
"service_classes": []any{"platform_admin"},
|
||||
"real_listener_enabled": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "running" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
if payload["real_listener_requested"] != true ||
|
||||
payload["real_listener_runtime_enabled"] != true ||
|
||||
payload["real_listener_start_allowed"] != true ||
|
||||
payload["ports_opened_by_stub"] != false {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorStartsWebIngressManagerWhenRealListenerAllowed(t *testing.T) {
|
||||
manager := webingress.NewManager()
|
||||
statuses, err := (StubSupervisor{Version: "test", WebIngressRuntimeEnabled: true, WebIngressManager: manager}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "admin-ingress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"listen_http_port": 80,
|
||||
"listen_https_port": 443,
|
||||
"listen_http_addr": "127.0.0.1:0",
|
||||
"listen_https_addr": "127.0.0.1:0",
|
||||
"tls_mode": "terminate",
|
||||
"scope": "platform",
|
||||
"service_classes": []any{"platform_admin"},
|
||||
"real_listener_enabled": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "degraded" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
listenerStatus, ok := payload["listener_status"].(webingress.ListenerStatus)
|
||||
if !ok {
|
||||
t.Fatalf("listener_status = %#v", payload["listener_status"])
|
||||
}
|
||||
if !listenerStatus.HTTPRunning || listenerStatus.HTTPSRunning || listenerStatus.HTTPAddr == "" {
|
||||
t.Fatalf("listener status = %+v", listenerStatus)
|
||||
}
|
||||
if payload["reason"] != "web_ingress_listener_partial" || payload["ports_opened_by_runtime"] != true || payload["ports_opened_by_stub"] != false {
|
||||
t.Fatalf("payload = %#v", payload)
|
||||
}
|
||||
_ = manager.Stop(context.Background())
|
||||
}
|
||||
|
||||
func TestStubSupervisorBlocksInvalidWebIngressContract(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test"}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "public-ingress",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"listen_http_port": 8080,
|
||||
"listen_https_port": 443,
|
||||
"scope": "organization",
|
||||
"service_classes": []any{"platform_admin"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "degraded" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
if payload["reason"] != "web_ingress_contract_invalid" || payload["traffic"] != "blocked" {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
missing, ok := payload["missing_checks"].([]string)
|
||||
if !ok || !containsString(missing, "listen_http_port_must_be_80") || !containsString(missing, "service_class_not_allowed:platform_admin") {
|
||||
t.Fatalf("missing checks = %#v", payload["missing_checks"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorKeepsUnsupportedEnabledWorkloadDegraded(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{Version: "test"}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{ServiceType: "rdp-worker", DesiredState: "enabled", RuntimeMode: "container"},
|
||||
|
||||
Reference in New Issue
Block a user