allow disabling JWT in HTTP query parameters (#4518)

This commit is contained in:
Alessandro Ros
2025-05-11 10:21:08 +02:00
committed by GitHub
parent c17a6de2a6
commit 74bfb988d7
11 changed files with 58 additions and 15 deletions
+1 -1
View File
@@ -1515,7 +1515,7 @@ Clients are expected to pass the JWT in one of the following ways (from best to
2. As password. Username is arbitrary.
3. As query parameter in the URL, with the `jwt` key. This method is discouraged since the JWT is visible and publicly shared if the URL is shared, causing a security issue.
3. As query parameter in the URL, with the `jwt` key. This method is discouraged since the JWT is publicly shared when the URL is shared, causing a security issue.
These are the recommended methods for each client:
+6
View File
@@ -93,6 +93,12 @@ components:
type: string
authJWTClaimKey:
type: string
authJWTExclude:
type: array
items:
$ref: '#/components/schemas/AuthInternalUserPermission'
authJWTInHTTPQuery:
type: boolean
# Control API
api:
+1
View File
@@ -249,6 +249,7 @@ func (a *API) middlewareOrigin(ctx *gin.Context) {
func (a *API) middlewareAuth(ctx *gin.Context) {
req := &auth.Request{
Action: conf.AuthActionAPI,
Query: ctx.Request.URL.RawQuery,
Credentials: httpp.Credentials(ctx.Request),
IP: net.ParseIP(ctx.ClientIP()),
}
+21 -2
View File
@@ -27,6 +27,21 @@ const (
jwksRefreshPeriod = 60 * 60 * time.Second
)
func isHTTPRequest(r *Request) bool {
switch r.Action {
case conf.AuthActionPlayback, conf.AuthActionAPI,
conf.AuthActionMetrics, conf.AuthActionPprof:
return true
}
switch r.Protocol {
case ProtocolHLS, ProtocolWebRTC:
return true
}
return false
}
// Error is a authentication error.
type Error struct {
Wrapped error
@@ -77,6 +92,7 @@ type Manager struct {
JWTJWKSFingerprint string
JWTClaimKey string
JWTExclude []conf.AuthInternalUserPermission
JWTInHTTPQuery bool
ReadTimeout time.Duration
mutex sync.RWMutex
@@ -219,18 +235,21 @@ func (m *Manager) authenticateJWT(req *Request) error {
case req.Credentials.Pass != "":
encodedJWT = req.Credentials.Pass
default:
case (!isHTTPRequest(req) || m.JWTInHTTPQuery):
var v url.Values
v, err = url.ParseQuery(req.Query)
if err != nil {
return err
}
if len(v["jwt"]) != 1 {
if len(v["jwt"]) != 1 || len(v["jwt"][0]) == 0 {
return fmt.Errorf("JWT not provided")
}
encodedJWT = v["jwt"][0]
default:
return fmt.Errorf("JWT not provided")
}
var cc jwtClaims
+5 -8
View File
@@ -21,14 +21,11 @@ const (
// Request is an authentication request.
type Request struct {
Action conf.AuthAction
// only for ActionPublish, ActionRead, ActionPlayback
Path string
Query string
Protocol Protocol
ID *uuid.UUID
Action conf.AuthAction
Path string // only for ActionPublish, ActionRead, ActionPlayback
Query string
Protocol Protocol // only for ActionPublish, ActionRead
ID *uuid.UUID // only for ActionPublish, ActionRead
Credentials *Credentials
IP net.IP
CustomVerifyFunc func(expectedUser string, expectedPass string) bool
+2
View File
@@ -181,6 +181,7 @@ type Conf struct {
AuthJWTJWKSFingerprint string `json:"authJWTJWKSFingerprint"`
AuthJWTClaimKey string `json:"authJWTClaimKey"`
AuthJWTExclude AuthInternalUserPermissions `json:"authJWTExclude"`
AuthJWTInHTTPQuery bool `json:"authJWTInHTTPQuery"`
// Control API
API bool `json:"api"`
@@ -335,6 +336,7 @@ func (conf *Conf) setDefaults() {
}
conf.AuthJWTClaimKey = "mediamtx_permissions"
conf.AuthJWTExclude = []AuthInternalUserPermission{}
conf.AuthJWTInHTTPQuery = true
// Control API
conf.APIAddress = ":9997"
+2
View File
@@ -277,6 +277,7 @@ func (p *Core) createResources(initial bool) error {
JWTJWKSFingerprint: p.conf.AuthJWTJWKSFingerprint,
JWTClaimKey: p.conf.AuthJWTClaimKey,
JWTExclude: p.conf.AuthJWTExclude,
JWTInHTTPQuery: p.conf.AuthJWTInHTTPQuery,
ReadTimeout: time.Duration(p.conf.ReadTimeout),
}
}
@@ -637,6 +638,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.AuthJWTJWKSFingerprint != p.conf.AuthJWTJWKSFingerprint ||
newConf.AuthJWTClaimKey != p.conf.AuthJWTClaimKey ||
!reflect.DeepEqual(newConf.AuthJWTExclude, p.conf.AuthJWTExclude) ||
newConf.AuthJWTInHTTPQuery != p.conf.AuthJWTInHTTPQuery ||
newConf.ReadTimeout != p.conf.ReadTimeout
if !closeAuthManager && !reflect.DeepEqual(newConf.AuthInternalUsers, p.conf.AuthInternalUsers) {
p.authManager.ReloadInternalUsers(newConf.AuthInternalUsers)
+1
View File
@@ -124,6 +124,7 @@ func (m *Metrics) middlewareOrigin(ctx *gin.Context) {
func (m *Metrics) middlewareAuth(ctx *gin.Context) {
req := &auth.Request{
Action: conf.AuthActionMetrics,
Query: ctx.Request.URL.RawQuery,
Credentials: httpp.Credentials(ctx.Request),
IP: net.ParseIP(ctx.ClientIP()),
}
+1
View File
@@ -99,6 +99,7 @@ func (pp *PPROF) middlewareOrigin(ctx *gin.Context) {
func (pp *PPROF) middlewareAuth(ctx *gin.Context) {
req := &auth.Request{
Action: conf.AuthActionPprof,
Query: ctx.Request.URL.RawQuery,
Credentials: httpp.Credentials(ctx.Request),
IP: net.ParseIP(ctx.ClientIP()),
}
+14 -3
View File
@@ -14,9 +14,10 @@ import (
)
type openAPIProperty struct {
Ref string `json:"$ref"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
Ref string `json:"$ref"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
Items *openAPIProperty `json:"items"`
}
type openAPISchema struct {
@@ -170,9 +171,19 @@ func TestAPIDocs(t *testing.T) {
Nullable: true,
}
case sf.Type == reflect.TypeOf(conf.AuthInternalUserPermissions{}):
content2.Properties[js] = openAPIProperty{
Type: "array",
Items: &openAPIProperty{
Ref: "#/components/schemas/AuthInternalUserPermission",
},
}
default:
if existing, ok := content1.Properties[js]; ok {
content2.Properties[js] = existing
} else {
t.Errorf("missing item: '%s'", js)
}
}
}
+4 -1
View File
@@ -119,7 +119,7 @@ authHTTPExclude:
# }
# ]
# }
# Users are expected to pass the JWT in the Authorization header or as password.
# Users are expected to pass the JWT in the Authorization header, password or query parameter.
# This is the JWKS URL that will be used to pull (once) the public key that allows
# to validate JWTs.
authJWTJWKS:
@@ -134,6 +134,9 @@ authJWTClaimKey: mediamtx_permissions
# Actions to exclude from JWT-based authentication.
# Format is the same as the one of user permissions.
authJWTExclude: []
# allow passing the JWT through query parameters of HTTP requests (i.e. ?jwt=JWT).
# This is a security risk.
authJWTInHTTPQuery: true
###############################################
# Global settings -> Control API