Refactor RDP proxy handling and update related tests
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package webingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFabricRuntimeReceiverVerifiesEnvelopeAndReturnsRuntimeResponse(t *testing.T) {
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("generate key: %v", err)
|
||||
}
|
||||
keyID := ed25519EnvelopeKeyID(publicKey)
|
||||
receiver := FabricRuntimeReceiver{
|
||||
Config: ReceiverConfig{ServiceType: "global-admin-runtime", Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
Keys: StaticEnvelopeKeyResolver{keyID: publicKey},
|
||||
Handler: recordingRuntimeHandler{response: FabricResponse{
|
||||
StatusCode: http.StatusCreated,
|
||||
Headers: http.Header{"X-RAP-Runtime": []string{"ok"}, "Set-Cookie": []string{"blocked"}},
|
||||
Body: []byte(`{"ok":true}`),
|
||||
}},
|
||||
Now: fixedEnvelopeNow,
|
||||
}
|
||||
payload := signedReceiverEnvelope(t, privateKey, keyID, FabricServiceChannelEnvelope{
|
||||
SchemaVersion: FabricServiceChannelEnvelopeSchema,
|
||||
RequestSchema: "rap.web_ingress.fabric_request.v1",
|
||||
Method: http.MethodPost,
|
||||
Path: "/platform-admin/root",
|
||||
Query: "tab=nodes",
|
||||
Host: "admin.example.test",
|
||||
ServiceType: "admin-ingress",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
Headers: map[string][]string{"X-Trace-Id": {"trace-1"}},
|
||||
BodyBase64: base64.StdEncoding.EncodeToString([]byte(`{"hello":"world"}`)),
|
||||
ObservedAt: "2026-05-17T00:00:00Z",
|
||||
EnvelopedAt: "2026-05-17T00:00:01Z",
|
||||
})
|
||||
|
||||
responsePayload, err := receiver.Receive(context.Background(), payload)
|
||||
if err != nil {
|
||||
t.Fatalf("receive: %v", err)
|
||||
}
|
||||
var response struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
StatusCode int `json:"status_code"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
BodyBase64 string `json:"body_b64"`
|
||||
}
|
||||
if err := json.Unmarshal(responsePayload, &response); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if response.SchemaVersion != FabricRuntimeResponseSchema ||
|
||||
response.StatusCode != http.StatusCreated ||
|
||||
response.Headers["X-Rap-Runtime"][0] != "ok" ||
|
||||
response.Headers["Set-Cookie"] != nil ||
|
||||
response.BodyBase64 != "eyJvayI6dHJ1ZX0=" {
|
||||
t.Fatalf("response = %+v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFabricRuntimeReceiverRejectsBadSignatureScopeClassAndStaleEnvelope(t *testing.T) {
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("generate key: %v", err)
|
||||
}
|
||||
keyID := ed25519EnvelopeKeyID(publicKey)
|
||||
receiver := FabricRuntimeReceiver{
|
||||
Config: ReceiverConfig{Scope: "platform", ServiceClasses: []string{"platform_admin"}},
|
||||
Keys: StaticEnvelopeKeyResolver{keyID: publicKey},
|
||||
Handler: recordingRuntimeHandler{},
|
||||
Now: fixedEnvelopeNow,
|
||||
}
|
||||
|
||||
base := FabricServiceChannelEnvelope{
|
||||
SchemaVersion: FabricServiceChannelEnvelopeSchema,
|
||||
RequestSchema: "rap.web_ingress.fabric_request.v1",
|
||||
Method: http.MethodGet,
|
||||
Path: "/platform-admin/root",
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ObservedAt: "2026-05-17T00:00:00Z",
|
||||
EnvelopedAt: "2026-05-17T00:00:01Z",
|
||||
}
|
||||
badSignature := signedReceiverEnvelope(t, privateKey, keyID, base)
|
||||
badSignature[len(badSignature)-2] = 'x'
|
||||
if _, err := receiver.Receive(context.Background(), badSignature); !errors.Is(err, ErrFabricEnvelopeSignatureInvalid) {
|
||||
t.Fatalf("bad signature err = %v", err)
|
||||
}
|
||||
|
||||
wrongScope := base
|
||||
wrongScope.Scope = "organization"
|
||||
if _, err := receiver.Receive(context.Background(), signedReceiverEnvelope(t, privateKey, keyID, wrongScope)); !errors.Is(err, ErrFabricEnvelopeUnauthorized) {
|
||||
t.Fatalf("wrong scope err = %v", err)
|
||||
}
|
||||
|
||||
wrongClass := base
|
||||
wrongClass.ServiceClass = "cluster_admin"
|
||||
if _, err := receiver.Receive(context.Background(), signedReceiverEnvelope(t, privateKey, keyID, wrongClass)); !errors.Is(err, ErrFabricEnvelopeUnauthorized) {
|
||||
t.Fatalf("wrong class err = %v", err)
|
||||
}
|
||||
|
||||
stale := base
|
||||
stale.EnvelopedAt = "2026-05-16T00:00:00Z"
|
||||
if _, err := receiver.Receive(context.Background(), signedReceiverEnvelope(t, privateKey, keyID, stale)); !errors.Is(err, ErrFabricEnvelopeUnauthorized) {
|
||||
t.Fatalf("stale err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFabricRuntimeReceiverRequiresTrustedKeyAndHandler(t *testing.T) {
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("generate key: %v", err)
|
||||
}
|
||||
keyID := ed25519EnvelopeKeyID(publicKey)
|
||||
payload := signedReceiverEnvelope(t, privateKey, keyID, FabricServiceChannelEnvelope{
|
||||
SchemaVersion: FabricServiceChannelEnvelopeSchema,
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ObservedAt: "2026-05-17T00:00:00Z",
|
||||
EnvelopedAt: "2026-05-17T00:00:01Z",
|
||||
})
|
||||
|
||||
_, err = (FabricRuntimeReceiver{Keys: StaticEnvelopeKeyResolver{keyID: publicKey}, Now: fixedEnvelopeNow}).Receive(context.Background(), payload)
|
||||
if !errors.Is(err, ErrFabricEnvelopeRuntimeRequired) {
|
||||
t.Fatalf("handler err = %v", err)
|
||||
}
|
||||
|
||||
_, err = (FabricRuntimeReceiver{Handler: recordingRuntimeHandler{}, Now: fixedEnvelopeNow}).Receive(context.Background(), payload)
|
||||
if !errors.Is(err, ErrFabricEnvelopeSignatureInvalid) {
|
||||
t.Fatalf("key resolver err = %v", err)
|
||||
}
|
||||
|
||||
_, otherPrivateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("generate other key: %v", err)
|
||||
}
|
||||
untrusted := signedReceiverEnvelope(t, otherPrivateKey, "other-key", FabricServiceChannelEnvelope{
|
||||
SchemaVersion: FabricServiceChannelEnvelopeSchema,
|
||||
Scope: "platform",
|
||||
ServiceClass: "platform_admin",
|
||||
ObservedAt: "2026-05-17T00:00:00Z",
|
||||
EnvelopedAt: "2026-05-17T00:00:01Z",
|
||||
})
|
||||
_, err = (FabricRuntimeReceiver{Keys: StaticEnvelopeKeyResolver{keyID: publicKey}, Handler: recordingRuntimeHandler{}, Now: fixedEnvelopeNow}).Receive(context.Background(), untrusted)
|
||||
if !errors.Is(err, ErrFabricEnvelopeUnauthorized) {
|
||||
t.Fatalf("untrusted key err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func signedReceiverEnvelope(t *testing.T, privateKey ed25519.PrivateKey, keyID string, envelope FabricServiceChannelEnvelope) []byte {
|
||||
t.Helper()
|
||||
canonical, err := json.Marshal(envelope)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal envelope: %v", err)
|
||||
}
|
||||
payload, err := json.Marshal(SignedFabricServiceChannelEnvelope{
|
||||
SchemaVersion: SignedFabricServiceChannelEnvelopeSchema,
|
||||
Envelope: envelope,
|
||||
Signature: FabricEnvelopeSignature{
|
||||
KeyID: keyID,
|
||||
Alg: "ed25519",
|
||||
Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, canonical)),
|
||||
SignedAt: "2026-05-17T00:00:01Z",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal signed envelope: %v", err)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
type recordingRuntimeHandler struct {
|
||||
request FabricRequest
|
||||
response FabricResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func (h recordingRuntimeHandler) HandleFabricRequest(_ context.Context, request FabricRequest) (FabricResponse, error) {
|
||||
h.request = request
|
||||
if h.err != nil {
|
||||
return FabricResponse{}, h.err
|
||||
}
|
||||
if h.response.StatusCode == 0 {
|
||||
h.response = FabricResponse{StatusCode: http.StatusOK, Body: []byte(`{"ready":true}`)}
|
||||
}
|
||||
return h.response, nil
|
||||
}
|
||||
Reference in New Issue
Block a user