рабочий вариант, но скороть 10 МБит
This commit is contained in:
@@ -77,14 +77,10 @@ func (d AdminRuntimeDispatcher) HandleFabricRequest(ctx context.Context, request
|
||||
|
||||
func allowedAdminRuntimeScope(scope string, serviceClass string) bool {
|
||||
switch serviceClass {
|
||||
case "platform_admin":
|
||||
return scope == "platform"
|
||||
case "cluster_admin":
|
||||
return scope == "cluster"
|
||||
case "organization_portal":
|
||||
return scope == "organization"
|
||||
case "user_portal":
|
||||
return scope == "user" || scope == "organization"
|
||||
case "admin-ingress":
|
||||
return scope == "platform" || scope == "cluster"
|
||||
case "public-ingress":
|
||||
return scope == "organization" || scope == "user"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -143,18 +139,22 @@ func (d AdminRuntimeDispatcher) manifest(request FabricRequest) map[string]any {
|
||||
sections := []string{}
|
||||
actions := []string{}
|
||||
switch serviceClass {
|
||||
case "platform_admin":
|
||||
case "admin-ingress":
|
||||
sections = []string{"clusters", "nodes", "roles", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_platform_summary", "read_cluster_summaries", "read_node_status"}
|
||||
case "cluster_admin":
|
||||
sections = []string{"cluster", "nodes", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_cluster_summary", "read_node_status"}
|
||||
case "organization_portal":
|
||||
if request.Scope == "cluster" {
|
||||
sections = []string{"cluster", "nodes", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_cluster_summary", "read_node_status"}
|
||||
} else {
|
||||
actions = []string{"read_platform_summary", "read_cluster_summaries", "read_node_status"}
|
||||
}
|
||||
case "public-ingress":
|
||||
sections = []string{"organization", "sessions", "resources", "audit"}
|
||||
actions = []string{"read_organization_summary", "read_sessions"}
|
||||
case "user_portal":
|
||||
sections = []string{"profile", "sessions", "resources"}
|
||||
actions = []string{"read_profile", "read_sessions"}
|
||||
if request.Scope == "user" {
|
||||
sections = []string{"profile", "sessions", "resources"}
|
||||
actions = []string{"read_profile", "read_sessions"}
|
||||
} else {
|
||||
actions = []string{"read_organization_summary", "read_sessions"}
|
||||
}
|
||||
default:
|
||||
sections = []string{"status"}
|
||||
actions = []string{"read_status"}
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestAdminRuntimeDispatcherReturnsHealthAndManifest(t *testing.T) {
|
||||
Method: http.MethodGet,
|
||||
Path: "/readyz",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("health: %v", err)
|
||||
@@ -25,9 +25,9 @@ func TestAdminRuntimeDispatcherReturnsHealthAndManifest(t *testing.T) {
|
||||
|
||||
manifest, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/ui-manifest",
|
||||
Path: "/admin/ui-manifest",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("manifest: %v", err)
|
||||
@@ -51,9 +51,9 @@ func TestAdminRuntimeDispatcherBlocksMutationsAndUnknownProjection(t *testing.T)
|
||||
|
||||
mutation, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodPost,
|
||||
Path: "/platform-admin/nodes",
|
||||
Path: "/admin/nodes",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("mutation: %v", err)
|
||||
@@ -68,9 +68,9 @@ func TestAdminRuntimeDispatcherBlocksMutationsAndUnknownProjection(t *testing.T)
|
||||
|
||||
projection, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/nodes",
|
||||
Path: "/admin/nodes",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("projection: %v", err)
|
||||
@@ -88,9 +88,9 @@ func TestAdminRuntimeDispatcherRejectsInvalidScopeClassPair(t *testing.T) {
|
||||
dispatcher := AdminRuntimeDispatcher{ProjectionClient: &recordingProjectionClient{}, Now: fixedEnvelopeNow}
|
||||
response, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/ui-manifest",
|
||||
Path: "/admin/ui-manifest",
|
||||
Scope: "organization",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("projection: %v", err)
|
||||
@@ -118,11 +118,11 @@ func TestAdminRuntimeDispatcherUsesControlAPIProjectionClientForReadRequests(t *
|
||||
|
||||
response, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/nodes",
|
||||
Path: "/admin/nodes",
|
||||
Query: "limit=10",
|
||||
Host: "admin.example.test",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("projection: %v", err)
|
||||
@@ -133,10 +133,10 @@ func TestAdminRuntimeDispatcherUsesControlAPIProjectionClientForReadRequests(t *
|
||||
string(response.Body) != `{"schema_version":"control.projection.v1","ok":true}` {
|
||||
t.Fatalf("response = %+v body=%s", response, string(response.Body))
|
||||
}
|
||||
if client.request.Path != "/platform-admin/nodes" ||
|
||||
if client.request.Path != "/admin/nodes" ||
|
||||
client.request.Query != "limit=10" ||
|
||||
client.request.Scope != "platform" ||
|
||||
client.request.ServiceClass != "platform_admin" {
|
||||
client.request.ServiceClass != "admin-ingress" {
|
||||
t.Fatalf("request = %+v", client.request)
|
||||
}
|
||||
}
|
||||
@@ -145,9 +145,9 @@ func TestAdminRuntimeDispatcherReportsProjectionClientFailure(t *testing.T) {
|
||||
dispatcher := AdminRuntimeDispatcher{ProjectionClient: failingProjectionClient{}, Now: fixedEnvelopeNow}
|
||||
response, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/nodes",
|
||||
Path: "/admin/nodes",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("projection: %v", err)
|
||||
@@ -175,9 +175,9 @@ func TestAdminRuntimeDispatcherRejectsInvalidProjectionResponseSchema(t *testing
|
||||
}
|
||||
response, err := dispatcher.HandleFabricRequest(context.Background(), FabricRequest{
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/nodes",
|
||||
Path: "/admin/nodes",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ServiceClass: "admin-ingress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("projection: %v", err)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
type ListenerConfig struct {
|
||||
RuntimeConfig
|
||||
HTTPAddr string
|
||||
HTTPSAddr string
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
@@ -23,9 +22,7 @@ type ListenerConfig struct {
|
||||
type ListenerStatus struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Running bool `json:"running"`
|
||||
HTTPRunning bool `json:"http_running"`
|
||||
HTTPSRunning bool `json:"https_running"`
|
||||
HTTPAddr string `json:"http_addr,omitempty"`
|
||||
HTTPSAddr string `json:"https_addr,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
@@ -34,7 +31,6 @@ type ListenerStatus struct {
|
||||
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
http *http.Server
|
||||
https *http.Server
|
||||
status ListenerStatus
|
||||
now func() time.Time
|
||||
@@ -56,19 +52,9 @@ func (m *Manager) Apply(ctx context.Context, cfg ListenerConfig) ListenerStatus
|
||||
ObservedAt: m.observedAt(),
|
||||
}
|
||||
errorsOut := []string{}
|
||||
if strings.TrimSpace(cfg.HTTPAddr) == "" {
|
||||
cfg.HTTPAddr = ":80"
|
||||
}
|
||||
if strings.TrimSpace(cfg.HTTPSAddr) == "" {
|
||||
cfg.HTTPSAddr = ":443"
|
||||
}
|
||||
if server, addr, err := startHTTPServer(ctx, cfg.HTTPAddr, runtime.HTTPHandler()); err == nil {
|
||||
m.http = server
|
||||
status.HTTPRunning = true
|
||||
status.HTTPAddr = addr
|
||||
} else {
|
||||
errorsOut = append(errorsOut, "http:"+err.Error())
|
||||
}
|
||||
if cfg.TLSCertFile == "" || cfg.TLSKeyFile == "" {
|
||||
errorsOut = append(errorsOut, "https:tls_cert_file_and_key_file_required")
|
||||
} else if server, addr, err := startHTTPSServer(ctx, cfg.HTTPSAddr, cfg.TLSCertFile, cfg.TLSKeyFile, runtime.HTTPSHandler()); err == nil {
|
||||
@@ -78,7 +64,7 @@ func (m *Manager) Apply(ctx context.Context, cfg ListenerConfig) ListenerStatus
|
||||
} else {
|
||||
errorsOut = append(errorsOut, "https:"+err.Error())
|
||||
}
|
||||
status.Running = status.HTTPRunning || status.HTTPSRunning
|
||||
status.Running = status.HTTPSRunning
|
||||
if len(errorsOut) > 0 {
|
||||
status.Errors = errorsOut
|
||||
if status.Running {
|
||||
@@ -118,10 +104,6 @@ func (m *Manager) Status() ListenerStatus {
|
||||
|
||||
func (m *Manager) stopLocked(ctx context.Context) error {
|
||||
var out error
|
||||
if m.http != nil {
|
||||
out = errors.Join(out, m.http.Shutdown(ctx))
|
||||
m.http = nil
|
||||
}
|
||||
if m.https != nil {
|
||||
out = errors.Join(out, m.https.Shutdown(ctx))
|
||||
m.https = nil
|
||||
@@ -137,24 +119,6 @@ func (m *Manager) observedAt() string {
|
||||
return now.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
func startHTTPServer(ctx context.Context, addr string, handler http.Handler) (*http.Server, string, error) {
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
server := &http.Server{Handler: handler, ReadHeaderTimeout: 5 * time.Second}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = server.Shutdown(context.Background())
|
||||
}()
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
_ = server.Close()
|
||||
}
|
||||
}()
|
||||
return server, listener.Addr().String(), nil
|
||||
}
|
||||
|
||||
func startHTTPSServer(ctx context.Context, addr, certFile, keyFile string, handler http.Handler) (*http.Server, string, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -16,37 +15,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestManagerStartsHTTPRedirectAndStops(t *testing.T) {
|
||||
manager := NewManager()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
status := manager.Apply(ctx, ListenerConfig{
|
||||
RuntimeConfig: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
HTTPAddr: "127.0.0.1:0",
|
||||
HTTPSAddr: "127.0.0.1:0",
|
||||
})
|
||||
if !status.HTTPRunning || status.HTTPSRunning || !status.Running || status.HTTPAddr == "" {
|
||||
t.Fatalf("status = %+v", status)
|
||||
}
|
||||
if status.Reason != "partial" || !containsError(status.Errors, "https:tls_cert_file_and_key_file_required") {
|
||||
t.Fatalf("status = %+v", status)
|
||||
}
|
||||
client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
|
||||
resp, err := client.Get("http://" + status.HTTPAddr + "/cluster-admin")
|
||||
if err != nil {
|
||||
t.Fatalf("http get: %v", err)
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusPermanentRedirect {
|
||||
t.Fatalf("status = %d", resp.StatusCode)
|
||||
}
|
||||
stopped := manager.Stop(context.Background())
|
||||
if stopped.Running || stopped.Reason != "stopped" {
|
||||
t.Fatalf("stopped = %+v", stopped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerStartsHTTPSWhenCertificateProvided(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
certFile, keyFile := writeSelfSignedCert(t, dir)
|
||||
@@ -56,12 +24,29 @@ func TestManagerStartsHTTPSWhenCertificateProvided(t *testing.T) {
|
||||
|
||||
status := manager.Apply(ctx, ListenerConfig{
|
||||
RuntimeConfig: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
HTTPAddr: "127.0.0.1:0",
|
||||
HTTPSAddr: "127.0.0.1:0",
|
||||
TLSCertFile: certFile,
|
||||
TLSKeyFile: keyFile,
|
||||
})
|
||||
if !status.HTTPRunning || !status.HTTPSRunning || status.HTTPAddr == "" || status.HTTPSAddr == "" || len(status.Errors) != 0 {
|
||||
if !status.HTTPSRunning || !status.Running || status.HTTPSAddr == "" || len(status.Errors) != 0 {
|
||||
t.Fatalf("status = %+v", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerDoesNotStartHTTPWithoutExplicitAddress(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
certFile, keyFile := writeSelfSignedCert(t, dir)
|
||||
manager := NewManager()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
status := manager.Apply(ctx, ListenerConfig{
|
||||
RuntimeConfig: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
HTTPSAddr: "127.0.0.1:0",
|
||||
TLSCertFile: certFile,
|
||||
TLSKeyFile: keyFile,
|
||||
})
|
||||
if !status.HTTPSRunning || !status.Running || status.HTTPSAddr == "" || len(status.Errors) != 0 {
|
||||
t.Fatalf("status = %+v", status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ type RuntimeConfig struct {
|
||||
Scope string
|
||||
ServiceClasses []string
|
||||
TLSMode string
|
||||
HTTPPort int
|
||||
HTTPSPort int
|
||||
}
|
||||
|
||||
@@ -59,23 +58,6 @@ type Response struct {
|
||||
ObservedAt string `json:"observed_at"`
|
||||
}
|
||||
|
||||
func (r Runtime) HTTPHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, "/.well-known/acme-challenge/") {
|
||||
writeJSON(w, http.StatusNotFound, r.response("not_found", "acme_challenge_backend_not_configured", ""))
|
||||
return
|
||||
}
|
||||
if req.URL.Path == "/healthz" || req.URL.Path == "/readyz" {
|
||||
writeJSON(w, http.StatusOK, r.response("ready", "http_redirect_runtime_ready", ""))
|
||||
return
|
||||
}
|
||||
target := "https://" + req.Host + req.URL.RequestURI()
|
||||
w.Header().Set("Location", target)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(http.StatusPermanentRedirect)
|
||||
})
|
||||
}
|
||||
|
||||
func (r Runtime) HTTPSHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/healthz" || req.URL.Path == "/readyz" {
|
||||
@@ -98,7 +80,7 @@ func (r Runtime) HTTPSHandler() http.Handler {
|
||||
writeJSON(w, http.StatusNotImplemented, r.response("blocked", "fabric_service_channel_binding_not_implemented", serviceClass))
|
||||
return
|
||||
}
|
||||
scope := scopeForServiceClass(serviceClass, r.Config.Scope)
|
||||
scope := scopeForServiceClass(serviceClass, req.URL.Path, r.Config.Scope)
|
||||
body, err := io.ReadAll(http.MaxBytesReader(w, req.Body, 1<<20))
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusRequestEntityTooLarge, r.response("blocked", "request_body_too_large", serviceClass))
|
||||
@@ -146,32 +128,38 @@ func (r Runtime) response(status, reason, serviceClass string) Response {
|
||||
}
|
||||
}
|
||||
|
||||
func scopeForServiceClass(serviceClass string, fallback string) string {
|
||||
func scopeForServiceClass(serviceClass string, path string, fallback string) string {
|
||||
path = strings.Trim(strings.ToLower(path), "/")
|
||||
switch strings.TrimSpace(serviceClass) {
|
||||
case "platform_admin":
|
||||
return "platform"
|
||||
case "cluster_admin":
|
||||
return "cluster"
|
||||
case "organization_portal":
|
||||
return "organization"
|
||||
case "user_portal":
|
||||
return "user"
|
||||
case "admin-ingress":
|
||||
if strings.HasPrefix(path, "clusters/") {
|
||||
return "cluster"
|
||||
}
|
||||
return firstNonEmpty(strings.TrimSpace(fallback), "platform")
|
||||
case "public-ingress":
|
||||
if strings.HasPrefix(path, "users/") {
|
||||
return "user"
|
||||
}
|
||||
return firstNonEmpty(strings.TrimSpace(fallback), "organization")
|
||||
default:
|
||||
return strings.TrimSpace(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(value string, fallback string) string {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return strings.TrimSpace(fallback)
|
||||
}
|
||||
|
||||
func serviceClassFromPath(path string) string {
|
||||
path = strings.Trim(strings.ToLower(path), "/")
|
||||
switch {
|
||||
case strings.HasPrefix(path, "platform-admin"):
|
||||
return "platform_admin"
|
||||
case strings.HasPrefix(path, "cluster-admin"):
|
||||
return "cluster_admin"
|
||||
case strings.HasPrefix(path, "organizations/"):
|
||||
return "organization_portal"
|
||||
case strings.HasPrefix(path, "users/"):
|
||||
return "user_portal"
|
||||
case strings.HasPrefix(path, "admin/"), strings.HasPrefix(path, "platform/"), strings.HasPrefix(path, "clusters/"):
|
||||
return "admin-ingress"
|
||||
case strings.HasPrefix(path, "public/"), strings.HasPrefix(path, "organizations/"), strings.HasPrefix(path, "users/"):
|
||||
return "public-ingress"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -10,31 +10,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHTTPHandlerRedirectsToHTTPS(t *testing.T) {
|
||||
runtime := Runtime{Config: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform"}}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://admin.example.test/cluster-admin/dashboard?x=1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
runtime.HTTPHandler().ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusPermanentRedirect {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
if rec.Header().Get("Location") != "https://admin.example.test/cluster-admin/dashboard?x=1" {
|
||||
t.Fatalf("Location = %q", rec.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPSHandlerBlocksUnknownServiceClass(t *testing.T) {
|
||||
runtime := Runtime{
|
||||
Config: RuntimeConfig{
|
||||
ServiceType: "public-ingress",
|
||||
Scope: "organization",
|
||||
ServiceClasses: []string{"organization_portal", "user_portal"},
|
||||
ServiceClasses: []string{"public-ingress", "public-ingress"},
|
||||
},
|
||||
Now: fixedNow,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "https://org.example.test/platform-admin/root", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "https://org.example.test/admin/root", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
runtime.HTTPSHandler().ServeHTTP(rec, req)
|
||||
@@ -46,7 +31,7 @@ func TestHTTPSHandlerBlocksUnknownServiceClass(t *testing.T) {
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload.Reason != "service_class_not_allowed" || payload.ServiceClass != "platform_admin" || payload.Scope != "organization" {
|
||||
if payload.Reason != "service_class_not_allowed" || payload.ServiceClass != "admin-ingress" || payload.Scope != "organization" {
|
||||
t.Fatalf("payload = %+v", payload)
|
||||
}
|
||||
}
|
||||
@@ -56,11 +41,11 @@ func TestHTTPSHandlerRequiresFabricServiceChannelBinding(t *testing.T) {
|
||||
Config: RuntimeConfig{
|
||||
ServiceType: "admin-ingress",
|
||||
Scope: "platform",
|
||||
ServiceClasses: []string{"platform_admin", "cluster_admin"},
|
||||
ServiceClasses: []string{"admin-ingress", "admin-ingress"},
|
||||
},
|
||||
Now: fixedNow,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/platform-admin/root", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/admin/root", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
runtime.HTTPSHandler().ServeHTTP(rec, req)
|
||||
@@ -73,7 +58,7 @@ func TestHTTPSHandlerRequiresFabricServiceChannelBinding(t *testing.T) {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload.Reason != "fabric_service_channel_binding_not_implemented" ||
|
||||
payload.ServiceClass != "platform_admin" ||
|
||||
payload.ServiceClass != "admin-ingress" ||
|
||||
payload.ObservedAt != "2026-05-17T00:00:00Z" {
|
||||
t.Fatalf("payload = %+v", payload)
|
||||
}
|
||||
@@ -91,13 +76,13 @@ func TestHTTPSHandlerForwardsAllowedRequestToBinder(t *testing.T) {
|
||||
Config: RuntimeConfig{
|
||||
ServiceType: "admin-ingress",
|
||||
Scope: "platform",
|
||||
ServiceClasses: []string{"platform_admin", "cluster_admin"},
|
||||
ServiceClasses: []string{"admin-ingress", "admin-ingress"},
|
||||
},
|
||||
Binder: binder,
|
||||
Now: fixedNow,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/platform-admin/root?tab=nodes", strings.NewReader(`{"hello":"world"}`))
|
||||
req.Header.Set("X-RAP-Service-Class", "platform_admin")
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/admin/root?tab=nodes", strings.NewReader(`{"hello":"world"}`))
|
||||
req.Header.Set("X-RAP-Service-Class", "admin-ingress")
|
||||
req.Header.Set("Authorization", "Bearer secret")
|
||||
req.Header.Set("X-Trace-ID", "trace-1")
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -110,9 +95,9 @@ func TestHTTPSHandlerForwardsAllowedRequestToBinder(t *testing.T) {
|
||||
if rec.Header().Get("X-RAP-Result") != "accepted" || rec.Body.String() != `{"ok":true}` {
|
||||
t.Fatalf("unexpected response headers=%v body=%s", rec.Header(), rec.Body.String())
|
||||
}
|
||||
if binder.request.ServiceClass != "platform_admin" ||
|
||||
if binder.request.ServiceClass != "admin-ingress" ||
|
||||
binder.request.Scope != "platform" ||
|
||||
binder.request.Path != "/platform-admin/root" ||
|
||||
binder.request.Path != "/admin/root" ||
|
||||
binder.request.Query != "tab=nodes" ||
|
||||
string(binder.request.Body) != `{"hello":"world"}` {
|
||||
t.Fatalf("request = %+v", binder.request)
|
||||
@@ -128,12 +113,12 @@ func TestHTTPSHandlerDerivesFabricScopeFromServiceClass(t *testing.T) {
|
||||
Config: RuntimeConfig{
|
||||
ServiceType: "admin-ingress",
|
||||
Scope: "platform",
|
||||
ServiceClasses: []string{"platform_admin", "cluster_admin"},
|
||||
ServiceClasses: []string{"admin-ingress", "admin-ingress"},
|
||||
},
|
||||
Binder: binder,
|
||||
Now: fixedNow,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "https://admin.example.test/cluster-admin/ui-manifest", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "https://admin.example.test/clusters/ui-manifest", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
runtime.HTTPSHandler().ServeHTTP(rec, req)
|
||||
@@ -141,18 +126,18 @@ func TestHTTPSHandlerDerivesFabricScopeFromServiceClass(t *testing.T) {
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if binder.request.ServiceClass != "cluster_admin" || binder.request.Scope != "cluster" {
|
||||
if binder.request.ServiceClass != "admin-ingress" || binder.request.Scope != "cluster" {
|
||||
t.Fatalf("request = %+v", binder.request)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPSHandlerReportsBinderFailure(t *testing.T) {
|
||||
runtime := Runtime{
|
||||
Config: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
Config: RuntimeConfig{ServiceType: "admin-ingress", Scope: "platform", ServiceClasses: []string{"admin-ingress"}},
|
||||
Binder: failingBinder{},
|
||||
Now: fixedNow,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/platform-admin/root", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "https://admin.example.test/admin/root", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
runtime.HTTPSHandler().ServeHTTP(rec, req)
|
||||
|
||||
Reference in New Issue
Block a user