Refactor RDP proxy handling and update related tests
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package webingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const FabricServiceChannelEnvelopeSchema = "rap.web_ingress.fabric_service_channel_envelope.v1"
|
||||
|
||||
var (
|
||||
ErrFabricEnvelopeSignerRequired = errors.New("web ingress fabric envelope signer required")
|
||||
ErrFabricEnvelopeSenderRequired = errors.New("web ingress fabric envelope sender required")
|
||||
ErrFabricEnvelopeScopeRequired = errors.New("web ingress fabric envelope scope required")
|
||||
ErrFabricEnvelopeClassRequired = errors.New("web ingress fabric envelope service class required")
|
||||
)
|
||||
|
||||
type EnvelopeSigner interface {
|
||||
Sign(ctx context.Context, canonical []byte) (FabricEnvelopeSignature, error)
|
||||
}
|
||||
|
||||
type EnvelopeSender interface {
|
||||
Send(ctx context.Context, envelope SignedFabricServiceChannelEnvelope) (FabricResponse, error)
|
||||
}
|
||||
|
||||
type DefaultFabricBinder struct {
|
||||
Signer EnvelopeSigner
|
||||
Sender EnvelopeSender
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
type FabricServiceChannelEnvelope struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
RequestSchema string `json:"request_schema"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query,omitempty"`
|
||||
Host string `json:"host"`
|
||||
ServiceType string `json:"service_type"`
|
||||
Scope string `json:"scope"`
|
||||
ServiceClass string `json:"service_class"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
BodyBase64 string `json:"body_b64,omitempty"`
|
||||
ObservedAt string `json:"observed_at"`
|
||||
EnvelopedAt string `json:"enveloped_at"`
|
||||
}
|
||||
|
||||
type FabricEnvelopeSignature struct {
|
||||
KeyID string `json:"key_id"`
|
||||
Alg string `json:"alg"`
|
||||
Signature string `json:"signature"`
|
||||
SignedAt string `json:"signed_at,omitempty"`
|
||||
}
|
||||
|
||||
type SignedFabricServiceChannelEnvelope struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Envelope FabricServiceChannelEnvelope `json:"envelope"`
|
||||
Signature FabricEnvelopeSignature `json:"signature"`
|
||||
Canonical []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (b DefaultFabricBinder) Forward(ctx context.Context, request FabricRequest) (FabricResponse, error) {
|
||||
if b.Signer == nil {
|
||||
return FabricResponse{}, ErrFabricEnvelopeSignerRequired
|
||||
}
|
||||
if b.Sender == nil {
|
||||
return FabricResponse{}, ErrFabricEnvelopeSenderRequired
|
||||
}
|
||||
if strings.TrimSpace(request.Scope) == "" {
|
||||
return FabricResponse{}, ErrFabricEnvelopeScopeRequired
|
||||
}
|
||||
if strings.TrimSpace(request.ServiceClass) == "" {
|
||||
return FabricResponse{}, ErrFabricEnvelopeClassRequired
|
||||
}
|
||||
|
||||
envelope := b.envelope(request)
|
||||
canonical, err := json.Marshal(envelope)
|
||||
if err != nil {
|
||||
return FabricResponse{}, err
|
||||
}
|
||||
signature, err := b.Signer.Sign(ctx, canonical)
|
||||
if err != nil {
|
||||
return FabricResponse{}, err
|
||||
}
|
||||
return b.Sender.Send(ctx, SignedFabricServiceChannelEnvelope{
|
||||
SchemaVersion: SignedFabricServiceChannelEnvelopeSchema,
|
||||
Envelope: envelope,
|
||||
Signature: signature,
|
||||
Canonical: canonical,
|
||||
})
|
||||
}
|
||||
|
||||
func (b DefaultFabricBinder) envelope(request FabricRequest) FabricServiceChannelEnvelope {
|
||||
now := time.Now().UTC()
|
||||
if b.Now != nil {
|
||||
now = b.Now().UTC()
|
||||
}
|
||||
observedAt := request.ObservedAt.UTC()
|
||||
if observedAt.IsZero() {
|
||||
observedAt = now
|
||||
}
|
||||
return FabricServiceChannelEnvelope{
|
||||
SchemaVersion: FabricServiceChannelEnvelopeSchema,
|
||||
RequestSchema: strings.TrimSpace(request.SchemaVersion),
|
||||
Method: strings.ToUpper(strings.TrimSpace(request.Method)),
|
||||
Path: request.Path,
|
||||
Query: request.Query,
|
||||
Host: strings.TrimSpace(request.Host),
|
||||
ServiceType: strings.TrimSpace(request.ServiceType),
|
||||
Scope: strings.TrimSpace(request.Scope),
|
||||
ServiceClass: strings.TrimSpace(request.ServiceClass),
|
||||
Headers: canonicalHeaders(request.Headers),
|
||||
BodyBase64: base64.StdEncoding.EncodeToString(request.Body),
|
||||
ObservedAt: observedAt.Format(time.RFC3339Nano),
|
||||
EnvelopedAt: now.Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalHeaders(headers http.Header) map[string][]string {
|
||||
if len(headers) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := map[string][]string{}
|
||||
for key, values := range headers {
|
||||
canonicalKey := http.CanonicalHeaderKey(strings.TrimSpace(key))
|
||||
if canonicalKey == "" || !safeRequestHeader(canonicalKey) {
|
||||
continue
|
||||
}
|
||||
copied := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" {
|
||||
copied = append(copied, value)
|
||||
}
|
||||
}
|
||||
if len(copied) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Strings(copied)
|
||||
out[canonicalKey] = copied
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user