From 3050893d32381f4278e681e804f1cdbe8add4b5f Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Thu, 31 Dec 2020 19:47:25 +0100 Subject: [PATCH] support hashed credentials --- README.md | 79 +++++++++++++++++++++++---------------- go.mod | 2 +- go.sum | 4 +- internal/client/client.go | 14 +++---- internal/conf/pathconf.go | 11 ++---- main_test.go | 35 +++++++++++++++++ rtsp-simple-server.yml | 4 ++ 7 files changed, 100 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 9b613577..23773d15 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Features: * [Advanced usage and FAQs](#advanced-usage-and-faqs) * [Configuration](#configuration) * [Encryption](#encryption) + * [Authentication](#authentication) * [RTSP proxy mode](#rtsp-proxy-mode) * [Publish a webcam](#publish-a-webcam) * [Publish a Raspberry Pi Camera](#publish-a-raspberry-pi-camera) @@ -37,7 +38,6 @@ Features: * [On-demand publishing](#on-demand-publishing) * [Redirect to another server](#redirect-to-another-server) * [Fallback stream](#fallback-stream) - * [Authentication](#authentication) * [Start on boot with systemd](#start-on-boot-with-systemd) * [Monitoring](#monitoring) * [Command-line usage](#command-line-usage) @@ -167,6 +167,52 @@ gst-launch-1.0 rtspsrc location=rtsps://ip:8555/... tls-validation-flags=0 If the client is _VLC_, encryption can't be deployed, since _VLC_ doesn't support it. +### Authentication + +Edit `rtsp-simple-server.yml` and replace everything inside section `paths` with the following content: + +```yml +paths: + all: + publishUser: admin + publishPass: mypassword +``` + +Only publishers that provide both username and password will be able to proceed: + +``` +ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://admin:mypassword@localhost:8554/mystream +``` + +It's possible to setup authentication for readers too: + +```yml +paths: + all: + publishUser: admin + publishPass: mypass + + readUser: user + readPass: userpass +``` + +If storing plain credentials in the configuration file is a security problem, username and passwords can be stored as sha256-hashed values; a value must be converted into sha256: + +``` +echo -n "userpass" | openssl dgst -binary -sha256 | openssl base64 +``` + +Then stored with the `sha256:` prefix: + +```yml +paths: + all: + readUser: sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo= + readPass: sha256:BdSWkrdV+ZxFBLUQQY7+7uv9RmiSVA8nrPmjGjJtZQQ= +``` + +**WARNING**: enable encryption or use a VPN to ensure that no one is intercepting and reading the credentials. + ### RTSP proxy mode _rtsp-simple-server_ is also a RTSP proxy, that is usually deployed in one of these scenarios: @@ -321,37 +367,6 @@ paths: fallback: rtsp://otherurl/otherpath ``` -### Authentication - -Edit `rtsp-simple-server.yml` and replace everything inside section `paths` with the following content: - -```yml -paths: - all: - publishUser: admin - publishPass: mypassword -``` - -Only publishers that provide both username and password will be able to proceed: - -``` -ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://admin:mypassword@localhost:8554/mystream -``` - -It's possible to setup authentication for readers too: - -```yml -paths: - all: - publishUser: admin - publishPass: mypassword - - readUser: user - readPass: userpassword -``` - -**WARNING**: enable encryption or use a VPN to ensure that no one is intercepting and reading the credentials. - ### Start on boot with systemd Systemd is the service manager used by Ubuntu, Debian and many other Linux distributions, and allows to launch rtsp-simple-server on boot. diff --git a/go.mod b/go.mod index 1873f639..c196fe1b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.15 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect - github.com/aler9/gortsplib v0.0.0-20201217115908-9a602f77f77c + github.com/aler9/gortsplib v0.0.0-20201231182741-9bd587e576f1 github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 diff --git a/go.sum b/go.sum index bf9c610a..7610ac4f 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aler9/gortsplib v0.0.0-20201217115908-9a602f77f77c h1:Qbb+ccjoEoPPCJLofGtdvWwxzCS4v+vogK43Om9D3OM= -github.com/aler9/gortsplib v0.0.0-20201217115908-9a602f77f77c/go.mod h1:8P09VjpiPJFyfkVosyF5/TY82jNwkMN165NS/7sc32I= +github.com/aler9/gortsplib v0.0.0-20201231182741-9bd587e576f1 h1:RqOBalGfTyA43DigWv64uKNEiAIJT9IPiXrjkDbR/Lo= +github.com/aler9/gortsplib v0.0.0-20201231182741-9bd587e576f1/go.mod h1:8P09VjpiPJFyfkVosyF5/TY82jNwkMN165NS/7sc32I= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/internal/client/client.go b/internal/client/client.go index 6b698aed..57f45f0b 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -107,7 +107,7 @@ type Client struct { path Path authUser string authPass string - authHelper *auth.Server + authValidator *auth.Validator authFailures int streamProtocol gortsplib.StreamProtocol streamTracks map[int]*streamTrack @@ -896,14 +896,14 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{ // validate user if user != "" { - // reset authHelper every time the credentials change - if c.authHelper == nil || c.authUser != user || c.authPass != pass { + // reset authValidator every time the credentials change + if c.authValidator == nil || c.authUser != user || c.authPass != pass { c.authUser = user c.authPass = pass - c.authHelper = auth.NewServer(user, pass, authMethods) + c.authValidator = auth.NewValidator(user, pass, authMethods) } - err := c.authHelper.ValidateHeader(req.Header["Authorization"], req.Method, req.URL) + err := c.authValidator.ValidateHeader(req.Header["Authorization"], req.Method, req.URL) if err != nil { c.authFailures++ @@ -919,7 +919,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{ return errAuthCritical{&base.Response{ StatusCode: base.StatusUnauthorized, Header: base.Header{ - "WWW-Authenticate": c.authHelper.GenerateHeader(), + "WWW-Authenticate": c.authValidator.GenerateHeader(), }, }} } @@ -931,7 +931,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{ return errAuthNotCritical{&base.Response{ StatusCode: base.StatusUnauthorized, Header: base.Header{ - "WWW-Authenticate": c.authHelper.GenerateHeader(), + "WWW-Authenticate": c.authValidator.GenerateHeader(), }, }} } diff --git a/internal/conf/pathconf.go b/internal/conf/pathconf.go index 482d3232..8202ce99 100644 --- a/internal/conf/pathconf.go +++ b/internal/conf/pathconf.go @@ -165,17 +165,15 @@ func (pconf *PathConf) fillAndCheck(name string) error { } if pconf.PublishUser != "" { - if !reUserPass.MatchString(pconf.PublishUser) { + if !strings.HasPrefix(pconf.PublishUser, "sha256:") && !reUserPass.MatchString(pconf.PublishUser) { return fmt.Errorf("publish username contains unsupported characters (supported are %s)", userPassSupportedChars) } } - if pconf.PublishPass != "" { - if !reUserPass.MatchString(pconf.PublishPass) { + if !strings.HasPrefix(pconf.PublishPass, "sha256:") && !reUserPass.MatchString(pconf.PublishPass) { return fmt.Errorf("publish password contains unsupported characters (supported are %s)", userPassSupportedChars) } } - if len(pconf.PublishIps) > 0 { var err error pconf.PublishIpsParsed, err = parseIPCidrList(pconf.PublishIps) @@ -191,19 +189,18 @@ func (pconf *PathConf) fillAndCheck(name string) error { return fmt.Errorf("read username and password must be both filled") } if pconf.ReadUser != "" { - if !reUserPass.MatchString(pconf.ReadUser) { + if !strings.HasPrefix(pconf.ReadUser, "sha256:") && !reUserPass.MatchString(pconf.ReadUser) { return fmt.Errorf("read username contains unsupported characters (supported are %s)", userPassSupportedChars) } } if pconf.ReadPass != "" { - if !reUserPass.MatchString(pconf.ReadPass) { + if !strings.HasPrefix(pconf.ReadPass, "sha256:") && !reUserPass.MatchString(pconf.ReadPass) { return fmt.Errorf("read password contains unsupported characters (supported are %s)", userPassSupportedChars) } } if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" { return fmt.Errorf("read username and password must be both filled") } - if len(pconf.ReadIps) > 0 { var err error pconf.ReadIpsParsed, err = parseIPCidrList(pconf.ReadIps) diff --git a/main_test.go b/main_test.go index 39afb54c..4ab8fdf5 100644 --- a/main_test.go +++ b/main_test.go @@ -619,6 +619,41 @@ func TestAuth(t *testing.T) { } } +func TestAuthHashed(t *testing.T) { + p, ok := testProgram("paths:\n" + + " all:\n" + + " readUser: sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=\n" + + " readPass: sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w=\n") + require.Equal(t, true, ok) + defer p.close() + + time.Sleep(1 * time.Second) + + cnt1, err := newContainer("ffmpeg", "source", []string{ + "-re", + "-stream_loop", "-1", + "-i", "emptyvideo.ts", + "-c", "copy", + "-f", "rtsp", + "-rtsp_transport", "udp", + "rtsp://" + ownDockerIP + ":8554/test/stream", + }) + require.NoError(t, err) + defer cnt1.close() + + cnt2, err := newContainer("ffmpeg", "dest", []string{ + "-rtsp_transport", "udp", + "-i", "rtsp://testuser:testpass@" + ownDockerIP + ":8554/test/stream", + "-vframes", "1", + "-f", "image2", + "-y", "/dev/null", + }) + require.NoError(t, err) + defer cnt2.close() + + require.Equal(t, 0, cnt2.wait()) +} + func TestAuthIpFail(t *testing.T) { p, ok := testProgram("paths:\n" + " all:\n" + diff --git a/rtsp-simple-server.yml b/rtsp-simple-server.yml index 9694d046..b7f6a81a 100644 --- a/rtsp-simple-server.yml +++ b/rtsp-simple-server.yml @@ -85,15 +85,19 @@ paths: fallback: # username required to publish. + # sha256-hashed values can be inserted with the "sha256:" prefix. publishUser: # password required to publish. + # sha256-hashed values can be inserted with the "sha256:" prefix. publishPass: # ips or networks (x.x.x.x/24) allowed to publish. publishIps: [] # username required to read. + # sha256-hashed values can be inserted with the "sha256:" prefix. readUser: # password required to read. + # sha256-hashed values can be inserted with the "sha256:" prefix. readPass: # ips or networks (x.x.x.x/24) allowed to read. readIps: []