Fix basic auth, disable localhost, replace template func

This commit is contained in:
Ingo Oppermann
2023-02-17 17:27:39 +01:00
parent 2df83c8032
commit 8215c20ae6
6 changed files with 118 additions and 52 deletions

View File

@@ -459,11 +459,11 @@ func (a *api) start() error {
iam.AddPolicy("$anon", "$none", "process:*", "ANY") iam.AddPolicy("$anon", "$none", "process:*", "ANY")
iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY") iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY")
iam.AddPolicy("$localhost", "$none", "process:*", "ANY") iam.AddPolicy("$localhost", "$none", "process:*", "ANY")
} } else {
if cfg.API.Auth.DisableLocalhost {
if cfg.API.Auth.DisableLocalhost { iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY")
iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY") iam.AddPolicy("$localhost", "$none", "process:*", "ANY")
iam.AddPolicy("$localhost", "$none", "process:*", "ANY") }
} }
if !cfg.Storage.Memory.Auth.Enable { if !cfg.Storage.Memory.Auth.Enable {
@@ -471,11 +471,11 @@ func (a *api) start() error {
} }
if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 { if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 {
iam.AddPolicy("$anon", "$none", "rtmp:/**", "PUBLISH|PLAY") iam.AddPolicy("$anon", "$none", "rtmp:/**", "ANY")
} }
if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 { if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 {
iam.AddPolicy("$anon", "$none", "srt:**", "PUBLISH|PLAY") iam.AddPolicy("$anon", "$none", "srt:**", "ANY")
} }
a.iam = iam a.iam = iam
@@ -669,7 +669,15 @@ func (a *api) start() error {
} }
template += "/{name}" template += "/{name}"
if identity, _ := a.iam.GetIdentity(config.Owner); identity != nil { var identity iam.IdentityVerifier = nil
if len(config.Owner) == 0 {
identity, _ = a.iam.GetDefaultIdentity()
} else {
identity, _ = a.iam.GetIdentity(config.Owner)
}
if identity != nil {
template += "/" + identity.GetServiceToken() template += "/" + identity.GetServiceToken()
} }
@@ -687,7 +695,15 @@ func (a *api) start() error {
template += ",mode:publish" template += ",mode:publish"
} }
if identity, _ := a.iam.GetIdentity(config.Owner); identity != nil { var identity iam.IdentityVerifier = nil
if len(config.Owner) == 0 {
identity, _ = a.iam.GetDefaultIdentity()
} else {
identity, _ = a.iam.GetIdentity(config.Owner)
}
if identity != nil {
template += ",token:" + identity.GetServiceToken() template += ",token:" + identity.GetServiceToken()
} }
@@ -1102,13 +1118,14 @@ func (a *api) start() error {
Cors: http.CorsConfig{ Cors: http.CorsConfig{
Origins: cfg.Storage.CORS.Origins, Origins: cfg.Storage.CORS.Origins,
}, },
RTMP: a.rtmpserver, RTMP: a.rtmpserver,
SRT: a.srtserver, SRT: a.srtserver,
Config: a.config.store, Config: a.config.store,
Sessions: a.sessions, Sessions: a.sessions,
Router: router, Router: router,
ReadOnly: cfg.API.ReadOnly, ReadOnly: cfg.API.ReadOnly,
IAM: a.iam, IAM: a.iam,
IAMDisableLocalhost: cfg.API.Auth.DisableLocalhost,
} }
mainserverhandler, err := http.NewServer(serverConfig) mainserverhandler, err := http.NewServer(serverConfig)

View File

