feat(gateway): fetch sealed Apollo session work
Verify Data Plane / gateway (push) Successful in 3m33s

This commit is contained in:
sechmachine
2026-07-29 11:27:31 +07:00
parent 8a302cd8cc
commit 844e548c95
9 changed files with 217 additions and 44 deletions
+3 -13
View File
@@ -30,7 +30,6 @@ func main() {
func run() error { func run() error {
var listen, advertiseAddress, controlPlane, certFile, keyFile, clientCAFile string var listen, advertiseAddress, controlPlane, certFile, keyFile, clientCAFile string
var gatewayID, instanceIdentity, certificateIdentity, publicIdentity string var gatewayID, instanceIdentity, certificateIdentity, publicIdentity string
var providerManagement, providerRTSPAddress, providerRTSPURL, providerIdentity string
flag.StringVar(&listen, "listen", "0.0.0.0:443", "gateway QUIC listen address") flag.StringVar(&listen, "listen", "0.0.0.0:443", "gateway QUIC listen address")
flag.StringVar(&advertiseAddress, "advertise-address", "", "client-visible gateway address host:port") flag.StringVar(&advertiseAddress, "advertise-address", "", "client-visible gateway address host:port")
flag.StringVar(&controlPlane, "control-plane", "", "Connection Server HTTPS base URL") flag.StringVar(&controlPlane, "control-plane", "", "Connection Server HTTPS base URL")
@@ -41,12 +40,8 @@ func run() error {
flag.StringVar(&instanceIdentity, "instance-identity", "", "gateway instance identity") flag.StringVar(&instanceIdentity, "instance-identity", "", "gateway instance identity")
flag.StringVar(&certificateIdentity, "certificate-identity", "", "gateway certificate identity") flag.StringVar(&certificateIdentity, "certificate-identity", "", "gateway certificate identity")
flag.StringVar(&publicIdentity, "public-identity", "gateway", "gateway public identity") flag.StringVar(&publicIdentity, "public-identity", "gateway", "gateway public identity")
flag.StringVar(&providerManagement, "provider-management", "", "internal Apollo management URL")
flag.StringVar(&providerRTSPAddress, "provider-rtsp-address", "", "internal Apollo RTSP address")
flag.StringVar(&providerRTSPURL, "provider-rtsp-url", "", "internal Apollo RTSP URL")
flag.StringVar(&providerIdentity, "provider-identity", "", "enrolled Apollo identity unique-id#fingerprint")
flag.Parse() flag.Parse()
for name, value := range map[string]string{"control-plane": controlPlane, "advertise-address": advertiseAddress, "cert": certFile, "key": keyFile, "client-ca": clientCAFile, "gateway-id": gatewayID, "instance-identity": instanceIdentity, "certificate-identity": certificateIdentity, "provider-management": providerManagement, "provider-rtsp-address": providerRTSPAddress, "provider-rtsp-url": providerRTSPURL, "provider-identity": providerIdentity} { for name, value := range map[string]string{"control-plane": controlPlane, "advertise-address": advertiseAddress, "cert": certFile, "key": keyFile, "client-ca": clientCAFile, "gateway-id": gatewayID, "instance-identity": instanceIdentity, "certificate-identity": certificateIdentity} {
if value == "" { if value == "" {
return fmt.Errorf("-%s is required", name) return fmt.Errorf("-%s is required", name)
} }
@@ -54,24 +49,19 @@ func run() error {
if err := validateAdvertisedAddress(advertiseAddress); err != nil { if err := validateAdvertisedAddress(advertiseAddress); err != nil {
return err return err
} }
expectedIdentity, err := parseProviderIdentity(providerIdentity)
if err != nil {
return err
}
serverTLS, clientTLS, err := loadTLS(certFile, keyFile, clientCAFile) serverTLS, clientTLS, err := loadTLS(certFile, keyFile, clientCAFile)
if err != nil { if err != nil {
return err return err
} }
transport := &http.Transport{TLSClientConfig: clientTLS} transport := &http.Transport{TLSClientConfig: clientTLS}
controlPlaneClient := gateway.NewControlPlaneClient(controlPlane, &http.Client{Transport: transport, Timeout: 5 * time.Second}) controlPlaneClient := gateway.NewControlPlaneClient(controlPlane, &http.Client{Transport: transport, Timeout: 5 * time.Second})
providerBackend := gateway.NewNativeApolloBackend(providerManagement, providerRTSPAddress, providerRTSPURL, &http.Client{Timeout: 5 * time.Second}) provider := gateway.NewApolloAdapter(gateway.NewNativeApolloBackend(), gateway.ProviderIdentity{})
provider := gateway.NewApolloAdapter(providerBackend, expectedIdentity)
capabilities := gateway.DefaultCapabilities() capabilities := gateway.DefaultCapabilities()
server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: serverTLS, GatewayID: gatewayID, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, ProviderStateReporter: controlPlaneClient, Provider: provider, PacerKbps: 100000}) server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: serverTLS, GatewayID: gatewayID, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, ProviderStateReporter: controlPlaneClient, Provider: provider, PacerKbps: 100000})
if err != nil { if err != nil {
return err return err
} }
registration := protocol.GatewayRegistration{Version: "1", GatewayID: gatewayID, InstanceIdentity: instanceIdentity, CertificateIdentity: certificateIdentity, PublicIdentity: publicIdentity, Address: advertiseAddress, ProviderIdentity: providerIdentity, ProtocolMinVersion: 1, ProtocolMaxVersion: 1, ConnectionCapacity: 8, BandwidthCapacityKbps: 100000, Features: []string{"quic-tls13", "datagram.media", "apollo"}, Capabilities: capabilities} registration := protocol.GatewayRegistration{Version: "1", GatewayID: gatewayID, InstanceIdentity: instanceIdentity, CertificateIdentity: certificateIdentity, PublicIdentity: publicIdentity, Address: advertiseAddress, ProviderIdentity: "server-derived", ProtocolMinVersion: 1, ProtocolMaxVersion: 1, ConnectionCapacity: 8, BandwidthCapacityKbps: 100000, Features: []string{"quic-tls13", "datagram.media", "apollo"}, Capabilities: capabilities}
if _, err := controlPlaneClient.Register(context.Background(), registration); err != nil { if _, err := controlPlaneClient.Register(context.Background(), registration); err != nil {
_ = server.Close() _ = server.Close()
return err return err
+57 -20
View File
@@ -2,12 +2,17 @@ package gateway
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"crypto/sha256"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -16,36 +21,35 @@ import (
) )
// NativeApolloBackend keeps provider sockets inside the gateway process. The // NativeApolloBackend keeps provider sockets inside the gateway process. The
// RTSP endpoint is configuration owned by the gateway and is never serialized // session-scoped Server work is the sole source of provider endpoint and mTLS
// into a client manifest or authority. // material; it is never serialized into a client manifest or authority.
type NativeApolloBackend struct { type NativeApolloBackend struct {
ManagementURL string Dialer *net.Dialer
RTSPAddress string
RTSPURL string
HTTPClient *http.Client
Dialer *net.Dialer
TLSConfig *tls.Config
mu sync.Mutex mu sync.Mutex
pending map[string]net.Conn pending map[string]net.Conn
} }
func NewNativeApolloBackend(managementURL, rtspAddress, rtspURL string, client *http.Client) *NativeApolloBackend { func NewNativeApolloBackend() *NativeApolloBackend {
if client == nil { return &NativeApolloBackend{Dialer: &net.Dialer{Timeout: 5 * time.Second}, pending: make(map[string]net.Conn)}
client = &http.Client{Timeout: 5 * time.Second}
}
return &NativeApolloBackend{ManagementURL: managementURL, RTSPAddress: rtspAddress, RTSPURL: rtspURL, HTTPClient: client, Dialer: &net.Dialer{Timeout: 5 * time.Second}, pending: make(map[string]net.Conn)}
} }
func (b *NativeApolloBackend) Management(ctx context.Context) ([]byte, error) { func (b *NativeApolloBackend) Management(ctx context.Context, request LaunchRequest) ([]byte, error) {
if b.ManagementURL == "" { work := request.ProviderWork
if err := work.Validate(); err != nil || work.ProviderProfile != ProviderProfileApollo {
return nil, ErrProviderMalformed return nil, ErrProviderMalformed
} }
request, err := http.NewRequestWithContext(ctx, http.MethodGet, b.ManagementURL, nil) tlsConfig, err := pinnedApolloTLSConfig(work)
if err != nil {
return nil, err
}
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}, Timeout: 5 * time.Second}
managementURL := "https://" + net.JoinHostPort(work.ManagementHost, strconv.FormatInt(work.ManagementPort, 10)) + "/serverinfo"
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodGet, managementURL, nil)
if err != nil { if err != nil {
return nil, ErrProviderMalformed return nil, ErrProviderMalformed
} }
response, err := b.HTTPClient.Do(request) response, err := client.Do(httpRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -56,18 +60,51 @@ func (b *NativeApolloBackend) Management(ctx context.Context) ([]byte, error) {
return readBounded(response.Body, 64*1024) return readBounded(response.Body, 64*1024)
} }
func pinnedApolloTLSConfig(work protocol.ProviderSessionWork) (*tls.Config, error) {
identity, ok := providerIdentityFromKey(work.ProviderIdentity)
if !ok || !strings.HasPrefix(identity.Fingerprint, "sha256:") {
return nil, ErrProviderIdentity
}
pinned, err := hex.DecodeString(strings.TrimPrefix(identity.Fingerprint, "sha256:"))
if err != nil || len(pinned) != sha256.Size {
return nil, ErrProviderIdentity
}
certificate, err := tls.X509KeyPair([]byte(work.ClientCertificatePem), []byte(work.ClientPrivateKeyPem))
if err != nil {
return nil, ErrProviderIdentity
}
trust := x509.NewCertPool()
if !trust.AppendCertsFromPEM([]byte(work.ServerCertificatePem)) {
return nil, ErrProviderIdentity
}
return &tls.Config{
MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate}, RootCAs: trust,
VerifyPeerCertificate: func(rawCertificates [][]byte, _ [][]*x509.Certificate) error {
if len(rawCertificates) == 0 {
return ErrProviderIdentity
}
digest := sha256.Sum256(rawCertificates[0])
if !bytes.Equal(digest[:], pinned) {
return ErrProviderIdentity
}
return nil
},
}, nil
}
func (b *NativeApolloBackend) Setup(ctx context.Context, request LaunchRequest) ([]byte, error) { func (b *NativeApolloBackend) Setup(ctx context.Context, request LaunchRequest) ([]byte, error) {
if b.RTSPAddress == "" || b.RTSPURL == "" || request.SessionID == "" { work := request.ProviderWork
if err := work.Validate(); err != nil || request.SessionID == "" {
return nil, ErrProviderMalformed return nil, ErrProviderMalformed
} }
conn, err := b.Dialer.DialContext(ctx, "tcp", b.RTSPAddress) conn, err := b.Dialer.DialContext(ctx, "tcp", net.JoinHostPort(work.StreamHost, strconv.FormatInt(work.StreamPort, 10)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if deadline, ok := ctx.Deadline(); ok { if deadline, ok := ctx.Deadline(); ok {
_ = conn.SetDeadline(deadline) _ = conn.SetDeadline(deadline)
} }
requestText := "SETUP " + b.RTSPURL + " RTSP/1.0\r\nCSeq: 1\r\nTransport: RTP/AVP/TCP;interleaved=0-1\r\nSession: " + request.SessionID + "\r\n\r\n" requestText := "SETUP rtsp://" + work.StreamHost + "/streamid=video/0/0 RTSP/1.0\r\nCSeq: 1\r\nTransport: RTP/AVP/TCP;interleaved=0-1\r\nSession: " + request.SessionID + "\r\n\r\n"
if _, err := io.WriteString(conn, requestText); err != nil { if _, err := io.WriteString(conn, requestText); err != nil {
_ = conn.Close() _ = conn.Close()
return nil, err return nil, err
+73
View File
@@ -0,0 +1,73 @@
package gateway
import (
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"net"
"net/http"
"net/http/httptest"
"strconv"
"testing"
protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol"
)
func TestNativeApolloManagementUsesSessionScopedMTLS(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
server := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/serverinfo" || request.TLS == nil || len(request.TLS.PeerCertificates) != 1 {
http.Error(response, "mTLS required", http.StatusUnauthorized)
return
}
_, _ = response.Write([]byte("<root><uniqueid>apollo-server</uniqueid></root>"))
}))
server.TLS = serverTLS
server.StartTLS()
defer server.Close()
host, portText, err := net.SplitHostPort(server.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
port, err := strconv.ParseInt(portText, 10, 64)
if err != nil {
t.Fatal(err)
}
pinned := sha256.Sum256(serverTLS.Certificates[0].Certificate[0])
work := protocol.ProviderSessionWork{
Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 0,
ExpiresAt: "2099-01-01T00:00:00Z", ProviderProfile: ProviderProfileApollo,
ProviderIdentity: "apollo-server#sha256:" + hex.EncodeToString(pinned[:]), PolicyVersionID: "policy-1", ApplicationID: "1",
ManagementHost: host, ManagementPort: port, StreamHost: host, StreamPort: 47984,
ClientCertificatePem: certificatePEM(t, clientTLS.Certificates[0]),
ClientPrivateKeyPem: privateKeyPEM(t, clientTLS.Certificates[0]),
ServerCertificatePem: certificatePEM(t, tls.Certificate{Certificate: [][]byte{serverTLS.Certificates[0].Certificate[1]}}),
}
data, err := NewNativeApolloBackend().Management(context.Background(), LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work})
if err != nil {
t.Fatalf("Management() error = %v", err)
}
if info, err := ParseManagementXML(data); err != nil || info.Identity.UniqueID != "apollo-server" {
t.Fatalf("ParseManagementXML() = %+v, %v", info, err)
}
}
func certificatePEM(t *testing.T, certificate tls.Certificate) string {
t.Helper()
if len(certificate.Certificate) == 0 {
t.Fatal("expected certificate")
}
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Certificate[0]}))
}
func privateKeyPEM(t *testing.T, certificate tls.Certificate) string {
t.Helper()
encoded, err := x509.MarshalPKCS8PrivateKey(certificate.PrivateKey)
if err != nil {
t.Fatal(err)
}
return string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encoded}))
}
+12
View File
@@ -76,6 +76,18 @@ func (c *ControlPlaneClient) Release(ctx context.Context, authority protocol.Ses
return err return err
} }
func (c *ControlPlaneClient) ProviderWork(ctx context.Context, authority protocol.SessionAuthority) (protocol.ProviderSessionWork, error) {
payload, err := protocol.EncodeSessionAuthority(authority)
if err != nil {
return protocol.ProviderSessionWork{}, err
}
response, err := c.post(ctx, "/api/v1/gateway/provider-work", payload)
if err != nil {
return protocol.ProviderSessionWork{}, err
}
return protocol.DecodeProviderSessionWork(response)
}
func (c *ControlPlaneClient) ReportProviderState(ctx context.Context, state protocol.ProviderState) error { func (c *ControlPlaneClient) ReportProviderState(ctx context.Context, state protocol.ProviderState) error {
payload, err := protocol.EncodeProviderState(state) payload, err := protocol.EncodeProviderState(state)
if err != nil { if err != nil {
+15 -1
View File
@@ -307,7 +307,7 @@ func testTLS(t *testing.T) (*tls.Config, *tls.Config) {
if keyErr != nil { if keyErr != nil {
t.Fatal(keyErr) t.Fatal(keyErr)
} }
template := &x509.Certificate{SerialNumber: big.NewInt(serial), Subject: pkix.Name{CommonName: dns}, DNSNames: []string{dns}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour), ExtKeyUsage: []x509.ExtKeyUsage{usage}, KeyUsage: x509.KeyUsageDigitalSignature} template := &x509.Certificate{SerialNumber: big.NewInt(serial), Subject: pkix.Name{CommonName: dns}, DNSNames: []string{dns}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour), ExtKeyUsage: []x509.ExtKeyUsage{usage}, KeyUsage: x509.KeyUsageDigitalSignature}
der, createErr := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey) der, createErr := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
if createErr != nil { if createErr != nil {
t.Fatal(createErr) t.Fatal(createErr)
@@ -353,6 +353,20 @@ func (a *oneTimeAdmission) Admit(context.Context, protocol.TunnelAdmissionReques
return a.authority, nil return a.authority, nil
} }
func (a *oneTimeAdmission) ProviderWork(_ context.Context, authority protocol.SessionAuthority) (protocol.ProviderSessionWork, error) {
if authority != a.authority {
return protocol.ProviderSessionWork{}, ErrAdmissionRejected
}
return protocol.ProviderSessionWork{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID,
ReconnectSequence: authority.ReconnectSequence, ExpiresAt: authority.ExpiresAt,
ProviderProfile: ProviderProfileApollo, ProviderIdentity: authority.ProviderIdentity,
PolicyVersionID: "policy-1", ApplicationID: "1", ManagementHost: "apollo.test", ManagementPort: 47990,
StreamHost: "apollo.test", StreamPort: 47984, ClientCertificatePem: "certificate",
ClientPrivateKeyPem: "private-key", ServerCertificatePem: "server-certificate",
}, nil
}
func (a *oneTimeAdmission) Release(context.Context, protocol.SessionAuthority) error { func (a *oneTimeAdmission) Release(context.Context, protocol.SessionAuthority) error {
if a.releases.Add(1) == 1 { if a.releases.Add(1) == 1 {
close(a.released) close(a.released)
+30 -8
View File
@@ -44,8 +44,17 @@ func (i ProviderIdentity) Key() string {
return i.UniqueID + "#" + i.Fingerprint return i.UniqueID + "#" + i.Fingerprint
} }
func providerIdentityFromKey(value string) (ProviderIdentity, bool) {
uniqueID, fingerprint, ok := strings.Cut(strings.TrimSpace(value), "#")
if !ok || uniqueID == "" || fingerprint == "" || strings.Contains(fingerprint, "#") || len(uniqueID) > 128 || len(fingerprint) > 256 {
return ProviderIdentity{}, false
}
return ProviderIdentity{UniqueID: uniqueID, Fingerprint: fingerprint}, true
}
func (i ProviderIdentity) Validate(now time.Time, expected ProviderIdentity) error { func (i ProviderIdentity) Validate(now time.Time, expected ProviderIdentity) error {
if i.UniqueID == "" || i.Fingerprint == "" || i.UniqueID != expected.UniqueID || i.Fingerprint != expected.Fingerprint { if i.UniqueID == "" || expected.UniqueID == "" || i.UniqueID != expected.UniqueID ||
(i.Fingerprint != "" && i.Fingerprint != expected.Fingerprint) {
return ErrProviderIdentity return ErrProviderIdentity
} }
if !i.NotBefore.IsZero() && now.Before(i.NotBefore) { if !i.NotBefore.IsZero() && now.Before(i.NotBefore) {
@@ -68,7 +77,8 @@ func ParseManagementXML(data []byte) (ManagementInfo, error) {
} }
var document struct { var document struct {
XMLName xml.Name `xml:"root"` XMLName xml.Name `xml:"root"`
UniqueID string `xml:"unique_id"` UniqueID string `xml:"uniqueid"`
LegacyID string `xml:"unique_id"`
Fingerprint string `xml:"fingerprint"` Fingerprint string `xml:"fingerprint"`
NotBefore string `xml:"not_before"` NotBefore string `xml:"not_before"`
NotAfter string `xml:"not_after"` NotAfter string `xml:"not_after"`
@@ -79,6 +89,9 @@ func ParseManagementXML(data []byte) (ManagementInfo, error) {
if err := decoder.Decode(&document); err != nil { if err := decoder.Decode(&document); err != nil {
return ManagementInfo{}, fmt.Errorf("%w: %v", ErrProviderMalformed, err) return ManagementInfo{}, fmt.Errorf("%w: %v", ErrProviderMalformed, err)
} }
if document.UniqueID == "" {
document.UniqueID = document.LegacyID
}
identity := ProviderIdentity{UniqueID: document.UniqueID, Fingerprint: document.Fingerprint} identity := ProviderIdentity{UniqueID: document.UniqueID, Fingerprint: document.Fingerprint}
var err error var err error
if document.NotBefore != "" { if document.NotBefore != "" {
@@ -93,7 +106,7 @@ func ParseManagementXML(data []byte) (ManagementInfo, error) {
return ManagementInfo{}, ErrProviderMalformed return ManagementInfo{}, ErrProviderMalformed
} }
} }
if identity.UniqueID == "" || len(identity.UniqueID) > 128 || identity.Fingerprint == "" || len(identity.Fingerprint) > 256 { if identity.UniqueID == "" || len(identity.UniqueID) > 128 || len(identity.Fingerprint) > 256 {
return ManagementInfo{}, ErrProviderMalformed return ManagementInfo{}, ErrProviderMalformed
} }
return ManagementInfo{Identity: identity, Name: document.Name}, nil return ManagementInfo{Identity: identity, Name: document.Name}, nil
@@ -181,6 +194,7 @@ type LaunchRequest struct {
Capabilities protocol.CapabilityProfile Capabilities protocol.CapabilityProfile
ProviderProfile string ProviderProfile string
ProviderIdentity string ProviderIdentity string
ProviderWork protocol.ProviderSessionWork
} }
type InputEvent struct { type InputEvent struct {
@@ -213,7 +227,7 @@ type ProviderSession interface {
} }
type ApolloBackend interface { type ApolloBackend interface {
Management(context.Context) ([]byte, error) Management(context.Context, LaunchRequest) ([]byte, error)
Setup(context.Context, LaunchRequest) ([]byte, error) Setup(context.Context, LaunchRequest) ([]byte, error)
Open(context.Context, LaunchRequest, RTSPResponse) (ProviderSession, error) Open(context.Context, LaunchRequest, RTSPResponse) (ProviderSession, error)
} }
@@ -233,7 +247,7 @@ func (a *ApolloAdapter) Start(ctx context.Context, request LaunchRequest) (Provi
if a == nil || a.backend == nil || request.ProviderProfile != ProviderProfileApollo { if a == nil || a.backend == nil || request.ProviderProfile != ProviderProfileApollo {
return nil, ErrProviderIdentity return nil, ErrProviderIdentity
} }
management, err := a.backend.Management(ctx) management, err := a.backend.Management(ctx, request)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -241,10 +255,18 @@ func (a *ApolloAdapter) Start(ctx context.Context, request LaunchRequest) (Provi
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := info.Identity.Validate(a.now(), a.expected); err != nil { expected := a.expected
if request.ProviderWork.ProviderIdentity != "" {
parsed, ok := providerIdentityFromKey(request.ProviderWork.ProviderIdentity)
if !ok {
return nil, ErrProviderIdentity
}
expected = parsed
}
if err := info.Identity.Validate(a.now(), expected); err != nil {
return nil, err return nil, err
} }
if request.ProviderIdentity != "" && request.ProviderIdentity != info.Identity.Key() { if request.ProviderIdentity != "" && info.Identity.UniqueID != expected.UniqueID {
return nil, ErrProviderIdentity return nil, ErrProviderIdentity
} }
rawRTSP, err := a.backend.Setup(ctx, request) rawRTSP, err := a.backend.Setup(ctx, request)
@@ -320,7 +342,7 @@ func NewFakeApollo(config FakeApolloConfig) *FakeApollo {
return &FakeApollo{config: config} return &FakeApollo{config: config}
} }
func (f *FakeApollo) Management(context.Context) ([]byte, error) { func (f *FakeApollo) Management(context.Context, LaunchRequest) ([]byte, error) {
if f.config.Failure == FakeFailureMalformed { if f.config.Failure == FakeFailureMalformed {
return []byte("<root>"), nil return []byte("<root>"), nil
} }
+24 -1
View File
@@ -33,6 +33,7 @@ var (
type Admission interface { type Admission interface {
Admit(context.Context, protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error) Admit(context.Context, protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error)
ProviderWork(context.Context, protocol.SessionAuthority) (protocol.ProviderSessionWork, error)
Release(context.Context, protocol.SessionAuthority) error Release(context.Context, protocol.SessionAuthority) error
} }
@@ -44,6 +45,10 @@ func (f AdmissionFunc) Admit(ctx context.Context, request protocol.TunnelAdmissi
func (AdmissionFunc) Release(context.Context, protocol.SessionAuthority) error { return nil } func (AdmissionFunc) Release(context.Context, protocol.SessionAuthority) error { return nil }
func (AdmissionFunc) ProviderWork(context.Context, protocol.SessionAuthority) (protocol.ProviderSessionWork, error) {
return protocol.ProviderSessionWork{}, ErrAdmissionRejected
}
type ProviderStateReporter interface { type ProviderStateReporter interface {
ReportProviderState(context.Context, protocol.ProviderState) error ReportProviderState(context.Context, protocol.ProviderState) error
} }
@@ -210,6 +215,12 @@ func (s *Server) handleConnection(parent context.Context, connection *quic.Conn)
_ = writeStableError(stream, "invalid_authority", err, false) _ = writeStableError(stream, "invalid_authority", err, false)
return return
} }
work, err := s.config.Admission.ProviderWork(ctx, authority)
if err != nil || s.validateProviderWork(work, authority) != nil {
_ = s.config.Admission.Release(context.Background(), authority)
_ = writeStableError(stream, "provider_work_unavailable", ErrAdmissionRejected, err != nil)
return
}
selected, err := IntersectCapabilities(s.config.Capabilities, s.config.ProviderCapabilities, request.Capabilities, authority.Capabilities) selected, err := IntersectCapabilities(s.config.Capabilities, s.config.ProviderCapabilities, request.Capabilities, authority.Capabilities)
if err != nil { if err != nil {
_ = s.config.Admission.Release(context.Background(), authority) _ = s.config.Admission.Release(context.Background(), authority)
@@ -222,7 +233,7 @@ func (s *Server) handleConnection(parent context.Context, connection *quic.Conn)
_ = writeStableError(stream, "provider_state_unavailable", err, true) _ = writeStableError(stream, "provider_state_unavailable", err, true)
return return
} }
providerSession, err := s.config.Provider.Start(ctx, LaunchRequest{SessionID: request.SessionID, Capabilities: selected, ProviderProfile: authority.ProviderProfile, ProviderIdentity: authority.ProviderIdentity}) providerSession, err := s.config.Provider.Start(ctx, LaunchRequest{SessionID: request.SessionID, Capabilities: selected, ProviderProfile: authority.ProviderProfile, ProviderIdentity: work.ProviderIdentity, ProviderWork: work})
if err != nil { if err != nil {
s.metrics.ProviderErrors.Add(1) s.metrics.ProviderErrors.Add(1)
_ = s.reportProviderState(context.Background(), protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateFailed, CleanupPending: false, Channels: []string{"video", "audio", "input", "feedback"}}) _ = s.reportProviderState(context.Background(), protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateFailed, CleanupPending: false, Channels: []string{"video", "audio", "input", "feedback"}})
@@ -279,6 +290,18 @@ func (s *Server) validateAuthority(authority protocol.SessionAuthority, request
return nil return nil
} }
func (s *Server) validateProviderWork(work protocol.ProviderSessionWork, authority protocol.SessionAuthority) error {
if err := work.Validate(); err != nil {
return err
}
if work.SessionID != authority.SessionID || work.GatewayID != authority.GatewayID ||
work.ReconnectSequence != authority.ReconnectSequence || work.ExpiresAt != authority.ExpiresAt ||
work.ProviderProfile != authority.ProviderProfile {
return ErrAdmissionRejected
}
return nil
}
func (s *Server) addSession(session *gatewaySession) { func (s *Server) addSession(session *gatewaySession) {
s.mu.Lock() s.mu.Lock()
s.sessions[session] = struct{}{} s.sessions[session] = struct{}{}
+1 -1
View File
@@ -3,7 +3,7 @@ module git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane
go 1.26.5 go 1.26.5
require ( require (
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3 git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.4
github.com/quic-go/quic-go v0.61.0 github.com/quic-go/quic-go v0.61.0
) )
+2
View File
@@ -1,5 +1,7 @@
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3 h1:ZoXbg9CRwlypVbDO0EaXwHVOKTGlIfZDC7s/4JuOISE= git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3 h1:ZoXbg9CRwlypVbDO0EaXwHVOKTGlIfZDC7s/4JuOISE=
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc= git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.4 h1:uwNoKtzRlpdbvq9kHblncKr+K+dck0ydtozzPKItQzA=
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.4/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=