Change anon user to localhost user only if DisableLocalhost is set

This commit is contained in:
Ingo Oppermann
2023-03-08 15:21:55 +01:00
parent 41eab6f40a
commit d101a76e9e
5 changed files with 63 additions and 44 deletions

View File

@@ -441,8 +441,7 @@ func (a *api) start() error {
return fmt.Errorf("iam: %w", err) return fmt.Errorf("iam: %w", err)
} }
// Create default policies for anonymous users in order to mimic // Create default policies for anonymous users in order to mimic the behaviour before IAM
// the behaviour before IAM
iam.RemovePolicy("$anon", "$none", "", nil) iam.RemovePolicy("$anon", "$none", "", nil)
iam.RemovePolicy("$localhost", "$none", "", nil) iam.RemovePolicy("$localhost", "$none", "", nil)
@@ -451,16 +450,14 @@ func (a *api) start() error {
iam.AddPolicy("$anon", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"}) iam.AddPolicy("$anon", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$anon", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"}) iam.AddPolicy("$anon", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$localhost", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$localhost", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
if !cfg.API.Auth.Enable { if !cfg.API.Auth.Enable {
iam.AddPolicy("$anon", "$none", "api:/api/**", []string{"ANY"}) iam.AddPolicy("$anon", "$none", "api:/api/**", []string{"ANY"})
iam.AddPolicy("$anon", "$none", "process:*", []string{"ANY"}) iam.AddPolicy("$anon", "$none", "process:*", []string{"ANY"})
iam.AddPolicy("$localhost", "$none", "api:/api/**", []string{"ANY"})
iam.AddPolicy("$localhost", "$none", "process:*", []string{"ANY"})
} else { } else {
if cfg.API.Auth.DisableLocalhost { if cfg.API.Auth.DisableLocalhost {
iam.AddPolicy("$localhost", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$localhost", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$localhost", "$none", "api:/api/**", []string{"ANY"}) iam.AddPolicy("$localhost", "$none", "api:/api/**", []string{"ANY"})
iam.AddPolicy("$localhost", "$none", "process:*", []string{"ANY"}) iam.AddPolicy("$localhost", "$none", "process:*", []string{"ANY"})
} }
@@ -1125,7 +1122,7 @@ func (a *api) start() error {
Router: router, Router: router,
ReadOnly: cfg.API.ReadOnly, ReadOnly: cfg.API.ReadOnly,
IAM: a.iam, IAM: a.iam,
IAMDisableLocalhost: cfg.API.Auth.DisableLocalhost, IAMDisableLocalhost: cfg.API.Auth.Enable && cfg.API.Auth.DisableLocalhost,
} }
mainserverhandler, err := http.NewServer(serverConfig) mainserverhandler, err := http.NewServer(serverConfig)

View File

@@ -36,9 +36,8 @@ func NewAbout(restream restream.Restreamer, auths func() []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" || (user == "$localhost" && !disablelocalhost) { if user == "$anon" {
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(),

View File

@@ -17,8 +17,9 @@
// only allow JWT as authentication method. // only allow JWT as authentication method.
// //
// If the identity can't be detected, the identity of "$anon" is given, representing // If the identity can't be detected, the identity of "$anon" is given, representing
// an anonmyous user. If the request originates from localhost, the identity will // an anonmyous user. If the request originates from localhost and DisableLocalhost
// be $localhost, representing an anonymous user from localhost. // is configured, the identity will be $localhost, representing an anonymous user from
// localhost.
// //
// The domain is provided as query parameter "group" for all API requests or the // The domain is provided as query parameter "group" for all API requests or the
// first path element after a mountpoint for filesystem requests. // first path element after a mountpoint for filesystem requests.
@@ -57,6 +58,7 @@ type Config struct {
Mounts []string Mounts []string
IAM iam.IAM IAM iam.IAM
DisableLocalhost bool DisableLocalhost bool
WaitAfterFailedLogin bool
Logger log.Logger Logger log.Logger
} }
@@ -65,9 +67,12 @@ var DefaultConfig = Config{
Mounts: []string{}, Mounts: []string{},
IAM: nil, IAM: nil,
DisableLocalhost: false, DisableLocalhost: false,
WaitAfterFailedLogin: false,
Logger: nil, Logger: nil,
} }
var realm = "datarhei-core"
type iammiddleware struct { type iammiddleware struct {
iam iam.IAM iam iam.IAM
mounts []string mounts []string
@@ -106,9 +111,6 @@ 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)
@@ -129,14 +131,18 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
if resource == "/api/login" { if resource == "/api/login" {
identity, err = mw.findIdentityFromUserpass(c) identity, err = mw.findIdentityFromUserpass(c)
if err != nil { if err != nil {
if config.WaitAfterFailedLogin {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
}
return api.Err(http.StatusForbidden, "Forbidden", "%s", err) return api.Err(http.StatusForbidden, "Forbidden", "%s", err)
} }
if identity == nil { if identity == nil {
identity, err = mw.findIdentityFromAuth0(c) identity, err = mw.findIdentityFromAuth0(c)
if err != nil { if err != nil {
if config.WaitAfterFailedLogin {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
}
return api.Err(http.StatusForbidden, "Forbidden", "%s", err) return api.Err(http.StatusForbidden, "Forbidden", "%s", err)
} }
} }
@@ -150,38 +156,53 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
if resource == "/api/login/refresh" { if resource == "/api/login/refresh" {
usefor, _ := c.Get("usefor").(string) usefor, _ := c.Get("usefor").(string)
if usefor != "refresh" { if usefor != "refresh" {
if config.WaitAfterFailedLogin {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
}
return api.Err(http.StatusForbidden, "Forbidden", "invalid token") return api.Err(http.StatusForbidden, "Forbidden", "invalid token")
} }
} else { } else {
usefor, _ := c.Get("usefor").(string) usefor, _ := c.Get("usefor").(string)
if usefor != "access" { if usefor != "access" {
if config.WaitAfterFailedLogin {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
}
return api.Err(http.StatusForbidden, "Forbidden", "invalid token") return api.Err(http.StatusForbidden, "Forbidden", "invalid token")
} }
} }
} }
} }
if config.DisableLocalhost {
ip := c.RealIP() ip := c.RealIP()
if ip == "127.0.0.1" || ip == "::1" { if ip == "127.0.0.1" || ip == "::1" {
username = "$localhost" username = "$localhost"
} }
}
domain = c.QueryParam("group") domain = c.QueryParam("group")
resource = "api:" + resource resource = "api:" + resource
} else { } else {
identity, err = mw.findIdentityFromBasicAuth(c) identity, err = mw.findIdentityFromBasicAuth(c)
if err != nil { if err != nil {
if err == ErrUnauthorized { if err == ErrAuthRequired {
c.Response().Header().Set(echo.HeaderWWWAuthenticate, "Basic realm=datarhei-core") c.Response().Header().Set(echo.HeaderWWWAuthenticate, "Basic realm="+realm)
return api.Err(http.StatusUnauthorized, "Unauthorized", "%s", err) return api.Err(http.StatusUnauthorized, "Unauthorized", "%s", err)
} else if err == ErrBadRequest { } else {
return api.Err(http.StatusBadRequest, "Bad request", "%s", err) if config.WaitAfterFailedLogin {
time.Sleep(5 * time.Second)
} }
if err == ErrBadRequest {
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
} else if err == ErrUnauthorized {
c.Response().Header().Set(echo.HeaderWWWAuthenticate, "Basic realm="+realm)
return api.Err(http.StatusUnauthorized, "Unauthorized", "%s", err)
} else {
return api.Err(http.StatusForbidden, "Forbidden", "%s", err) return api.Err(http.StatusForbidden, "Forbidden", "%s", err)
} }
}
}
domain = mw.findDomainFromFilesystem(resource) domain = mw.findDomainFromFilesystem(resource)
resource = "fs:" + resource resource = "fs:" + resource
@@ -208,6 +229,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
} }
} }
var ErrAuthRequired = errors.New("unauthorized")
var ErrUnauthorized = errors.New("unauthorized") var ErrUnauthorized = errors.New("unauthorized")
var ErrBadRequest = errors.New("bad request") var ErrBadRequest = errors.New("bad request")
@@ -224,7 +246,7 @@ func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityV
} }
if !m.iam.Enforce("$anon", domain, "fs:"+path, c.Request().Method) { if !m.iam.Enforce("$anon", domain, "fs:"+path, c.Request().Method) {
return nil, ErrUnauthorized return nil, ErrAuthRequired
} }
return nil, nil return nil, nil

View File

@@ -147,7 +147,7 @@ func TestFindDomainFromFilesystem(t *testing.T) {
mw := &iammiddleware{ mw := &iammiddleware{
iam: iam, iam: iam,
mounts: []string{"/memfs", "/"}, mounts: []string{"/", "/memfs"},
} }
domain := mw.findDomainFromFilesystem("/") domain := mw.findDomainFromFilesystem("/")

View File

@@ -226,6 +226,7 @@ func NewServer(config Config) (Server, error) {
IAM: config.IAM, IAM: config.IAM,
Mounts: mounts, Mounts: mounts,
DisableLocalhost: config.IAMDisableLocalhost, DisableLocalhost: config.IAMDisableLocalhost,
WaitAfterFailedLogin: true,
Logger: s.logger.WithComponent("IAM"), Logger: s.logger.WithComponent("IAM"),
}) })