mirror of
https://github.com/bluenviron/mediamtx
synced 2025-03-30 15:18:01 +00:00
support hashed credentials
This commit is contained in:
parent
36caa51081
commit
3050893d32
79
README.md
79
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.
|
||||
|
2
go.mod
2
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
|
||||
|
4
go.sum
4
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=
|
||||
|
@ -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(),
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
@ -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)
|
||||
|
35
main_test.go
35
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" +
|
||||
|
@ -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: []
|
||||
|
Loading…
Reference in New Issue
Block a user