From 8215c20ae6ad4a6f4f3f74a4e7f50142b7e22cf8 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 17 Feb 2023 17:27:39 +0100 Subject: [PATCH] Fix basic auth, disable localhost, replace template func --- app/api/api.go | 49 +++++++++++++++++++++++----------- http/handler/api/about.go | 11 ++++---- http/handler/api/about_test.go | 2 +- http/middleware/iam/iam.go | 45 +++++++++++++++++++++++++------ http/server.go | 46 ++++++++++++++++--------------- iam/identity_test.go | 17 ++++++++++++ 6 files changed, 118 insertions(+), 52 deletions(-) diff --git a/app/api/api.go b/app/api/api.go index a14bfbd0..86570d63 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -459,11 +459,11 @@ func (a *api) start() error { iam.AddPolicy("$anon", "$none", "process:*", "ANY") iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY") iam.AddPolicy("$localhost", "$none", "process:*", "ANY") - } - - if cfg.API.Auth.DisableLocalhost { - iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY") - iam.AddPolicy("$localhost", "$none", "process:*", "ANY") + } else { + if cfg.API.Auth.DisableLocalhost { + iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY") + iam.AddPolicy("$localhost", "$none", "process:*", "ANY") + } } if !cfg.Storage.Memory.Auth.Enable { @@ -471,11 +471,11 @@ func (a *api) start() error { } 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 { - iam.AddPolicy("$anon", "$none", "srt:**", "PUBLISH|PLAY") + iam.AddPolicy("$anon", "$none", "srt:**", "ANY") } a.iam = iam @@ -669,7 +669,15 @@ func (a *api) start() error { } 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() } @@ -687,7 +695,15 @@ func (a *api) start() error { 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() } @@ -1102,13 +1118,14 @@ func (a *api) start() error { Cors: http.CorsConfig{ Origins: cfg.Storage.CORS.Origins, }, - RTMP: a.rtmpserver, - SRT: a.srtserver, - Config: a.config.store, - Sessions: a.sessions, - Router: router, - ReadOnly: cfg.API.ReadOnly, - IAM: a.iam, + RTMP: a.rtmpserver, + SRT: a.srtserver, + Config: a.config.store, + Sessions: a.sessions, + Router: router, + ReadOnly: cfg.API.ReadOnly, + IAM: a.iam, + IAMDisableLocalhost: cfg.API.Auth.DisableLocalhost, } mainserverhandler, err := http.NewServer(serverConfig) diff --git a/http/handler/api/about.go b/http/handler/api/about.go index 6a9114d4..b80fa8cd 100644 --- a/http/handler/api/about.go +++ b/http/handler/api/about.go @@ -15,11 +15,11 @@ import ( // about the API version and build infos. type AboutHandler struct { restream restream.Restreamer - auths []string + auths func() []string } // 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{ restream: restream, auths: auths, @@ -36,11 +36,12 @@ func NewAbout(restream restream.Restreamer, auths []string) *AboutHandler { // @Router /api [get] func (p *AboutHandler) About(c echo.Context) error { 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{ App: app.Name, - Auths: p.auths, + Auths: p.auths(), Version: api.VersionMinimal{ Number: app.Version.MajorString(), }, @@ -52,7 +53,7 @@ func (p *AboutHandler) About(c echo.Context) error { about := api.About{ App: app.Name, Name: p.restream.Name(), - Auths: p.auths, + Auths: p.auths(), ID: p.restream.ID(), CreatedAt: createdAt.Format(time.RFC3339), Uptime: uint64(time.Since(createdAt).Seconds()), diff --git a/http/handler/api/about_test.go b/http/handler/api/about_test.go index dfdb4673..a2f039b5 100644 --- a/http/handler/api/about_test.go +++ b/http/handler/api/about_test.go @@ -19,7 +19,7 @@ func getDummyAboutRouter() (*echo.Echo, error) { return nil, err } - handler := NewAbout(rs, []string{}) + handler := NewAbout(rs, func() []string { return []string{} }) router.Add("GET", "/", handler.About) diff --git a/http/middleware/iam/iam.go b/http/middleware/iam/iam.go index b2f785fa..8bbaeb41 100644 --- a/http/middleware/iam/iam.go +++ b/http/middleware/iam/iam.go @@ -33,6 +33,7 @@ package iam import ( "encoding/base64" + "errors" "fmt" "net/http" "path/filepath" @@ -52,17 +53,19 @@ import ( type Config struct { // Skipper defines a function to skip middleware. - Skipper middleware.Skipper - Mounts []string - IAM iam.IAM - Logger log.Logger + Skipper middleware.Skipper + Mounts []string + IAM iam.IAM + DisableLocalhost bool + Logger log.Logger } var DefaultConfig = Config{ - Skipper: middleware.DefaultSkipper, - Mounts: []string{}, - IAM: nil, - Logger: nil, + Skipper: middleware.DefaultSkipper, + Mounts: []string{}, + IAM: nil, + DisableLocalhost: false, + Logger: nil, } type iammiddleware struct { @@ -95,6 +98,9 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { + + c.Set("disablelocalhost", config.DisableLocalhost) + if config.Skipper(c) { c.Set("user", "$anon") return next(c) @@ -159,6 +165,11 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } else { identity, err = mw.findIdentityFromBasicAuth(c) 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) } @@ -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) { basic := "basic" auth := c.Request().Header.Get(echo.HeaderAuthorization) l := len(basic) 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 } diff --git a/http/server.go b/http/server.go index cbc7f69f..09855c75 100644 --- a/http/server.go +++ b/http/server.go @@ -75,24 +75,25 @@ import ( var ListenAndServe = http.ListenAndServe type Config struct { - Logger log.Logger - LogBuffer log.BufferWriter - Restream restream.Restreamer - Metrics monitor.HistoryReader - Prometheus prometheus.Reader - MimeTypesFile string - Filesystems []fs.FS - IPLimiter net.IPLimiter - Profiling bool - Cors CorsConfig - RTMP rtmp.Server - SRT srt.Server - Config cfgstore.Store - Cache cache.Cacher - Sessions session.RegistryReader - Router router.Router - ReadOnly bool - IAM iam.IAM + Logger log.Logger + LogBuffer log.BufferWriter + Restream restream.Restreamer + Metrics monitor.HistoryReader + Prometheus prometheus.Reader + MimeTypesFile string + Filesystems []fs.FS + IPLimiter net.IPLimiter + Profiling bool + Cors CorsConfig + RTMP rtmp.Server + SRT srt.Server + Config cfgstore.Store + Cache cache.Cacher + Sessions session.RegistryReader + Router router.Router + ReadOnly bool + IAM iam.IAM + IAMDisableLocalhost bool } type CorsConfig struct { @@ -221,14 +222,15 @@ func NewServer(config Config) (Server, error) { } s.middleware.iam = mwiam.NewWithConfig(mwiam.Config{ - IAM: config.IAM, - Mounts: mounts, - Logger: s.logger.WithComponent("IAM"), + IAM: config.IAM, + Mounts: mounts, + DisableLocalhost: config.IAMDisableLocalhost, + Logger: s.logger.WithComponent("IAM"), }) s.handler.about = api.NewAbout( config.Restream, - config.IAM.Validators(), + func() []string { return config.IAM.Validators() }, ) s.handler.jwt = api.NewJWT(config.IAM) diff --git a/iam/identity_test.go b/iam/identity_test.go index 7e032fe9..69d7a1b9 100644 --- a/iam/identity_test.go +++ b/iam/identity_test.go @@ -71,6 +71,23 @@ func TestIdentity(t *testing.T) { require.False(t, identity.IsSuperuser()) identity.user.Superuser = true 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) {