1
0
Fork 0
forked from External/mediamtx

webrtc: support passing username and password through Bearer Token (#3248) (#3459)

This commit is contained in:
Alessandro Ros 2024-06-11 23:37:59 +02:00 committed by GitHub
parent caa9fa6ca0
commit 39ae1269ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 134 additions and 7 deletions

View file

@ -134,7 +134,8 @@ _rtsp-simple-server_ has been rebranded as _MediaMTX_. The reason is pretty obvi
* [SRT-specific features](#srt-specific-features)
* [Standard stream ID syntax](#standard-stream-id-syntax)
* [WebRTC-specific features](#webrtc-specific-features)
* [Connectivity issues](#connectivity-issues)
* [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep)
* [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues)
* [RTSP-specific features](#rtsp-specific-features)
* [Transport protocols](#transport-protocols)
* [Encryption](#encryption)
@ -338,6 +339,7 @@ Latest versions of OBS Studio can publish to the server with the [WebRTC / WHIP
* Service: `WHIP`
* Server: `http://localhost:8889/mystream/whip`
* Bearer Token: `myuser:mypass` (if internal authentication is enabled) or JWT (if JWT-based authentication is enabled)
Save the configuration and click `Start streaming`.
@ -610,7 +612,9 @@ WHIP is a WebRTC extensions that allows to publish streams by using a URL, witho
http://localhost:8889/mystream/whip
```
Depending on the network it may be difficult to establish a connection between server and clients, see [WebRTC-specific features](#webrtc-specific-features) for remediations.
Regarding authentication, read [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep).
Depending on the network it may be difficult to establish a connection between server and clients, read [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues).
Known clients that can publish with WebRTC and WHIP are [FFmpeg](#ffmpeg), [GStreamer](#gstreamer), [OBS Studio](#obs-studio).
@ -876,7 +880,9 @@ WHEP is a WebRTC extensions that allows to read streams by using a URL, without
http://localhost:8889/mystream/whep
```
Depending on the network it may be difficult to establish a connection between server and clients, see [WebRTC-specific features](#webrtc-specific-features) for remediations.
Regarding authentication, read [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep).
Depending on the network it may be difficult to establish a connection between server and clients, read [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues).
Known clients that can read with WebRTC and WHEP are [FFmpeg](#ffmpeg-1), [GStreamer](#gstreamer-1) and [web browsers](#web-browsers-1).
@ -1838,7 +1844,35 @@ Where:
### WebRTC-specific features
#### Connectivity issues
#### Authenticating with WHIP/WHEP
When using WHIP or WHEP to establish a WebRTC connection, there are multiple ways to provide credentials.
If internal authentication or HTTP-based authentication is enabled, username and password can be passed through the `Authentication: Basic` header:
```
Authentication: Basic [base64_encoded_credentials]
```
Username and password can be also passed through the `Authentication: Bearer` header (since it's mandated by the specification):
```
Authentication: Bearer username:password
```
If JWT-based authentication is enabled, JWT can be passed through the `Authentication: Bearer` header:
```
Authentication: Bearer [jwt]
```
The JWT can also be passed through query parameters:
```
http://localhost:8889/mystream/whip?jwt=[jwt]
```
#### Solving WebRTC connectivity issues
If the server is hosted inside a container or is behind a NAT, additional configuration is required in order to allow the two WebRTC parts (server and client) to establish a connection.

View file

@ -121,10 +121,17 @@ func (s *httpServer) close() {
func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool {
user, pass, hasCredentials := ctx.Request.BasicAuth()
q := ctx.Request.URL.RawQuery
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
// JWT in authorization bearer -> JWT in query parameters
q = addJWTFromAuthorization(q, h)
// credentials in authorization bearer -> credentials in authorization basic
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
user = parts[0]
pass = parts[1]
}
}
_, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
@ -194,10 +201,17 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool)
}
user, pass, _ := ctx.Request.BasicAuth()
q := ctx.Request.URL.RawQuery
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
// JWT in authorization bearer -> JWT in query parameters
q = addJWTFromAuthorization(q, h)
// credentials in authorization bearer -> credentials in authorization basic
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
user = parts[0]
pass = parts[1]
}
}
res := s.parent.newSession(webRTCNewSessionReq{

View file

@ -603,7 +603,7 @@ func TestServerRead(t *testing.T) {
}
}
func TestServerReadAuthorizationHeader(t *testing.T) {
func TestServerReadAuthorizationBearerJWT(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
str, err := stream.New(
@ -680,6 +680,85 @@ func TestServerReadAuthorizationHeader(t *testing.T) {
require.Equal(t, http.StatusCreated, res.StatusCode)
}
func TestServerReadAuthorizationUserPass(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
str, err := stream.New(
1460,
desc,
true,
test.NilLogger,
)
require.NoError(t, err)
path := &dummyPath{stream: str}
pm := &dummyPathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return &conf.Path{}, nil
},
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, str, nil
},
}
s := &Server{
Address: "127.0.0.1:8886",
Encryption: false,
ServerKey: "",
ServerCert: "",
AllowOrigin: "",
TrustedProxies: conf.IPNetworks{},
ReadTimeout: conf.StringDuration(10 * time.Second),
WriteQueueSize: 512,
LocalUDPAddress: "127.0.0.1:8887",
LocalTCPAddress: "127.0.0.1:8887",
IPsFromInterfaces: true,
IPsFromInterfacesList: []string{},
AdditionalHosts: []string{},
ICEServers: []conf.WebRTCICEServer{},
HandshakeTimeout: conf.StringDuration(10 * time.Second),
TrackGatherTimeout: conf.StringDuration(2 * time.Second),
ExternalCmdPool: nil,
PathManager: pm,
Parent: test.NilLogger,
}
err = s.Initialize()
require.NoError(t, err)
defer s.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{})
require.NoError(t, err)
defer pc.Close() //nolint:errcheck
_, err = pc.AddTransceiverFromKind(pwebrtc.RTPCodecTypeVideo)
require.NoError(t, err)
offer, err := pc.CreateOffer(nil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodPost,
"http://localhost:8886/teststream/whep", bytes.NewReader([]byte(offer.SDP)))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/sdp")
req.Header.Set("Authorization", "Bearer myuser:mypass")
res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusCreated, res.StatusCode)
}
func TestServerReadNotFound(t *testing.T) {
pm := &dummyPathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) {