Browse Source

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

pull/2100/head
Alessandro Ros 3 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 {
return right == guess 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 type authProtocol string
const ( const (
@ -43,16 +52,23 @@ const (
authProtocolWebRTC authProtocol = "webrtc" 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, ur string,
ip string,
user string,
password string,
path string, path string,
protocol authProtocol,
id *uuid.UUID,
publish bool, publish bool,
query string, credentials authCredentials,
) error { ) error {
enc, _ := json.Marshal(struct { enc, _ := json.Marshal(struct {
IP string `json:"ip"` IP string `json:"ip"`
@ -64,19 +80,19 @@ func externalAuth(
Action string `json:"action"` Action string `json:"action"`
Query string `json:"query"` Query string `json:"query"`
}{ }{
IP: ip, IP: credentials.ip.String(),
User: user, User: credentials.user,
Password: password, Password: credentials.pass,
Path: path, Path: path,
Protocol: string(protocol), Protocol: string(credentials.proto),
ID: id, ID: credentials.id,
Action: func() string { Action: func() string {
if publish { if publish {
return "publish" return "publish"
} }
return "read" return "read"
}(), }(),
Query: query, Query: credentials.query,
}) })
res, err := http.Post(ur, "application/json", bytes.NewReader(enc)) res, err := http.Post(ur, "application/json", bytes.NewReader(enc))
if err != nil { if err != nil {
@ -86,28 +102,15 @@ func externalAuth(
if res.StatusCode < 200 || res.StatusCode > 299 { if res.StatusCode < 200 || res.StatusCode > 299 {
if resBody, err := io.ReadAll(res.Body); err == nil && len(resBody) != 0 { 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("server replied with code %d", res.StatusCode)
return fmt.Errorf("external authentication replied with code %d", res.StatusCode)
} }
return nil return nil
} }
type authCredentials struct { func doAuthentication(
query string
ip net.IP
user string
pass string
proto authProtocol
id *uuid.UUID
rtspRequest *base.Request
rtspBaseURL *url.URL
rtspNonce string
}
func authenticate(
externalAuthenticationURL string, externalAuthenticationURL string,
rtspAuthMethods conf.AuthMethods, rtspAuthMethods conf.AuthMethods,
pathName string, pathName string,
@ -125,19 +128,14 @@ func authenticate(
} }
if externalAuthenticationURL != "" { if externalAuthenticationURL != "" {
err := externalAuth( err := doExternalAuthentication(
externalAuthenticationURL, externalAuthenticationURL,
credentials.ip.String(),
credentials.user,
credentials.pass,
pathName, pathName,
credentials.proto,
credentials.id,
publish, publish,
credentials.query, credentials,
) )
if err != nil { 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(
if pathIPs != nil { if pathIPs != nil {
if !ipEqualOrInRange(credentials.ip, pathIPs) { 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(
"IPCAM", "IPCAM",
credentials.rtspNonce) credentials.rtspNonce)
if err != nil { if err != nil {
return err return &errAuthentication{message: err.Error()}
} }
} else if !checkCredential(pathUser, credentials.user) || } else if !checkCredential(pathUser, credentials.user) ||
!checkCredential(pathPass, credentials.pass) { !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) {
}, },
}) })
if res.err != nil { if res.err != nil {
if terr, ok := res.err.(pathErrAuth); ok { if terr, ok := res.err.(*errAuthentication); ok {
if !hasCredentials { if !hasCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized) ctx.Writer.WriteHeader(http.StatusUnauthorized)
return 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) ctx.Writer.WriteHeader(http.StatusUnauthorized)
return return
} }

17
internal/core/path.go

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

16
internal/core/path_manager.go

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

8
internal/core/rtmp_conn.go

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

6
internal/core/rtsp_conn.go

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

10
internal/core/rtsp_session.go

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

7
internal/core/webrtc_http_server.go

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

4
internal/core/webrtc_session.go

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

Loading…
Cancel
Save