Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ components:
$ref: "#/components/schemas/AuthInternalUserPermission"
authHTTPFingerprint:
type: string
authHTTPForceInternalUsersForActions:
type: array
items:
$ref: "#/components/schemas/AuthInternalUserPermission"
authInternalUsers:
type: array
items:
Expand Down
21 changes: 21 additions & 0 deletions docs/2-features/06-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,27 @@ authHTTPExclude:
- action: pprof
```

Some of the excluded actions can be validated through internal users instead of being left unauthenticated:

```yml
authHTTPExclude:
- action: publish
- action: api
- action: metrics
- action: pprof
authHTTPForceInternalUsersForActions:
- action: publish
- action: api
authInternalUsers:
- user: internal
pass: internalpass
permissions:
- action: publish
- action: api
```

In this example, `read` is still validated through the external HTTP server, while `publish` and `api` are validated through internal users. `metrics` and `pprof` remain excluded from any authentication.

If the authentication server uses HTTPS and has a self-signed or invalid TLS certificate, you can provide the fingerprint of the certificate to validate it anyway:

```yml
Expand Down
30 changes: 17 additions & 13 deletions internal/auth/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ func getToken(tokenInHTTPQuery bool, req *Request) string {

// Manager is the authentication manager.
type Manager struct {
Method conf.AuthMethod
InternalUsers []conf.AuthInternalUser
HTTPAddress string
HTTPFingerprint string
HTTPExclude []conf.AuthInternalUserPermission
JWTJWKS string
JWTJWKSFingerprint string
JWTClaimKey string
JWTExclude []conf.AuthInternalUserPermission
JWTInHTTPQuery *bool
JWTIssuer string
JWTAudience string
ReadTimeout time.Duration
Method conf.AuthMethod
InternalUsers []conf.AuthInternalUser
HTTPAddress string
HTTPFingerprint string
HTTPExclude []conf.AuthInternalUserPermission
HTTPForceInternalUsersForActions []conf.AuthInternalUserPermission
JWTJWKS string
JWTJWKSFingerprint string
JWTClaimKey string
JWTExclude []conf.AuthInternalUserPermission
JWTInHTTPQuery *bool
JWTIssuer string
JWTAudience string
ReadTimeout time.Duration

mutex sync.RWMutex
jwksLastRefresh time.Time
Expand Down Expand Up @@ -190,6 +191,9 @@ func (m *Manager) authenticateWithUser(

func (m *Manager) authenticateHTTP(req *Request, token string) (string, error) {
if matchesPermission(m.HTTPExclude, req) {
if matchesPermission(m.HTTPForceInternalUsersForActions, req) {
return m.authenticateInternal(req)
}
return "", nil
}

Expand Down
111 changes: 111 additions & 0 deletions internal/auth/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,117 @@ func TestAuthHTTPExclude(t *testing.T) {
require.Equal(t, "", user)
}

func TestAuthHTTPForceInternalUsersForActions(t *testing.T) {
m := Manager{
Method: conf.AuthMethodHTTP,
HTTPAddress: "http://not-to-be-used:9120/auth",
HTTPExclude: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}, {
Action: conf.AuthActionAPI,
}},
HTTPForceInternalUsersForActions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}},
InternalUsers: []conf.AuthInternalUser{
{
User: conf.Credential("pubuser"),
Pass: conf.Credential("pubpass"),
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}},
},
{
User: conf.Credential("apiuser"),
Pass: conf.Credential("apipass"),
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionAPI,
}},
},
},
}

t.Run("publish with valid credentials", func(t *testing.T) {
user, err := m.Authenticate(&Request{
Action: conf.AuthActionPublish,
Path: "teststream",
Protocol: ProtocolRTSP,
Credentials: &Credentials{
User: "pubuser",
Pass: "pubpass",
},
IP: net.ParseIP("127.0.0.1"),
})
require.Nil(t, err)
require.Equal(t, "pubuser", user)
})

t.Run("publish with invalid credentials", func(t *testing.T) {
_, err := m.Authenticate(&Request{
Action: conf.AuthActionPublish,
Path: "teststream",
Protocol: ProtocolRTSP,
Credentials: &Credentials{
User: "wrong",
Pass: "wrong",
},
IP: net.ParseIP("127.0.0.1"),
})
require.NotNil(t, err)
})

