support X-Forwarded-Proto in playback server (#4970) (#5445)

allow reverse proxies to change the schema of URLs returned by the
server through the X-Forwarded-Proto header.
This commit is contained in:
Alessandro Ros
2026-02-11 21:47:02 +01:00
committed by GitHub
parent 460b20b6d0
commit c250e34075
4 changed files with 92 additions and 16 deletions
+2 -2
View File
@@ -2623,7 +2623,7 @@ paths:
get:
operationId: recordingsList
tags: [Recordings]
summary: returns all recordings.
summary: returns all recordings, splitted by path.
description: ''
parameters:
- name: page
@@ -2662,7 +2662,7 @@ paths:
get:
operationId: recordingsGet
tags: [Recordings]
summary: returns recordings for a path.
summary: returns recordings of a path.
description: ''
parameters:
- name: name
+17 -6
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
@@ -83,6 +84,21 @@ func parseSegments(segments []*recordstore.Segment) ([]*parsedSegment, error) {
return parsed, err
}
func urlScheme(ctx *gin.Context, trustedProxies conf.IPNetworks, encryption bool) string {
if trustedProxies.Contains(net.ParseIP(ctx.RemoteIP())) {
xForwardedProto := ctx.Request.Header.Get("X-Forwarded-Proto")
if xForwardedProto != "" {
return xForwardedProto
}
}
if encryption {
return "https"
}
return "http"
}
type listEntry struct {
Start time.Time `json:"start"`
Duration listEntryDuration `json:"duration"`
@@ -212,12 +228,7 @@ func (s *Server) onList(ctx *gin.Context) {
}
}
var scheme string
if s.Encryption {
scheme = "https"
} else {
scheme = "http"
}
scheme := urlScheme(ctx, s.TrustedProxies, s.Encryption)
for i := range entries {
v := url.Values{}
+65
View File
@@ -336,3 +336,68 @@ func TestOnListCachedDuration(t *testing.T) {
},
}, out)
}
func TestOnListXForwardedProto(t *testing.T) {
dir, err := os.MkdirTemp("", "mediamtx-playback")
require.NoError(t, err)
defer os.RemoveAll(dir)
err = os.Mkdir(filepath.Join(dir, "mypath"), 0o755)
require.NoError(t, err)
writeSegment1(t, filepath.Join(dir, "mypath", "2008-11-07_11-22-00-500000.mp4"))
var trustedProxies conf.IPNetworks
err = json.Unmarshal([]byte(`["127.0.0.0/8"]`), &trustedProxies)
require.NoError(t, err)
s := &Server{
Address: "127.0.0.1:9996",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
TrustedProxies: trustedProxies,
PathConfs: map[string]*conf.Path{
"mypath": {
Name: "mypath",
RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
RecordFormat: conf.RecordFormatFMP4,
},
},
AuthManager: test.NilAuthManager,
Parent: test.NilLogger,
}
err = s.Initialize()
require.NoError(t, err)
defer s.Close()
u, err := url.Parse("http://localhost:9996/list")
require.NoError(t, err)
v := url.Values{}
v.Set("path", "mypath")
u.RawQuery = v.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
require.NoError(t, err)
req.Header.Set("X-Forwarded-Proto", "https")
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
var out any
err = json.NewDecoder(res.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, []any{
map[string]any{
"duration": float64(62),
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "https://localhost:9996/get?duration=62&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
}
+8 -8
View File
@@ -171,8 +171,8 @@ apiServerCert: server.crt
# Supports wildcards: ['http://*.example.com']
apiAllowOrigins: ['*']
# IPs or CIDRs of proxies placed before the HTTP server.
# If the server receives a request from one of these entries, IP in logs
# will be taken from the X-Forwarded-For header.
# These proxies can use the X-Forwarded-For header to set the real IP of clients,
# and the X-Forwarded-Proto header to set the original protocol.
apiTrustedProxies: []
###############################################
@@ -195,8 +195,8 @@ metricsServerCert: server.crt
# Supports wildcards: ['http://*.example.com']
metricsAllowOrigins: ['*']
# IPs or CIDRs of proxies placed before the HTTP server.
# If the server receives a request from one of these entries, IP in logs
# will be taken from the X-Forwarded-For header.
# These proxies can use the X-Forwarded-For header to set the real IP of clients,
# and the X-Forwarded-Proto header to set the original protocol.
metricsTrustedProxies: []
###############################################
@@ -219,8 +219,8 @@ pprofServerCert: server.crt
# Supports wildcards: ['http://*.example.com']
pprofAllowOrigins: ['*']
# IPs or CIDRs of proxies placed before the HTTP server.
# If the server receives a request from one of these entries, IP in logs
# will be taken from the X-Forwarded-For header.
# These proxies can use the X-Forwarded-For header to set the real IP of clients,
# and the X-Forwarded-Proto header to set the original protocol.
pprofTrustedProxies: []
###############################################
@@ -243,8 +243,8 @@ playbackServerCert: server.crt
# Supports wildcards: ['http://*.example.com']
playbackAllowOrigins: ['*']
# IPs or CIDRs of proxies placed before the HTTP server.
# If the server receives a request from one of these entries, IP in logs
# will be taken from the X-Forwarded-For header.
# These proxies can use the X-Forwarded-For header to set the real IP of clients,
# and the X-Forwarded-Proto header to set the original protocol.
playbackTrustedProxies: []
###############################################