Browse Source

webrtc, hls: show IP in logs in case of failed authentication (#2099)

pull/2100/head
Alessandro Ros 2 years ago committed by GitHub
parent
commit
0137734294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      internal/core/authentication.go
  2. 8
      internal/core/hls_http_server.go
  3. 17
      internal/core/path.go
  4. 16
      internal/core/path_manager.go
  5. 8
      internal/core/rtmp_conn.go
  6. 6
      internal/core/rtsp_conn.go
  7. 10
      internal/core/rtsp_session.go
  8. 7
      internal/core/webrtc_http_server.go
  9. 4
      internal/core/webrtc_session.go

78
internal/core/authentication.go

@ -34,6 +34,15 @@ func checkCredential(right string, guess string) bool { @@ -34,6 +34,15 @@ func checkCredential(right string, guess string) bool {
return right == guess
}
type errAuthentication struct {
message string
}
// Error implements the error interface.
func (e *errAuthentication) Error() string {
return "authentication failed: " + e.message
}
type authProtocol string
const (
@ -43,16 +52,23 @@ const ( @@ -43,16 +52,23 @@ const (
authProtocolWebRTC authProtocol = "webrtc"
)
func externalAuth(
type authCredentials struct {
query string
ip net.IP
user string
pass string
proto authProtocol
id *uuid.UUID
rtspRequest *base.Request
rtspBaseURL *url.URL
rtspNonce string
}
func doExternalAuthentication(
ur string,
ip string,
user string,
password string,
path string,
protocol authProtocol,
id *uuid.UUID,
publish bool,
query string,
credentials authCredentials,
) error {
enc, _ := json.Marshal(struct {
IP string `json:"ip"`
@ -64,19 +80,19 @@ func externalAuth( @@ -64,19 +80,19 @@ func externalAuth(
Action string `json:"action"`
Query string `json:"query"`
}{
IP: ip,
User: user,
Password: password,
IP: credentials.ip.String(),
User: credentials.user,
Password: credentials.pass,
Path: path,
Protocol: string(protocol),
ID: id,
Protocol: string(credentials.proto),
ID: credentials.id,
Action: func() string {
if publish {
return "publish"
}
return "read"
}(),
Query: query,
Query: credentials.query,
})
res, err := http.Post(ur, "application/json", bytes.NewReader(enc))
if err != nil {
@ -86,28 +102,15 @@ func externalAuth( @@ -86,28 +102,15 @@ func externalAuth(
if res.StatusCode < 200 || res.StatusCode > 299 {
if resBody, err := io.ReadAll(res.Body); err == nil && len(resBody) != 0 {
return fmt.Errorf("external authentication replied with code %d: %s", res.StatusCode, string(resBody))
return fmt.Errorf("server replied with code %d: %s", res.StatusCode, string(resBody))
}
return fmt.Errorf("external authentication replied with code %d", res.StatusCode)
return fmt.Errorf("server replied with code %d", res.StatusCode)
}
return nil
}
type authCredentials struct {
query string
ip net.IP
user string
pass string
proto authProtocol
id *uuid.UUID
rtspRequest *base.Request
rtspBaseURL *url.URL
rtspNonce string
}
func authenticate(
func doAuthentication(
externalAuthenticationURL string,
rtspAuthMethods conf.AuthMethods,
pathName string,
@ -125,19 +128,14 @@ func authenticate( @@ -125,19 +128,14 @@ func authenticate(
}
if externalAuthenticationURL != "" {
err := externalAuth(
err := doExternalAuthentication(
externalAuthenticationURL,
credentials.ip.String(),
credentials.user,
credentials.pass,
pathName,
credentials.proto,
credentials.id,
publish,
credentials.query,
credentials,
)
if err != nil {
return fmt.Errorf("external authentication failed: %s", err)
return &errAuthentication{message: fmt.Sprintf("external authentication failed: %s", err)}
}
}
@ -157,7 +155,7 @@ func authenticate( @@ -157,7 +155,7 @@ func authenticate(
if pathIPs != nil {
if !ipEqualOrInRange(credentials.ip, pathIPs) {
return fmt.Errorf("IP '%s' not allowed", credentials.ip)
return &errAuthentication{message: fmt.Sprintf("IP %s not allowed", credentials.ip)}
}
}
@ -172,11 +170,11 @@ func authenticate( @@ -172,11 +170,11 @@ func authenticate(
"IPCAM",
credentials.rtspNonce)
if err != nil {
return err
return &errAuthentication{message: err.Error()}
}
} else if !checkCredential(pathUser, credentials.user) ||
!checkCredential(pathPass, credentials.pass) {
return fmt.Errorf("invalid credentials")
return &errAuthentication{message: "invalid credentials"}
}
}

8
internal/core/hls_http_server.go

@ -154,14 +154,18 @@ func (s *hlsHTTPServer) onRequest(ctx *gin.Context) { @@ -154,14 +154,18 @@ func (s *hlsHTTPServer) onRequest(ctx *gin.Context) {
},
})
if res.err != nil {
if terr, ok := res.err.(pathErrAuth); ok {
if terr, ok := res.err.(*errAuthentication); ok {
if !hasCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return
}
s.Log(logger.Info, "authentication failed: %v", terr.wrapped)
ip := ctx.ClientIP()
_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
remoteAddr := net.JoinHostPort(ip, port)
s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.message)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return
}

17
internal/core/path.go

@ -24,21 +24,12 @@ func newEmptyTimer() *time.Timer { @@ -24,21 +24,12 @@ func newEmptyTimer() *time.Timer {
return t
}
type pathErrAuth struct {
wrapped error
}
// Error implements the error interface.
func (e pathErrAuth) Error() string {
return "authentication failed"
}
type pathErrNoOnePublishing struct {
type errPathNoOnePublishing struct {
pathName string
}
// Error implements the error interface.
func (e pathErrNoOnePublishing) Error() string {
func (e errPathNoOnePublishing) Error() string {
return fmt.Sprintf("no one is publishing to path '%s'", e.pathName)
}
@ -739,7 +730,7 @@ func (pa *path) handleDescribe(req pathDescribeReq) { @@ -739,7 +730,7 @@ func (pa *path) handleDescribe(req pathDescribeReq) {
return
}
req.res <- pathDescribeRes{err: pathErrNoOnePublishing{pathName: pa.name}}
req.res <- pathDescribeRes{err: errPathNoOnePublishing{pathName: pa.name}}
}
func (pa *path) handlePublisherRemove(req pathPublisherRemoveReq) {
@ -855,7 +846,7 @@ func (pa *path) handleReaderAdd(req pathReaderAddReq) { @@ -855,7 +846,7 @@ func (pa *path) handleReaderAdd(req pathReaderAddReq) {
return
}
req.res <- pathReaderSetupPlayRes{err: pathErrNoOnePublishing{pathName: pa.name}}
req.res <- pathReaderSetupPlayRes{err: errPathNoOnePublishing{pathName: pa.name}}
}
func (pa *path) handleReaderAddPost(req pathReaderAddReq) {

16
internal/core/path_manager.go

@ -235,10 +235,10 @@ outer: @@ -235,10 +235,10 @@ outer:
continue
}
err = authenticate(pm.externalAuthenticationURL, pm.authMethods,
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods,
req.name, pathConf, req.publish, req.credentials)
if err != nil {
req.res <- pathGetConfForPathRes{err: pathErrAuth{wrapped: err}}
req.res <- pathGetConfForPathRes{err: err}
continue
}
@ -251,9 +251,9 @@ outer: @@ -251,9 +251,9 @@ outer:
continue
}
err = authenticate(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, false, req.credentials)
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, false, req.credentials)
if err != nil {
req.res <- pathDescribeRes{err: pathErrAuth{wrapped: err}}
req.res <- pathDescribeRes{err: err}
continue
}
@ -272,9 +272,9 @@ outer: @@ -272,9 +272,9 @@ outer:
}
if !req.skipAuth {
err = authenticate(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, false, req.credentials)
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, false, req.credentials)
if err != nil {
req.res <- pathReaderSetupPlayRes{err: pathErrAuth{wrapped: err}}
req.res <- pathReaderSetupPlayRes{err: err}
continue
}
}
@ -294,9 +294,9 @@ outer: @@ -294,9 +294,9 @@ outer:
}
if !req.skipAuth {
err = authenticate(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, true, req.credentials)
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods, req.pathName, pathConf, true, req.credentials)
if err != nil {
req.res <- pathPublisherAnnounceRes{err: pathErrAuth{wrapped: err}}
req.res <- pathPublisherAnnounceRes{err: err}
continue
}
}

8
internal/core/rtmp_conn.go

@ -365,10 +365,10 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error { @@ -365,10 +365,10 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
})
if res.err != nil {
if terr, ok := res.err.(pathErrAuth); ok {
if terr, ok := res.err.(*errAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(rtmpConnPauseAfterAuthError)
return terr.wrapped
return terr
}
return res.err
}
@ -780,10 +780,10 @@ func (c *rtmpConn) runPublish(u *url.URL) error { @@ -780,10 +780,10 @@ func (c *rtmpConn) runPublish(u *url.URL) error {
})
if res.err != nil {
if terr, ok := res.err.(pathErrAuth); ok {
if terr, ok := res.err.(*errAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(rtmpConnPauseAfterAuthError)
return terr.wrapped
return terr
}
return res.err
}

6
internal/core/rtsp_conn.go

@ -155,11 +155,11 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, @@ -155,11 +155,11 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
if res.err != nil {
switch terr := res.err.(type) {
case pathErrAuth:
res, err := c.handleAuthError(terr.wrapped)
case *errAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
case pathErrNoOnePublishing:
case errPathNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, res.err

10
internal/core/rtsp_session.go

@ -140,8 +140,8 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno @@ -140,8 +140,8 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
if res.err != nil {
switch terr := res.err.(type) {
case pathErrAuth:
return c.handleAuthError(terr.wrapped)
case *errAuthentication:
return c.handleAuthError(terr)
default:
return &base.Response{
@ -219,11 +219,11 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt @@ -219,11 +219,11 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
if res.err != nil {
switch terr := res.err.(type) {
case pathErrAuth:
res, err := c.handleAuthError(terr.wrapped)
case *errAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
case pathErrNoOnePublishing:
case errPathNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, res.err

7
internal/core/webrtc_http_server.go

@ -293,6 +293,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -293,6 +293,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
ip := ctx.ClientIP()
_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
remoteAddr := net.JoinHostPort(ip, port)
user, pass, hasCredentials := ctx.Request.BasicAuth()
// if request doesn't belong to a session, check authentication here
@ -309,14 +310,14 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -309,14 +310,14 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
},
})
if res.err != nil {
if terr, ok := res.err.(pathErrAuth); ok {
if terr, ok := res.err.(*errAuthentication); ok {
if !hasCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return
}
s.Log(logger.Info, "authentication failed: %v", terr.wrapped)
s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.message)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return
}
@ -358,7 +359,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -358,7 +359,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
res := s.parent.sessionNew(webRTCSessionNewReq{
pathName: dir,
remoteAddr: net.JoinHostPort(ip, port),
remoteAddr: remoteAddr,
query: ctx.Request.URL.RawQuery,
user: user,
pass: pass,

4
internal/core/webrtc_session.go

@ -239,7 +239,7 @@ func (s *webRTCSession) runPublish() (int, error) { @@ -239,7 +239,7 @@ func (s *webRTCSession) runPublish() (int, error) {
},
})
if res.err != nil {
if _, ok := res.err.(pathErrAuth); ok {
if _, ok := res.err.(*errAuthentication); ok {
return http.StatusUnauthorized, res.err
}
return http.StatusBadRequest, res.err
@ -362,7 +362,7 @@ func (s *webRTCSession) runRead() (int, error) { @@ -362,7 +362,7 @@ func (s *webRTCSession) runRead() (int, error) {
},
})
if res.err != nil {
if _, ok := res.err.(pathErrAuth); ok {
if _, ok := res.err.(*errAuthentication); ok {
return http.StatusUnauthorized, res.err
}
if strings.HasPrefix(res.err.Error(), "no one is publishing") {

Loading…
Cancel
Save