t.Run("api excluded but not forced to internal", func(t *testing.T) {
user, err := m.Authenticate(&Request{
Action: conf.AuthActionAPI,
Path: "",
Protocol: ProtocolRTSP,
Credentials: &Credentials{
User: "",
Pass: "",
},
IP: net.ParseIP("127.0.0.1"),
})
require.Nil(t, err)
require.Equal(t, "", user)
})

t.Run("second matching user", func(t *testing.T) {
m2 := m
m2.HTTPForceInternalUsersForActions = []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}}
m2.InternalUsers = []conf.AuthInternalUser{
{
User: conf.Credential("wronguser"),
Pass: conf.Credential("wrongpass"),
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}},
},
{
User: conf.Credential("pubuser"),
Pass: conf.Credential("pubpass"),
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}},
},
}

user, err := m2.Authenticate(&Request{
Action: conf.AuthActionPublish,
Path: "teststream",
Protocol: ProtocolRTSP,
Credentials: &Credentials{
User: "pubuser",
Pass: "pubpass",
},
IP: net.ParseIP("127.0.0.1"),
})
require.Nil(t, err)
require.Equal(t, "pubuser", user)
})
}

func TestAuthJWT(t *testing.T) {
for _, ca := range []string{"object", "string"} {
t.Run(ca, func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ type Conf struct {
AuthHTTPAddress string `json:"authHTTPAddress"`
ExternalAuthenticationURL *string `json:"externalAuthenticationURL,omitempty" deprecated:"true"`
AuthHTTPFingerprint string `json:"authHTTPFingerprint"`
AuthHTTPExclude []AuthInternalUserPermission `json:"authHTTPExclude"`
AuthHTTPExclude []AuthInternalUserPermission `json:"authHTTPExclude"`
AuthHTTPForceInternalUsersForActions []AuthInternalUserPermission `json:"authHTTPForceInternalUsersForActions"`
AuthJWTJWKS string `json:"authJWTJWKS"`
AuthJWTJWKSFingerprint string `json:"authJWTJWKSFingerprint"`
AuthJWTClaimKey string `json:"authJWTClaimKey"`
Expand Down
4 changes: 3 additions & 1 deletion internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ func (p *Core) createResources(initial bool) error {
InternalUsers: p.conf.AuthInternalUsers,
HTTPAddress: p.conf.AuthHTTPAddress,
HTTPFingerprint: p.conf.AuthHTTPFingerprint,
HTTPExclude: p.conf.AuthHTTPExclude,
HTTPExclude: p.conf.AuthHTTPExclude,
HTTPForceInternalUsersForActions: p.conf.AuthHTTPForceInternalUsersForActions,
JWTJWKS: p.conf.AuthJWTJWKS,
JWTJWKSFingerprint: p.conf.AuthJWTJWKSFingerprint,
JWTClaimKey: p.conf.AuthJWTClaimKey,
Expand Down Expand Up @@ -782,6 +783,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.AuthHTTPAddress != p.conf.AuthHTTPAddress ||
newConf.AuthHTTPFingerprint != p.conf.AuthHTTPFingerprint ||
!reflect.DeepEqual(newConf.AuthHTTPExclude, p.conf.AuthHTTPExclude) ||
!reflect.DeepEqual(newConf.AuthHTTPForceInternalUsersForActions, p.conf.AuthHTTPForceInternalUsersForActions) ||
newConf.AuthJWTJWKS != p.conf.AuthJWTJWKS ||
newConf.AuthJWTJWKSFingerprint != p.conf.AuthJWTJWKSFingerprint ||
newConf.AuthJWTClaimKey != p.conf.AuthJWTClaimKey ||
Expand Down
4 changes: 4 additions & 0 deletions mediamtx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ authHTTPExclude:
- action: api
- action: metrics
- action: pprof
# Actions excluded from HTTP-based authentication that must be validated through
# authInternalUsers instead. Format is the same as the one of user permissions.
# An action must be present in both authHTTPExclude and this parameter.
authHTTPForceInternalUsersForActions: []

# JWT-based authentication.
# Users have to log in through an external identity server and obtain a JWT.
Expand Down