This commit is contained in:
parent
6da35c8041
commit
0d1da6bd5b
|
@ -1147,7 +1147,7 @@ If the URL returns a status code that begins with `20` (i.e. `200`), authenticat
|
|||
```json
|
||||
{
|
||||
"user": "",
|
||||
"password": "",
|
||||
"password": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1171,9 +1171,10 @@ Authentication can be delegated to an external identity server, that is capable
|
|||
```yml
|
||||
authMethod: jwt
|
||||
authJWTJWKS: http://my_identity_server/jwks_endpoint
|
||||
authJWTClaimKey: mediamtx_permissions
|
||||
```
|
||||
|
||||
The JWT is expected to contain the `mediamtx_permissions` scope, with a list of permissions in the same format as the one of user permissions:
|
||||
The JWT is expected to contain a claim, with a list of permissions in the same format as the one of user permissions:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
@ -87,6 +87,8 @@ components:
|
|||
$ref: '#/components/schemas/AuthInternalUserPermission'
|
||||
authJWTJWKS:
|
||||
type: string
|
||||
authJWTClaimKey:
|
||||
type: string
|
||||
|
||||
# Control API
|
||||
api:
|
||||
|
|
|
@ -99,7 +99,33 @@ func matchesPermission(perms []conf.AuthInternalUserPermission, req *Request) bo
|
|||
|
||||
type customClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
MediaMTXPermissions []conf.AuthInternalUserPermission `json:"mediamtx_permissions"`
|
||||
permissionsKey string
|
||||
permissions []conf.AuthInternalUserPermission
|
||||
}
|
||||
|
||||
func (c *customClaims) UnmarshalJSON(b []byte) error {
|
||||
err := json.Unmarshal(b, &c.RegisteredClaims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var claimMap map[string]json.RawMessage
|
||||
err = json.Unmarshal(b, &claimMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawPermissions, ok := claimMap[c.permissionsKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("claim '%s' not found inside JWT", c.permissionsKey)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(rawPermissions, &c.permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Manager is the authentication manager.
|
||||
|
@ -109,6 +135,7 @@ type Manager struct {
|
|||
HTTPAddress string
|
||||
HTTPExclude []conf.AuthInternalUserPermission
|
||||
JWTJWKS string
|
||||
JWTClaimKey string
|
||||
ReadTimeout time.Duration
|
||||
RTSPAuthMethods []auth.ValidateMethod
|
||||
|
||||
|
@ -270,12 +297,13 @@ func (m *Manager) authenticateJWT(req *Request) error {
|
|||
}
|
||||
|
||||
var cc customClaims
|
||||
cc.permissionsKey = m.JWTClaimKey
|
||||
_, err = jwt.ParseWithClaims(v["jwt"][0], &cc, keyfunc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !matchesPermission(cc.MediaMTXPermissions, req) {
|
||||
if !matchesPermission(cc.permissions, req) {
|
||||
return fmt.Errorf("user doesn't have permission to perform action")
|
||||
}
|
||||
|
||||
|
|
|
@ -327,7 +327,7 @@ func TestAuthJWT(t *testing.T) {
|
|||
|
||||
type customClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
MediaMTXPermissions []conf.AuthInternalUserPermission `json:"mediamtx_permissions"`
|
||||
MediaMTXPermissions []conf.AuthInternalUserPermission `json:"my_permission_key"`
|
||||
}
|
||||
|
||||
claims := customClaims{
|
||||
|
@ -351,8 +351,9 @@ func TestAuthJWT(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
m := Manager{
|
||||
Method: conf.AuthMethodJWT,
|
||||
JWTJWKS: "http://localhost:4567/jwks",
|
||||
Method: conf.AuthMethodJWT,
|
||||
JWTJWKS: "http://localhost:4567/jwks",
|
||||
JWTClaimKey: "my_permission_key",
|
||||
}
|
||||
|
||||
err = m.Authenticate(&Request{
|
||||
|
|
|
@ -177,6 +177,7 @@ type Conf struct {
|
|||
ExternalAuthenticationURL *string `json:"externalAuthenticationURL,omitempty"` // deprecated
|
||||
AuthHTTPExclude AuthInternalUserPermissions `json:"authHTTPExclude"`
|
||||
AuthJWTJWKS string `json:"authJWTJWKS"`
|
||||
AuthJWTClaimKey string `json:"authJWTClaimKey"`
|
||||
|
||||
// Control API
|
||||
API bool `json:"api"`
|
||||
|
@ -323,6 +324,7 @@ func (conf *Conf) setDefaults() {
|
|||
Action: AuthActionPprof,
|
||||
},
|
||||
}
|
||||
conf.AuthJWTClaimKey = "mediamtx_permissions"
|
||||
|
||||
// Control API
|
||||
conf.APIAddress = ":9997"
|
||||
|
@ -562,6 +564,9 @@ func (conf *Conf) Validate() error {
|
|||
if conf.AuthJWTJWKS == "" {
|
||||
return fmt.Errorf("'authJWTJWKS' is empty")
|
||||
}
|
||||
if conf.AuthJWTClaimKey == "" {
|
||||
return fmt.Errorf("'authJWTClaimKey' is empty")
|
||||
}
|
||||
}
|
||||
|
||||
// RTSP
|
||||
|
|
|
@ -357,6 +357,13 @@ func TestConfErrors(t *testing.T) {
|
|||
`record path './recordings/%path/%Y-%m-%d_%H-%M-%S' is missing one of the` +
|
||||
` mandatory elements for the playback server to work: %Y %m %d %H %M %S %f`,
|
||||
},
|
||||
{
|
||||
"jwt claim key empty",
|
||||
"authMethod: jwt\n" +
|
||||
"authJWTJWKS: https://not-real.com\n" +
|
||||
"authJWTClaimKey: \"\"",
|
||||
"'authJWTClaimKey' is empty",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
tmpf, err := createTempFile([]byte(ca.conf))
|
||||
|
|
|
@ -287,6 +287,7 @@ func (p *Core) createResources(initial bool) error {
|
|||
HTTPAddress: p.conf.AuthHTTPAddress,
|
||||
HTTPExclude: p.conf.AuthHTTPExclude,
|
||||
JWTJWKS: p.conf.AuthJWTJWKS,
|
||||
JWTClaimKey: p.conf.AuthJWTClaimKey,
|
||||
ReadTimeout: time.Duration(p.conf.ReadTimeout),
|
||||
RTSPAuthMethods: p.conf.RTSPAuthMethods,
|
||||
}
|
||||
|
@ -674,6 +675,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
|||
newConf.AuthHTTPAddress != p.conf.AuthHTTPAddress ||
|
||||
!reflect.DeepEqual(newConf.AuthHTTPExclude, p.conf.AuthHTTPExclude) ||
|
||||
newConf.AuthJWTJWKS != p.conf.AuthJWTJWKS ||
|
||||
newConf.AuthJWTClaimKey != p.conf.AuthJWTClaimKey ||
|
||||
newConf.ReadTimeout != p.conf.ReadTimeout ||
|
||||
!reflect.DeepEqual(newConf.RTSPAuthMethods, p.conf.RTSPAuthMethods)
|
||||
if !closeAuthManager && !reflect.DeepEqual(newConf.AuthInternalUsers, p.conf.AuthInternalUsers) {
|
||||
|
|
|
@ -121,6 +121,8 @@ authHTTPExclude:
|
|||
# This is the JWKS URL that will be used to pull (once) the public key that allows
|
||||
# to validate JWTs.
|
||||
authJWTJWKS:
|
||||
# name of the claim that contains permissions.
|
||||
authJWTClaimKey: mediamtx_permissions
|
||||
|
||||
###############################################
|
||||
# Global settings -> Control API
|
||||
|
|
Loading…
Reference in New Issue