rtsp: rewrite authentication around ServerConn.VerifyCredentials (#4267)

This commit is contained in:
Alessandro Ros
2025-02-18 17:54:13 +01:00
committed by GitHub
parent 7ade2896e5
commit 386be42784
21 changed files with 165 additions and 218 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/alecthomas/kong v1.8.1
github.com/asticode/go-astits v1.13.0
github.com/bluenviron/gohlslib/v2 v2.1.4-0.20250210133907-d3dddacbb9fc
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250214103455-885a9975ef10
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250218163904-55556f1ecfa2
github.com/bluenviron/mediacommon/v2 v2.0.0
github.com/datarhei/gosrt v0.8.0
github.com/fsnotify/fsnotify v1.8.0
+2 -2
View File
@@ -33,8 +33,8 @@ github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYh
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
github.com/bluenviron/gohlslib/v2 v2.1.4-0.20250210133907-d3dddacbb9fc h1:t1i9foTQ+RfFT5Ke9HV845zWtz2vtWQCWV8ZXvpzM4g=
github.com/bluenviron/gohlslib/v2 v2.1.4-0.20250210133907-d3dddacbb9fc/go.mod h1:soTVqoidOT+L08hUSDreM7DebNyjjViUiEvpWlr7EIs=
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250214103455-885a9975ef10 h1:nQI+hp8j2uSSHTumlqG4JcgwdpK+jy8Hj19Z96HRmPo=
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250214103455-885a9975ef10/go.mod h1:87/zjOqku9cRSk7q6tZ4R8N7evB29E11GnwLVUk7sAQ=
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250218163904-55556f1ecfa2 h1:GgPHJTUYTwKBVu/bf0SVSCdXxRz9AMcLnV7tDmiVYko=
github.com/bluenviron/gortsplib/v4 v4.12.4-0.20250218163904-55556f1ecfa2/go.mod h1:87/zjOqku9cRSk7q6tZ4R8N7evB29E11GnwLVUk7sAQ=
github.com/bluenviron/mediacommon/v2 v2.0.0 h1:JinZ9v2x6QeAOzA0cDA6aFe8vQuCrU8OyWEhG2iNzwY=
github.com/bluenviron/mediacommon/v2 v2.0.0/go.mod h1:iHEz1SFIet6zBwAQoh1a92vTQ3dV3LpVFbom6/SLz3k=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
+1 -1
View File
@@ -294,7 +294,7 @@ func (a *API) middlewareAuth(ctx *gin.Context) {
err := a.AuthManager.Authenticate(req)
if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint
if err.(auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.AbortWithStatus(http.StatusUnauthorized)
return
+24 -46
View File
@@ -14,8 +14,6 @@ import (
"time"
"github.com/MicahParks/keyfunc/v3"
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/conf/jsonwrapper"
"github.com/golang-jwt/jwt/v5"
@@ -26,19 +24,19 @@ const (
// PauseAfterError is the pause to apply after an authentication failure.
PauseAfterError = 2 * time.Second
rtspAuthRealm = "IPCAM"
jwtRefreshPeriod = 60 * 60 * time.Second
)
// Error is a authentication error.
type Error struct {
Wrapped error
Message string
AskCredentials bool
}
// Error implements the error interface.
func (e *Error) Error() string {
return "authentication failed: " + e.Message
func (e Error) Error() string {
return "authentication failed: " + e.Wrapped.Error()
}
func matchesPermission(perms []conf.AuthInternalUserPermission, req *Request) bool {
@@ -102,14 +100,13 @@ func (c *customClaims) UnmarshalJSON(b []byte) error {
// Manager is the authentication manager.
type Manager struct {
Method conf.AuthMethod
InternalUsers []conf.AuthInternalUser
HTTPAddress string
HTTPExclude []conf.AuthInternalUserPermission
JWTJWKS string
JWTClaimKey string
ReadTimeout time.Duration
RTSPAuthMethods []auth.VerifyMethod
Method conf.AuthMethod
InternalUsers []conf.AuthInternalUser
HTTPAddress string
HTTPExclude []conf.AuthInternalUserPermission
JWTJWKS string
JWTClaimKey string
ReadTimeout time.Duration
mutex sync.RWMutex
jwtHTTPClient *http.Client
@@ -140,8 +137,8 @@ func (m *Manager) Authenticate(req *Request) error {
}
if err != nil {
return &Error{
Message: err.Error(),
return Error{
Wrapped: err,
AskCredentials: (req.User == "" && req.Pass == ""),
}
}
@@ -150,20 +147,11 @@ func (m *Manager) Authenticate(req *Request) error {
}
func (m *Manager) authenticateInternal(req *Request) error {
var rtspAuthHeader *headers.Authorization
if req.RTSPRequest != nil {
var tmp headers.Authorization
err := tmp.Unmarshal(req.RTSPRequest.Header["Authorization"])
if err == nil {
rtspAuthHeader = &tmp
}
}
m.mutex.RLock()
defer m.mutex.RUnlock()
for _, u := range m.InternalUsers {
if err := m.authenticateWithUser(req, rtspAuthHeader, &u); err == nil {
if ok := m.authenticateWithUser(req, &u); ok {
return nil
}
}
@@ -173,39 +161,29 @@ func (m *Manager) authenticateInternal(req *Request) error {
func (m *Manager) authenticateWithUser(
req *Request,
rtspAuthHeader *headers.Authorization,
u *conf.AuthInternalUser,
) error {
if u.User != "any" && !u.User.Check(req.User) {
return fmt.Errorf("wrong user")
}
) bool {
if len(u.IPs) != 0 && !u.IPs.Contains(req.IP) {
return fmt.Errorf("IP not allowed")
return false
}
if !matchesPermission(u.Permissions, req) {
return fmt.Errorf("user doesn't have permission to perform action")
return false
}
if u.User != "any" {
if req.RTSPRequest != nil && rtspAuthHeader != nil && rtspAuthHeader.Method == headers.AuthMethodDigest {
err := auth.Verify(
req.RTSPRequest,
string(u.User),
string(u.Pass),
m.RTSPAuthMethods,
rtspAuthRealm,
req.RTSPNonce)
if err != nil {
return err
if req.CustomVerifyFunc != nil {
if ok := req.CustomVerifyFunc(string(u.User), string(u.Pass)); !ok {
return false
}
} else {
if !u.User.Check(req.User) || !u.Pass.Check(req.Pass) {
return false
}
} else if !u.Pass.Check(req.Pass) {
return fmt.Errorf("invalid credentials")
}
}
return nil
return true
}
func (m *Manager) authenticateHTTP(req *Request) error {
+11 -30
View File
@@ -12,7 +12,6 @@ import (
"time"
"github.com/MicahParks/jwkset"
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/golang-jwt/jwt/v5"
@@ -56,8 +55,6 @@ func TestAuthInternal(t *testing.T) {
}},
},
},
HTTPAddress: "",
RTSPAuthMethods: nil,
}
switch encryption {
@@ -142,7 +139,7 @@ func TestAuthInternal(t *testing.T) {
}
}
func TestAuthInternalRTSPDigest(t *testing.T) {
func TestAuthInternalCustomVerifyFunc(t *testing.T) {
for _, ca := range []string{"ok", "invalid"} {
t.Run(ca, func(t *testing.T) {
m := Manager{
@@ -158,8 +155,6 @@ func TestAuthInternalRTSPDigest(t *testing.T) {
}},
},
},
HTTPAddress: "",
RTSPAuthMethods: []auth.VerifyMethod{auth.VerifyMethodDigestMD5},
}
u, err := base.ParseURL("rtsp://127.0.0.1:8554/mypath")
@@ -170,25 +165,15 @@ func TestAuthInternalRTSPDigest(t *testing.T) {
URL: u,
}
if ca == "ok" {
var s *auth.Sender
s, err = auth.NewSender(
auth.GenerateWWWAuthenticate([]auth.VerifyMethod{auth.VerifyMethodDigestMD5}, "IPCAM", "mynonce"),
"myuser",
"mypass",
)
require.NoError(t, err)
s.AddAuthorization(req)
} else {
req.Header = base.Header{"Authorization": base.HeaderValue{"garbage"}}
}
req1 := &Request{
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
RTSPRequest: req,
RTSPNonce: "mynonce",
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
require.Equal(t, "myuser", expectedUser)
require.Equal(t, "mypass", expectedPass)
return (ca == "ok")
},
}
req1.FillFromRTSPRequest(req)
err = m.Authenticate(req1)
@@ -216,8 +201,6 @@ func TestAuthInternalCredentialsInBearer(t *testing.T) {
}},
},
},
HTTPAddress: "",
RTSPAuthMethods: []auth.VerifyMethod{auth.VerifyMethodDigestMD5},
}
req := &Request{
@@ -282,9 +265,8 @@ func TestAuthHTTP(t *testing.T) {
defer httpServ.Shutdown(context.Background())
m := Manager{
Method: conf.AuthMethodHTTP,
HTTPAddress: "http://127.0.0.1:9120/auth",
RTSPAuthMethods: nil,
Method: conf.AuthMethodHTTP,
HTTPAddress: "http://127.0.0.1:9120/auth",
}
if outcome == "ok" {
@@ -321,7 +303,6 @@ func TestAuthHTTPExclude(t *testing.T) {
HTTPExclude: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
}},
RTSPAuthMethods: nil,
}
err := m.Authenticate(&Request{
+6 -11
View File
@@ -37,20 +37,17 @@ const (
// Request is an authentication request.
type Request struct {
User string
Pass string
IP net.IP
Action conf.AuthAction
User string
Pass string
IP net.IP
Action conf.AuthAction
CustomVerifyFunc func(expectedUser string, expectedPass string) bool
// only for ActionPublish, ActionRead, ActionPlayback
Path string
Protocol Protocol
ID *uuid.UUID
Query string
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
}
// FillFromRTSPRequest fills User and Pass from a RTSP request.
@@ -58,11 +55,9 @@ func (r *Request) FillFromRTSPRequest(rt *base.Request) {
var rtspAuthHeader headers.Authorization
err := rtspAuthHeader.Unmarshal(rt.Header["Authorization"])
if err == nil {
r.User = rtspAuthHeader.Username
if rtspAuthHeader.Method == headers.AuthMethodBasic {
r.User = rtspAuthHeader.BasicUser
r.Pass = rtspAuthHeader.BasicPass
} else {
r.User = rtspAuthHeader.Username
}
}
}
+21
View File
@@ -1,6 +1,8 @@
package conf
import (
"fmt"
"github.com/bluenviron/mediamtx/internal/conf/jsonwrapper"
)
@@ -18,6 +20,25 @@ type AuthInternalUser struct {
Permissions []AuthInternalUserPermission `json:"permissions"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (d *AuthInternalUser) UnmarshalJSON(b []byte) error {
type alias AuthInternalUser
if err := jsonwrapper.Unmarshal(b, (*alias)(d)); err != nil {
return err
}
// https://github.com/bluenviron/gortsplib/blob/55556f1ecfa2bd51b29fe14eddd70512a0361cbd/server_conn.go#L155-L156
if d.User == "" {
return fmt.Errorf("empty usernames are not supported")
}
if d.User == "any" && d.Pass != "" {
return fmt.Errorf("using a password with 'any' user is not supported")
}
return nil
}
// AuthInternalUsers is a list of AuthInternalUser
type AuthInternalUsers []AuthInternalUser
+18
View File
@@ -407,6 +407,24 @@ func TestConfErrors(t *testing.T) {
" sourceRedirect: invalid://invalid",
`'sourceRedirect' is useless when source is not 'redirect'`,
},
{
"invalid user",
"authInternalUsers:\n" +
"- user:\n" +
" pass: test\n" +
" permissions:\n" +
" - action: publish\n",
"empty usernames are not supported",
},
{
"invalid pass",
"authInternalUsers:\n" +
"- user: any\n" +
" pass: test\n" +
" permissions:\n" +
" - action: publish\n",
`using a password with 'any' user is not supported`,
},
} {
t.Run(ca.name, func(t *testing.T) {
tmpf, err := createTempFile([]byte(ca.conf))
+8 -10
View File
@@ -264,14 +264,13 @@ func (p *Core) createResources(initial bool) error {
if p.authManager == nil {
p.authManager = &auth.Manager{
Method: p.conf.AuthMethod,
InternalUsers: p.conf.AuthInternalUsers,
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,
Method: p.conf.AuthMethod,
InternalUsers: p.conf.AuthInternalUsers,
HTTPAddress: p.conf.AuthHTTPAddress,
HTTPExclude: p.conf.AuthHTTPExclude,
JWTJWKS: p.conf.AuthJWTJWKS,
JWTClaimKey: p.conf.AuthJWTClaimKey,
ReadTimeout: time.Duration(p.conf.ReadTimeout),
}
}
@@ -653,8 +652,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
!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)
newConf.ReadTimeout != p.conf.ReadTimeout
if !closeAuthManager && !reflect.DeepEqual(newConf.AuthInternalUsers, p.conf.AuthInternalUsers) {
p.authManager.ReloadInternalUsers(newConf.AuthInternalUsers)
}
+12 -18
View File
@@ -32,15 +32,12 @@ type PathAccessRequest struct {
SkipAuth bool
// only if skipAuth = false
User string
Pass string
IP net.IP
Proto auth.Protocol
ID *uuid.UUID
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
User string
Pass string
IP net.IP
CustomVerifyFunc func(expectedUser string, expectedPass string) bool
Proto auth.Protocol
ID *uuid.UUID
}
// ToAuthRequest converts a path access request into an authentication request.
@@ -55,12 +52,11 @@ func (r *PathAccessRequest) ToAuthRequest() *auth.Request {
}
return conf.AuthActionRead
}(),
Path: r.Name,
Protocol: r.Proto,
ID: r.ID,
Query: r.Query,
RTSPRequest: r.RTSPRequest,
RTSPNonce: r.RTSPNonce,
CustomVerifyFunc: r.CustomVerifyFunc,
Path: r.Name,
Protocol: r.Proto,
ID: r.ID,
Query: r.Query,
}
}
@@ -69,11 +65,9 @@ func (r *PathAccessRequest) FillFromRTSPRequest(rt *base.Request) {
var rtspAuthHeader headers.Authorization
err := rtspAuthHeader.Unmarshal(rt.Header["Authorization"])
if err == nil {
r.User = rtspAuthHeader.Username
if rtspAuthHeader.Method == headers.AuthMethodBasic {
r.User = rtspAuthHeader.BasicUser
r.Pass = rtspAuthHeader.BasicPass
} else {
r.User = rtspAuthHeader.Username
}
}
}
+1 -1
View File
@@ -130,7 +130,7 @@ func (m *Metrics) middlewareAuth(ctx *gin.Context) {
err := m.AuthManager.Authenticate(req)
if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint
if err.(auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.AbortWithStatus(http.StatusUnauthorized)
return
+1 -1
View File
@@ -126,7 +126,7 @@ func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
err := s.AuthManager.Authenticate(req)
if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint
if err.(auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return false
+1 -1
View File
@@ -105,7 +105,7 @@ func (pp *PPROF) middlewareAuth(ctx *gin.Context) {
err := pp.AuthManager.Authenticate(req)
if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint
if err.(auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.AbortWithStatus(http.StatusUnauthorized)
return
+1 -1
View File
@@ -159,7 +159,7 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
AccessRequest: req,
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
if terr.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
+2 -2
View File
@@ -168,7 +168,7 @@ func (c *conn) runRead(conn *rtmp.Conn, u *url.URL) error {
},
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
@@ -235,7 +235,7 @@ func (c *conn) runPublish(conn *rtmp.Conn, u *url.URL) error {
},
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
+26 -46
View File
@@ -10,6 +10,8 @@ import (
"github.com/bluenviron/gortsplib/v4"
rtspauth "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/auth"
@@ -20,10 +22,6 @@ import (
"github.com/bluenviron/mediamtx/internal/logger"
)
const (
rtspAuthRealm = "IPCAM"
)
func absoluteURL(req *base.Request, v string) string {
if strings.HasPrefix(v, "/") {
ur := base.URL{
@@ -37,6 +35,12 @@ func absoluteURL(req *base.Request, v string) string {
return v
}
func credentialsProvided(req *base.Request) bool {
var auth headers.Authorization
err := auth.Unmarshal(req.Header["Authorization"])
return err == nil && auth.Username != ""
}
type connParent interface {
logger.Writer
findSessionByRSessionUnsafe(rsession *gortsplib.ServerSession) *session
@@ -59,8 +63,6 @@ type conn struct {
uuid uuid.UUID
created time.Time
onDisconnectHook func()
authNonce string
authFailures int
}
func (c *conn) initialize() {
@@ -135,24 +137,15 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}
ctx.Path = ctx.Path[1:]
if c.authNonce == "" {
var err error
c.authNonce, err = rtspauth.GenerateNonce()
if err != nil {
return &base.Response{
StatusCode: base.StatusInternalServerError,
}, nil, err
}
}
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
return c.rconn.VerifyCredentials(ctx.Request, expectedUser, expectedPass)
},
}
req.FillFromRTSPRequest(ctx.Request)
@@ -161,10 +154,10 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
})
if res.Err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(res.Err, &terr) {
res, err := c.handleAuthError(terr)
return res, nil, err
res, err2 := c.handleAuthError(ctx.Request)
return res, nil, err2
}
var terr2 defs.PathNoOnePublishingError
@@ -200,30 +193,17 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}, stream, nil
}
func (c *conn) handleAuthError(authErr error) (*base.Response, error) {
c.authFailures++
// VLC with login prompt sends 4 requests:
// 1) without credentials
// 2) with password but without username
// 3) without credentials
// 4) with password and username
// therefore we must allow up to 3 failures
if c.authFailures <= 3 {
return &base.Response{
StatusCode: base.StatusUnauthorized,
Header: base.Header{
"WWW-Authenticate": rtspauth.GenerateWWWAuthenticate(c.authMethods, rtspAuthRealm, c.authNonce),
},
}, nil
func (c *conn) handleAuthError(req *base.Request) (*base.Response, error) {
if credentialsProvided(req) {
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
}
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
// let gortsplib decide whether connection should be terminated,
// depending on whether credentials have been provided or not.
return &base.Response{
StatusCode: base.StatusUnauthorized,
}, authErr
}, liberrors.ErrServerAuth{}
}
func (c *conn) apiItem() *defs.APIRTSPConn {
+1
View File
@@ -106,6 +106,7 @@ func (s *Server) Initialize() error {
WriteTimeout: time.Duration(s.WriteTimeout),
WriteQueueSize: s.WriteQueueSize,
RTSPAddress: s.Address,
AuthMethods: s.AuthMethods,
}
if s.UseUDP {
+3 -3
View File
@@ -72,7 +72,7 @@ func TestServerPublish(t *testing.T) {
pathManager := &test.PathManager{
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
if req.AccessRequest.User == "" && req.AccessRequest.Pass == "" {
return nil, &auth.Error{Message: "", AskCredentials: true}
return nil, auth.Error{Message: "", AskCredentials: true}
}
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
@@ -162,7 +162,7 @@ func TestServerRead(t *testing.T) {
pathManager := &test.PathManager{
DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
if req.AccessRequest.User == "" && req.AccessRequest.Pass == "" {
return defs.PathDescribeRes{Err: &auth.Error{Message: "", AskCredentials: true}}
return defs.PathDescribeRes{Err: auth.Error{Message: "", AskCredentials: true}}
}
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
@@ -282,7 +282,7 @@ func TestServerRedirect(t *testing.T) {
}
if req.AccessRequest.User == "" && req.AccessRequest.Pass == "" {
return defs.PathDescribeRes{Err: &auth.Error{Message: "", AskCredentials: true}}
return defs.PathDescribeRes{Err: auth.Error{Message: "", AskCredentials: true}}
}
require.Equal(t, "path2", req.AccessRequest.Name)
+21 -40
View File
@@ -9,7 +9,6 @@ import (
"time"
"github.com/bluenviron/gortsplib/v4"
rtspauth "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/google/uuid"
"github.com/pion/rtp"
@@ -101,25 +100,16 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
}
ctx.Path = ctx.Path[1:]
if c.authNonce == "" {
var err error
c.authNonce, err = rtspauth.GenerateNonce()
if err != nil {
return &base.Response{
StatusCode: base.StatusInternalServerError,
}, err
}
}
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
Publish: true,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
Name: ctx.Path,
Query: ctx.Query,
Publish: true,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
return c.rconn.VerifyCredentials(ctx.Request, expectedUser, expectedPass)
},
}
req.FillFromRTSPRequest(ctx.Request)
@@ -128,9 +118,9 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
AccessRequest: req,
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
return c.handleAuthError(terr)
return c.handleAuthError(ctx.Request)
}
return &base.Response{
@@ -175,24 +165,15 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
switch s.rsession.State() {
case gortsplib.ServerSessionStateInitial, gortsplib.ServerSessionStatePrePlay: // play
if c.authNonce == "" {
var err error
c.authNonce, err = rtspauth.GenerateNonce()
if err != nil {
return &base.Response{
StatusCode: base.StatusInternalServerError,
}, nil, err
}
}
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
return c.rconn.VerifyCredentials(ctx.Request, expectedUser, expectedPass)
},
}
req.FillFromRTSPRequest(ctx.Request)
@@ -201,9 +182,9 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
AccessRequest: req,
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
res, err2 := c.handleAuthError(terr)
res, err2 := c.handleAuthError(ctx.Request)
return res, nil, err2
}
+2 -2
View File
@@ -151,7 +151,7 @@ func (c *conn) runPublish(streamID *streamID) error {
},
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
@@ -250,7 +250,7 @@ func (c *conn) runRead(streamID *streamID) error {
},
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
// wait some seconds to mitigate brute force attacks
<-time.After(auth.PauseAfterError)
+2 -2
View File
@@ -136,7 +136,7 @@ func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string,
AccessRequest: req,
})
if err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
if terr.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
@@ -198,7 +198,7 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool)
httpRequest: ctx.Request,
})
if res.err != nil {
var terr *auth.Error
var terr auth.Error
if errors.As(err, &terr) {
if terr.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)