allow disabling JWT in HTTP query parameters (#4518)
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -93,6 +93,12 @@ components:
|
||||
type: string
|
||||
authJWTClaimKey:
|
||||
type: string
|
||||
authJWTExclude:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthInternalUserPermission'
|
||||
authJWTInHTTPQuery:
|
||||
type: boolean
|
||||
|
||||
# Control API
|
||||
api:
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user