feat(mediaserver): authenticate internal clients

This commit is contained in:
Rob Watson 2025-03-26 21:12:08 +01:00
parent 3866d9dd07
commit bcaf9f1cae
3 changed files with 40 additions and 7 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"cmp" "cmp"
"context" "context"
"crypto/rand"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
@ -50,6 +51,7 @@ type Actor struct {
rtmpPort int rtmpPort int
streamKey StreamKey streamKey StreamKey
fetchIngressStateInterval time.Duration fetchIngressStateInterval time.Duration
pass string // password for the media server
logger *slog.Logger logger *slog.Logger
httpClient *http.Client httpClient *http.Client
@ -83,6 +85,7 @@ func StartActor(ctx context.Context, params StartActorParams) *Actor {
rtmpPort: cmp.Or(params.RTMPPort, defaultRTMPPort), rtmpPort: cmp.Or(params.RTMPPort, defaultRTMPPort),
streamKey: cmp.Or(params.StreamKey, defaultStreamKey), streamKey: cmp.Or(params.StreamKey, defaultStreamKey),
fetchIngressStateInterval: cmp.Or(params.FetchIngressStateInterval, defaultFetchIngressStateInterval), fetchIngressStateInterval: cmp.Or(params.FetchIngressStateInterval, defaultFetchIngressStateInterval),
pass: generatePassword(),
actorC: make(chan action, chanSize), actorC: make(chan action, chanSize),
state: new(domain.Source), state: new(domain.Source),
stateC: make(chan domain.Source, chanSize), stateC: make(chan domain.Source, chanSize),
@ -101,18 +104,26 @@ func StartActor(ctx context.Context, params StartActorParams) *Actor {
LogDestinations: []string{"stdout"}, LogDestinations: []string{"stdout"},
AuthMethod: "internal", AuthMethod: "internal",
AuthInternalUsers: []User{ AuthInternalUsers: []User{
// TODO: tighten permissions // TODO: TLS
{ {
User: "any", User: "any",
IPs: []string{}, // any IP IPs: []string{}, // any IP
Permissions: []UserPermission{ Permissions: []UserPermission{
{Action: "publish"}, {Action: "publish"},
},
},
{
User: "api",
Pass: actor.pass,
IPs: []string{}, // any IP
Permissions: []UserPermission{
{Action: "read"}, {Action: "read"},
}, },
}, },
{ {
User: "any", User: "api",
IPs: []string{"127.0.0.1", "::1", "172.17.0.0/16"}, Pass: actor.pass,
IPs: []string{}, // any IP
Permissions: []UserPermission{{Action: "api"}}, Permissions: []UserPermission{{Action: "api"}},
}, },
}, },
@ -144,7 +155,7 @@ func StartActor(ctx context.Context, params StartActorParams) *Actor {
container.LabelComponent: componentName, container.LabelComponent: componentName,
}, },
Healthcheck: &typescontainer.HealthConfig{ Healthcheck: &typescontainer.HealthConfig{
Test: []string{"CMD", "curl", "-f", "http://localhost:9997/v3/paths/list"}, Test: []string{"CMD", "curl", "-f", actor.pathsURL()},
Interval: time.Second * 10, Interval: time.Second * 10,
StartPeriod: time.Second * 2, StartPeriod: time.Second * 2,
StartInterval: time.Second * 2, StartInterval: time.Second * 2,
@ -317,18 +328,18 @@ func (s *Actor) rtmpURL() string {
// the app network. // the app network.
func (s *Actor) rtmpInternalURL() string { func (s *Actor) rtmpInternalURL() string {
// Container port, not host port: // Container port, not host port:
return fmt.Sprintf("rtmp://mediaserver:1935/%s", s.streamKey) return fmt.Sprintf("rtmp://mediaserver:1935/%s?user=api&pass=%s", s.streamKey, s.pass)
} }
// rtmpConnsURL returns the URL for fetching RTMP connections, accessible from // rtmpConnsURL returns the URL for fetching RTMP connections, accessible from
// the host. // the host.
func (s *Actor) rtmpConnsURL() string { func (s *Actor) rtmpConnsURL() string {
return fmt.Sprintf("http://localhost:%d/v3/rtmpconns/list", s.apiPort) return fmt.Sprintf("http://api:%s@localhost:%d/v3/rtmpconns/list", s.pass, s.apiPort)
} }
// pathsURL returns the URL for fetching paths, accessible from the host. // pathsURL returns the URL for fetching paths, accessible from the host.
func (s *Actor) pathsURL() string { func (s *Actor) pathsURL() string {
return fmt.Sprintf("http://localhost:%d/v3/paths/list", s.apiPort) return fmt.Sprintf("http://api:%s@localhost:%d/v3/paths/list", s.pass, s.apiPort)
} }
// shortID returns the first 12 characters of the given container ID. // shortID returns the first 12 characters of the given container ID.
@ -338,3 +349,12 @@ func shortID(id string) string {
} }
return id[:12] return id[:12]
} }
// generatePassword securely generates a random password suitable for
// authenticating media server endpoints.
func generatePassword() string {
const lenBytes = 32
p := make([]byte, lenBytes)
_, _ = rand.Read(p)
return fmt.Sprintf("%x", []byte(p))
}

View File

@ -0,0 +1,12 @@
package mediaserver
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGeneratePassword(t *testing.T) {
assert.Len(t, generatePassword(), 64)
assert.NotEqual(t, generatePassword(), generatePassword())
}

View File

@ -36,6 +36,7 @@ type UserPermission struct {
// User represents a user configuration in MediaMTX. // User represents a user configuration in MediaMTX.
type User struct { type User struct {
User string `yaml:"user,omitempty"` User string `yaml:"user,omitempty"`
Pass string `yaml:"pass,omitempty"`
IPs []string `yaml:"ips,omitempty"` IPs []string `yaml:"ips,omitempty"`
Permissions []UserPermission `yaml:"permissions,omitempty"` Permissions []UserPermission `yaml:"permissions,omitempty"`
} }