fix(gateway): enforce audited production traversal
This commit is contained in:
@@ -82,14 +82,18 @@ func TestNativeApolloSetupRejectsUnsupportedStreamPolicyBeforeProviderReadiness(
|
||||
ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30},
|
||||
}
|
||||
for name, policy := range map[string]protocol.ProviderStreamPolicy{
|
||||
"audio-disabled": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: false},
|
||||
"av1": {ResolutionWidth: 3840, ResolutionHeight: 2160, Fps: 60, Codec: "AV1", BitrateKbps: 50000, AudioEnabled: true},
|
||||
"audio-disabled": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: false},
|
||||
"av1": {ResolutionWidth: 3840, ResolutionHeight: 2160, Fps: 60, Codec: "AV1", BitrateKbps: 50000, AudioEnabled: true},
|
||||
"h264-resolution": {ResolutionWidth: 4097, ResolutionHeight: 2160, Fps: 60, Codec: "H264", BitrateKbps: 50000, AudioEnabled: true},
|
||||
"hevc-resolution": {ResolutionWidth: 8193, ResolutionHeight: 4320, Fps: 60, Codec: "HEVC", BitrateKbps: 80000, AudioEnabled: true},
|
||||
"fps": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 241, Codec: "H264", BitrateKbps: 8000, AudioEnabled: true},
|
||||
"bitrate-cap": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 125001, AudioEnabled: true},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
work.StreamPolicy = policy
|
||||
_, err := NewNativeApolloBackend().Setup(context.Background(), LaunchRequest{
|
||||
SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work,
|
||||
})
|
||||
}, nil)
|
||||
if !errors.Is(err, ErrProviderMalformed) {
|
||||
t.Fatalf("Setup() error = %v, want ErrProviderMalformed before provider readiness", err)
|
||||
}
|
||||
@@ -97,6 +101,73 @@ func TestNativeApolloSetupRejectsUnsupportedStreamPolicyBeforeProviderReadiness(
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeApolloRejectsProviderCapabilityMismatchBeforeInventoryOrLaunch(t *testing.T) {
|
||||
serverTLS, clientTLS := testTLS(t)
|
||||
var paths []string
|
||||
management := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
paths = append(paths, request.URL.Path)
|
||||
switch request.URL.Path {
|
||||
case "/serverinfo":
|
||||
_, _ = response.Write([]byte("<root><uniqueid>apollo-server</uniqueid><ServerCodecModeSupport>1</ServerCodecModeSupport><MaxLumaPixelsHEVC>0</MaxLumaPixelsHEVC></root>"))
|
||||
case "/applist":
|
||||
_, _ = response.Write([]byte("<root><App><ID>42</ID></App></root>"))
|
||||
default:
|
||||
http.Error(response, "unexpected provider request", http.StatusBadRequest)
|
||||
}
|
||||
}))
|
||||
management.TLS = serverTLS
|
||||
management.StartTLS()
|
||||
defer management.Close()
|
||||
host, portText, err := net.SplitHostPort(management.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-source-policy", GatewayID: "gateway-1",
|
||||
ExpiresAt: "2099-01-01T00:00:00Z", ProviderProfile: ProviderProfileApollo,
|
||||
ProviderIdentity: "apollo-server#sha256:" + hex.EncodeToString(pinned[:]), PolicyVersionID: "policy-1",
|
||||
ApplicationID: "42", ClientID: "paired-client",
|
||||
StreamPolicy: protocol.ProviderStreamPolicy{
|
||||
ResolutionWidth: 2560, ResolutionHeight: 1440, Fps: 120,
|
||||
Codec: "HEVC", BitrateKbps: 40000, AudioEnabled: true,
|
||||
},
|
||||
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]}}),
|
||||
ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30},
|
||||
}
|
||||
adapter := NewApolloAdapter(NewNativeApolloBackend(), ProviderIdentity{UniqueID: "apollo-server", Fingerprint: "sha256:" + hex.EncodeToString(pinned[:])})
|
||||
if _, err := adapter.Start(context.Background(), LaunchRequest{
|
||||
SessionID: work.SessionID, ProviderProfile: ProviderProfileApollo,
|
||||
ProviderIdentity: work.ProviderIdentity, ProviderWork: work,
|
||||
}); err == nil {
|
||||
t.Fatal("Start() accepted a provider/source policy mismatch")
|
||||
}
|
||||
if got := strings.Join(paths, ","); got != "/serverinfo" {
|
||||
t.Fatalf("provider requests before capability rejection = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApolloProviderCapabilityRejectsHEVCLumaDowngrade(t *testing.T) {
|
||||
info := ManagementInfo{
|
||||
ServerCodecModeSupport: 0x101, HasServerCodecModeSupport: true,
|
||||
MaxLumaPixelsHEVC: 1920 * 1080, HasMaxLumaPixelsHEVC: true,
|
||||
}
|
||||
policy := protocol.ProviderStreamPolicy{
|
||||
ResolutionWidth: 3840, ResolutionHeight: 2160, Fps: 60,
|
||||
Codec: "HEVC", BitrateKbps: 80000, AudioEnabled: true,
|
||||
}
|
||||
if err := validateApolloProviderStreamPolicy(info, policy); !errors.Is(err, ErrProviderMalformed) {
|
||||
t.Fatalf("provider HEVC luma downgrade error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
|
||||
serverTLS, clientTLS := testTLS(t)
|
||||
streamListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@@ -213,7 +284,7 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
|
||||
}
|
||||
switch request.URL.Path {
|
||||
case "/serverinfo":
|
||||
_, _ = response.Write([]byte("<root><uniqueid>apollo-server</uniqueid></root>"))
|
||||
_, _ = response.Write([]byte("<root><uniqueid>apollo-server</uniqueid><ServerCodecModeSupport>257</ServerCodecModeSupport><MaxLumaPixelsHEVC>1869449984</MaxLumaPixelsHEVC></root>"))
|
||||
case "/applist":
|
||||
if request.URL.Query().Get("uniqueid") != "paired-client" {
|
||||
http.Error(response, "wrong client", http.StatusBadRequest)
|
||||
@@ -291,7 +362,7 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
|
||||
ProviderApplicationTerminationAllowed: true,
|
||||
}
|
||||
backend := NewNativeApolloBackend()
|
||||
response, err := backend.Setup(context.Background(), LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work})
|
||||
response, err := backend.Setup(context.Background(), LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work}, []byte("<root><uniqueid>apollo-server</uniqueid><ServerCodecModeSupport>257</ServerCodecModeSupport><MaxLumaPixelsHEVC>1869449984</MaxLumaPixelsHEVC></root>"))
|
||||
if err != nil {
|
||||
t.Fatalf("Setup() error = %v", err)
|
||||
}
|
||||
@@ -474,7 +545,8 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
|
||||
t.Fatal("encrypted host termination was not forwarded")
|
||||
}
|
||||
select {
|
||||
case payload := <-session.Video():
|
||||
case media := <-session.Video():
|
||||
payload := media.Payload
|
||||
if len(payload) != 1001 || payload[0] != 'A' || payload[1000] != 'B' {
|
||||
t.Fatalf("source-shaped video relay = %x", payload)
|
||||
}
|
||||
@@ -482,7 +554,8 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
|
||||
t.Fatal("source-shaped video was not relayed")
|
||||
}
|
||||
select {
|
||||
case payload := <-session.Audio():
|
||||
case media := <-session.Audio():
|
||||
payload := media.Payload
|
||||
if string(payload) != "A" {
|
||||
t.Fatalf("source-shaped audio relay = %x", payload)
|
||||
}
|
||||
@@ -608,7 +681,8 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
|
||||
}
|
||||
|
||||
select {
|
||||
case payload := <-session.Video():
|
||||
case media := <-session.Video():
|
||||
payload := media.Payload
|
||||
if string(payload) != string([]byte{0x01, 0x02, 0x03}) {
|
||||
t.Fatalf("video relay = %x, want encoded payload", payload)
|
||||
}
|
||||
@@ -617,7 +691,8 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
|
||||
}
|
||||
for _, want := range wantAudio {
|
||||
select {
|
||||
case payload := <-session.Audio():
|
||||
case media := <-session.Audio():
|
||||
payload := media.Payload
|
||||
if string(payload) != string(want) {
|
||||
t.Fatalf("audio relay = %x, want %x", payload, want)
|
||||
}
|
||||
@@ -632,7 +707,8 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case payload := <-session.Video():
|
||||
case media := <-session.Video():
|
||||
payload := media.Payload
|
||||
if len(payload) != 1001 || payload[0] != 'A' || payload[999] != 'A' || payload[1000] != 'B' {
|
||||
t.Fatalf("FEC video relay = %x", payload)
|
||||
}
|
||||
@@ -646,7 +722,8 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
|
||||
}
|
||||
for _, want := range [][]byte{{'A'}, {'B'}, {'C'}, {'D'}} {
|
||||
select {
|
||||
case payload := <-session.Audio():
|
||||
case media := <-session.Audio():
|
||||
payload := media.Payload
|
||||
if string(payload) != string(want) {
|
||||
t.Fatalf("FEC audio relay = %x, want %x", payload, want)
|
||||
}
|
||||
@@ -670,7 +747,8 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
|
||||
}
|
||||
for _, want := range [][]byte{{0xa0}, {0xa1}, {0xa2}, {0xa3}} {
|
||||
select {
|
||||
case payload := <-session.Audio():
|
||||
case media := <-session.Audio():
|
||||
payload := media.Payload
|
||||
if string(payload) != string(want) {
|
||||
t.Fatalf("post-loss audio relay = %x, want %x", payload, want)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user