@@ -15,11 +15,11 @@ import (
// about the API version and build infos. // about the API version and build infos.
type AboutHandler struct { type AboutHandler struct {
restream restream.Restreamer restream restream.Restreamer
auths []string auths func() []string
} }
// NewAbout returns a new About type // NewAbout returns a new About type
func NewAbout(restream restream.Restreamer, auths []string) *AboutHandler { func NewAbout(restream restream.Restreamer, auths func() []string) *AboutHandler {
return &AboutHandler{ return &AboutHandler{
restream: restream, restream: restream,
auths: auths, auths: auths,
@@ -36,11 +36,12 @@ func NewAbout(restream restream.Restreamer, auths []string) *AboutHandler {
// @Router /api [get] // @Router /api [get]
func (p *AboutHandler) About(c echo.Context) error { func (p *AboutHandler) About(c echo.Context) error {
user, _ := c.Get("user").(string) user, _ := c.Get("user").(string)
disablelocalhost, _ := c.Get("disablelocalhost").(bool)
if user == "$anon" { if user == "$anon" || (user == "$localhost" && !disablelocalhost) {
return c.JSON(http.StatusOK, api.MinimalAbout{ return c.JSON(http.StatusOK, api.MinimalAbout{
App: app.Name, App: app.Name,
Auths: p.auths, Auths: p.auths(),
Version: api.VersionMinimal{ Version: api.VersionMinimal{
Number: app.Version.MajorString(), Number: app.Version.MajorString(),
}, },
@@ -52,7 +53,7 @@ func (p *AboutHandler) About(c echo.Context) error {
about := api.About{ about := api.About{
App: app.Name, App: app.Name,
Name: p.restream.Name(), Name: p.restream.Name(),
Auths: p.auths, Auths: p.auths(),
ID: p.restream.ID(), ID: p.restream.ID(),
CreatedAt: createdAt.Format(time.RFC3339), CreatedAt: createdAt.Format(time.RFC3339),
Uptime: uint64(time.Since(createdAt).Seconds()), Uptime: uint64(time.Since(createdAt).Seconds()),

View File

@@ -19,7 +19,7 @@ func getDummyAboutRouter() (*echo.Echo, error) {
return nil, err return nil, err
} }
handler := NewAbout(rs, []string{}) handler := NewAbout(rs, func() []string { return []string{} })
router.Add("GET", "/", handler.About) router.Add("GET", "/", handler.About)

View File

@@ -33,6 +33,7 @@ package iam
import ( import (
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"path/filepath" "path/filepath"
@@ -52,17 +53,19 @@ import (
type Config struct { type Config struct {
// Skipper defines a function to skip middleware. // Skipper defines a function to skip middleware.
Skipper middleware.Skipper Skipper middleware.Skipper
Mounts []string Mounts []string
IAM iam.IAM IAM iam.IAM
Logger log.Logger DisableLocalhost bool
Logger log.Logger
} }
var DefaultConfig = Config{ var DefaultConfig = Config{
Skipper: middleware.DefaultSkipper, Skipper: middleware.DefaultSkipper,
Mounts: []string{}, Mounts: []string{},
IAM: nil, IAM: nil,
Logger: nil, DisableLocalhost: false,
Logger: nil,
} }
type iammiddleware struct { type iammiddleware struct {
@@ -95,6 +98,9 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
c.Set("disablelocalhost", config.DisableLocalhost)
if config.Skipper(c) { if config.Skipper(c) {
c.Set("user", "$anon") c.Set("user", "$anon")
return next(c) return next(c)
@@ -159,6 +165,11 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
} else { } else {
identity, err = mw.findIdentityFromBasicAuth(c) identity, err = mw.findIdentityFromBasicAuth(c)
if err != nil { if err != nil {
if err == ErrUnauthorized {
c.Response().Header().Set(echo.HeaderWWWAuthenticate, "Basic realm=datarhei-core")
return api.Err(http.StatusUnauthorized, "Unauthorized", "%s", err)
}
return api.Err(http.StatusForbidden, "Forbidden", "%s", err) return api.Err(http.StatusForbidden, "Forbidden", "%s", err)
} }
@@ -187,12 +198,30 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
} }
} }
var ErrUnauthorized = errors.New("unauthorized")
func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityVerifier, error) { func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityVerifier, error) {
basic := "basic" basic := "basic"
auth := c.Request().Header.Get(echo.HeaderAuthorization) auth := c.Request().Header.Get(echo.HeaderAuthorization)
l := len(basic) l := len(basic)
if len(auth) == 0 { if len(auth) == 0 {
method := c.Request().Method
if method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions {
return nil, nil
}
path := c.Request().URL.Path
for _, m := range m.mounts {
if m == "/" {
continue
}
if strings.HasPrefix(path, m+"/") {
return nil, ErrUnauthorized
}
}
return nil, nil return nil, nil
} }

View File

@@ -75,24 +75,25 @@ import (
var ListenAndServe = http.ListenAndServe var ListenAndServe = http.ListenAndServe
type Config struct { type Config struct {
Logger log.Logger Logger log.Logger
LogBuffer log.BufferWriter LogBuffer log.BufferWriter
Restream restream.Restreamer Restream restream.Restreamer
Metrics monitor.HistoryReader Metrics monitor.HistoryReader
Prometheus prometheus.Reader Prometheus prometheus.Reader
MimeTypesFile string MimeTypesFile string
Filesystems []fs.FS Filesystems []fs.FS
IPLimiter net.IPLimiter IPLimiter net.IPLimiter
Profiling bool Profiling bool
Cors CorsConfig Cors CorsConfig
RTMP rtmp.Server RTMP rtmp.Server
SRT srt.Server SRT srt.Server
Config cfgstore.Store Config cfgstore.Store
Cache cache.Cacher Cache cache.Cacher
Sessions session.RegistryReader Sessions session.RegistryReader
Router router.Router Router router.Router
ReadOnly bool ReadOnly bool
IAM iam.IAM IAM iam.IAM
IAMDisableLocalhost bool
} }
type CorsConfig struct { type CorsConfig struct {
@@ -221,14 +222,15 @@ func NewServer(config Config) (Server, error) {
} }
s.middleware.iam = mwiam.NewWithConfig(mwiam.Config{ s.middleware.iam = mwiam.NewWithConfig(mwiam.Config{
IAM: config.IAM, IAM: config.IAM,
Mounts: mounts, Mounts: mounts,
Logger: s.logger.WithComponent("IAM"), DisableLocalhost: config.IAMDisableLocalhost,
Logger: s.logger.WithComponent("IAM"),
}) })
s.handler.about = api.NewAbout( s.handler.about = api.NewAbout(
config.Restream, config.Restream,
config.IAM.Validators(), func() []string { return config.IAM.Validators() },
) )
s.handler.jwt = api.NewJWT(config.IAM) s.handler.jwt = api.NewJWT(config.IAM)

View File

@@ -71,6 +71,23 @@ func TestIdentity(t *testing.T) {
require.False(t, identity.IsSuperuser()) require.False(t, identity.IsSuperuser())
identity.user.Superuser = true identity.user.Superuser = true
require.True(t, identity.IsSuperuser()) require.True(t, identity.IsSuperuser())
dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{})
require.NoError(t, err)
im, err := NewIdentityManager(IdentityConfig{
FS: dummyfs,
Superuser: User{Name: "foobar"},
JWTRealm: "test-realm",
JWTSecret: "abc123",
Logger: nil,
})
require.NoError(t, err)
require.NotNil(t, im)
id, err := im.GetVerifier("unknown")
require.Error(t, err)
require.Nil(t, id)
} }
func TestDefaultIdentity(t *testing.T) { func TestDefaultIdentity(t *testing.T) {