From 850731d344117ef371832b701657f35e1115bee8 Mon Sep 17 00:00:00 2001 From: veds-g Date: Mon, 4 Dec 2023 16:21:43 +0530 Subject: [PATCH 1/6] access path for auth endpoints Signed-off-by: veds-g --- server/authz/route_map.go | 62 +++++++++++-------- server/routes/routes.go | 2 +- .../components/common/AccountMenu/index.tsx | 3 +- ui/src/components/pages/Login/index.tsx | 8 ++- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/server/authz/route_map.go b/server/authz/route_map.go index 698397e302..d239748249 100644 --- a/server/authz/route_map.go +++ b/server/authz/route_map.go @@ -18,8 +18,9 @@ package authz import ( "fmt" - "github.com/gin-gonic/gin" + sharedutil "github.com/numaproj/numaflow/pkg/shared/util" + "strings" ) // RouteInfo is a struct which contains the route information with the object @@ -38,34 +39,45 @@ func newRouteInfo(object string, requiresAuthZ bool) *RouteInfo { } } +// GetBaseHref returns the base href of the server with a trailing slash. +func GetBaseHref() string { + baseHref := sharedutil.LookupEnvStringOr("NUMAFLOW_SERVER_BASE_HREF", "/") + if !strings.HasSuffix(baseHref, "/") { + baseHref = baseHref + "/" + } + return baseHref +} + +var baseHref = GetBaseHref() + // RouteMap is a map of routes to their corresponding RouteInfo objects. // It saves the object corresponding to the route and a boolean to indicate // whether the route requires authorization. var RouteMap = map[string]*RouteInfo{ - "GET:/api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), - "GET:/api/v1/authinfo": newRouteInfo(ObjectEvents, false), - "GET:/api/v1/namespaces": newRouteInfo(ObjectEvents, false), - "GET:/api/v1/cluster-summary": newRouteInfo(ObjectPipeline, false), - "GET:/api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "POST:/api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline/health": newRouteInfo(ObjectPipeline, true), - "PUT:/api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "DELETE:/api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "PATCH:/api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "POST:/api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:/api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:/api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "PUT:/api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "DELETE:/api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": newRouteInfo(ObjectPipeline, true), - "PUT:/api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/metrics/namespaces/:namespace/pods": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/pods/:pod/logs": newRouteInfo(ObjectPipeline, true), - "GET:/api/v1/namespaces/:namespace/events": newRouteInfo(ObjectEvents, true), + "GET:" + baseHref + "api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), + "GET:" + baseHref + "api/v1/authinfo": newRouteInfo(ObjectEvents, false), + "GET:" + baseHref + "api/v1/namespaces": newRouteInfo(ObjectEvents, false), + "GET:" + baseHref + "api/v1/cluster-summary": newRouteInfo(ObjectPipeline, false), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/health": newRouteInfo(ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "PATCH:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": newRouteInfo(ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/metrics/namespaces/:namespace/pods": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pods/:pod/logs": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/events": newRouteInfo(ObjectEvents, true), } // GetRouteMapKey returns the key for the RouteMap. diff --git a/server/routes/routes.go b/server/routes/routes.go index d856c8be48..12a3f68f08 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -51,7 +51,7 @@ func Routes(r *gin.Engine, sysInfo SystemInfo, authInfo AuthInfo, baseHref strin panic(err) } // noAuthGroup is a group of routes that do not require AuthN/AuthZ no matter whether auth is enabled. - noAuthGroup := r.Group("/auth/v1") + noAuthGroup := r.Group(baseHref + "auth/v1") v1RoutesNoAuth(noAuthGroup, dexObj) // r1Group is a group of routes that require AuthN/AuthZ when auth is enabled. diff --git a/ui/src/components/common/AccountMenu/index.tsx b/ui/src/components/common/AccountMenu/index.tsx index 639d28720d..3d114caef7 100644 --- a/ui/src/components/common/AccountMenu/index.tsx +++ b/ui/src/components/common/AccountMenu/index.tsx @@ -4,6 +4,7 @@ import IconButton from "@mui/material/IconButton"; import { AppContextProps } from "../../../types/declarations/app"; import { AppContext } from "../../../App"; import { useNavigate } from "react-router-dom"; +import { getBaseHref } from "../../../utils"; import Chip from "@mui/material/Chip"; export default function AccountMenu() { @@ -12,7 +13,7 @@ export default function AccountMenu() { const handleLogout = useCallback(async () => { try { - const response = await fetch(`/auth/v1/logout`); + const response = await fetch(`${getBaseHref()}/auth/v1/logout`); if (response.ok) { navigate("/login"); } diff --git a/ui/src/components/pages/Login/index.tsx b/ui/src/components/pages/Login/index.tsx index a50a4a95a5..5e74af7510 100644 --- a/ui/src/components/pages/Login/index.tsx +++ b/ui/src/components/pages/Login/index.tsx @@ -9,7 +9,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import CircularProgress from "@mui/material/CircularProgress"; -import { getAPIResponseError } from "../../../utils"; +import { getAPIResponseError, getBaseHref } from "../../../utils"; import { AppContext } from "../../../App"; import { AppContextProps } from "../../../types/declarations/app"; import gitIcon from "../../../images/github.png"; @@ -40,7 +40,9 @@ export function Login() { setLoadingMessage("Logging in..."); setCallbackError(undefined); try { - const response = await fetch(`/auth/v1/login?returnUrl=${returnURL}`); + const response = await fetch( + `${getBaseHref()}/auth/v1/login?returnUrl=${returnURL}` + ); if (response.ok) { const data = await response.json(); if (data?.data?.AuthCodeURL) { @@ -67,7 +69,7 @@ export function Login() { setLoadingMessage("Logging in..."); try { const response = await fetch( - `/auth/v1/callback?code=${code}&state=${state}` + `${getBaseHref()}/auth/v1/callback?code=${code}&state=${state}` ); if (response.ok) { const data = await response.json(); From 0602036f558fca7b311b7740df7a1048d4be154a Mon Sep 17 00:00:00 2001 From: veds-g Date: Tue, 5 Dec 2023 20:26:16 +0530 Subject: [PATCH 2/6] updated routeMap Signed-off-by: veds-g --- server/authz/rbac.go | 9 ++-- server/authz/route_map.go | 93 ++++++++++++++++++++++----------------- server/cmd/start.go | 3 ++ server/routes/routes.go | 8 ++-- 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/server/authz/rbac.go b/server/authz/rbac.go index 14756bcdfe..a1266d6887 100644 --- a/server/authz/rbac.go +++ b/server/authz/rbac.go @@ -236,12 +236,9 @@ func extractResource(c *gin.Context) string { // extractObject extracts the object from the request. func extractObject(c *gin.Context) string { - action := c.Request.Method - // Get the route map from the context. Key is in the format "method:path". - routeMapKey := fmt.Sprintf("%s:%s", action, c.FullPath()) - // Return the object from the route map. - if RouteMap[routeMapKey] != nil { - return RouteMap[routeMapKey].Object + // Return the object from the route map from context. + if GlobalRouteMap.GetRouteFromContext(c) != nil { + return GlobalRouteMap.GetRouteFromContext(c).Object } return emptyString } diff --git a/server/authz/route_map.go b/server/authz/route_map.go index d239748249..5bd1e8d2b2 100644 --- a/server/authz/route_map.go +++ b/server/authz/route_map.go @@ -18,9 +18,8 @@ package authz import ( "fmt" + "github.com/gin-gonic/gin" - sharedutil "github.com/numaproj/numaflow/pkg/shared/util" - "strings" ) // RouteInfo is a struct which contains the route information with the object @@ -39,52 +38,66 @@ func newRouteInfo(object string, requiresAuthZ bool) *RouteInfo { } } -// GetBaseHref returns the base href of the server with a trailing slash. -func GetBaseHref() string { - baseHref := sharedutil.LookupEnvStringOr("NUMAFLOW_SERVER_BASE_HREF", "/") - if !strings.HasSuffix(baseHref, "/") { - baseHref = baseHref + "/" - } - return baseHref -} - -var baseHref = GetBaseHref() +type routeMap map[string]*RouteInfo -// RouteMap is a map of routes to their corresponding RouteInfo objects. +// GlobalRouteMap is a map of routes to their corresponding RouteInfo objects. // It saves the object corresponding to the route and a boolean to indicate // whether the route requires authorization. -var RouteMap = map[string]*RouteInfo{ - "GET:" + baseHref + "api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), - "GET:" + baseHref + "api/v1/authinfo": newRouteInfo(ObjectEvents, false), - "GET:" + baseHref + "api/v1/namespaces": newRouteInfo(ObjectEvents, false), - "GET:" + baseHref + "api/v1/cluster-summary": newRouteInfo(ObjectPipeline, false), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "POST:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/health": newRouteInfo(ObjectPipeline, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "DELETE:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "PATCH:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "POST:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "DELETE:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": newRouteInfo(ObjectPipeline, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/metrics/namespaces/:namespace/pods": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pods/:pod/logs": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/events": newRouteInfo(ObjectEvents, true), +var GlobalRouteMap *routeMap + +// InitializeRouteMapInfo initializes the GlobalRouteMap. +// It is called only once when the server starts. +// It adds baseHref to the routeMap keys. +// For example, "GET:/api/v1/namespaces" becomes "GET:/baseHref/api/v1/namespaces". +func InitializeRouteMapInfo(baseHref string) { + if GlobalRouteMap == nil { + GlobalRouteMap = &routeMap{ + "GET:" + baseHref + "api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), + "GET:" + baseHref + "api/v1/authinfo": newRouteInfo(ObjectEvents, false), + "GET:" + baseHref + "api/v1/namespaces": newRouteInfo(ObjectEvents, false), + "GET:" + baseHref + "api/v1/cluster-summary": newRouteInfo(ObjectPipeline, false), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/health": newRouteInfo(ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "PATCH:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": newRouteInfo(ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/metrics/namespaces/:namespace/pods": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pods/:pod/logs": newRouteInfo(ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/events": newRouteInfo(ObjectEvents, true), + } + } } -// GetRouteMapKey returns the key for the RouteMap. +// GetRouteMapKey returns the key for the routeMap. // The key is a combination of the HTTP method and the path. // The format is "method:path". // For example, "GET:/api/v1/namespaces", "POST:/api/v1/namespaces". -// This key is used to get the RouteInfo object from the RouteMap. +// This key is used to get the RouteInfo object from the routeMap. func GetRouteMapKey(c *gin.Context) string { return fmt.Sprintf("%s:%s", c.Request.Method, c.FullPath()) } + +// GetRouteFromKey returns the RouteInfo object from the routeMap based on the key. +func (r *routeMap) GetRouteFromKey(key string) *RouteInfo { + if routeEntry, ok := (*r)[key]; ok { + return routeEntry + } + return nil +} + +// GetRouteFromContext returns the RouteInfo object from the routeMap based on the context. +func (r *routeMap) GetRouteFromContext(c *gin.Context) *RouteInfo { + return r.GetRouteFromKey(GetRouteMapKey(c)) +} diff --git a/server/cmd/start.go b/server/cmd/start.go index 8fe3323f2d..313cbec1b7 100644 --- a/server/cmd/start.go +++ b/server/cmd/start.go @@ -29,6 +29,7 @@ import ( "github.com/numaproj/numaflow/pkg/shared/logging" sharedtls "github.com/numaproj/numaflow/pkg/shared/tls" v1 "github.com/numaproj/numaflow/server/apis/v1" + "github.com/numaproj/numaflow/server/authz" "github.com/numaproj/numaflow/server/routes" ) @@ -66,6 +67,8 @@ func (s *server) Start() { router := gin.New() router.Use(gin.LoggerWithConfig(gin.LoggerConfig{SkipPaths: []string{"/livez"}})) router.RedirectTrailingSlash = true + // sets the route map for authorization with the base href + authz.InitializeRouteMapInfo(s.options.BaseHref) router.Use(static.Serve(s.options.BaseHref, static.LocalFile("./ui/build", true))) if s.options.BaseHref != "/" { router.NoRoute(func(c *gin.Context) { diff --git a/server/routes/routes.go b/server/routes/routes.go index 12a3f68f08..6f7811ae69 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -154,10 +154,8 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat c.Abort() return } - // Get the route map from the context. Key is in the format "method:path". - routeMapKey := authz.GetRouteMapKey(c) // Check if the route requires authorization. - if authz.RouteMap[routeMapKey] != nil && authz.RouteMap[routeMapKey].RequiresAuthZ { + if authz.GlobalRouteMap.GetRouteFromContext(c) != nil && authz.GlobalRouteMap.GetRouteFromContext(c).RequiresAuthZ { // Check if the user is authorized to execute the requested action. isAuthorized := authorizer.Authorize(c, userInfo) if isAuthorized { @@ -169,12 +167,12 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat c.JSON(http.StatusForbidden, v1.NewNumaflowAPIResponse(&errMsg, nil)) c.Abort() } - } else if authz.RouteMap[routeMapKey] != nil && !authz.RouteMap[routeMapKey].RequiresAuthZ { + } else if authz.GlobalRouteMap.GetRouteFromContext(c) != nil && !authz.GlobalRouteMap.GetRouteFromContext(c).RequiresAuthZ { // If the route does not require AuthZ, skip the AuthZ check. c.Next() } else { // If the route is not present in the route map, return an error. - logger.Errorw("route not present in routeMap", "route", routeMapKey) + logger.Errorw("route not present in routeMap", "route", authz.GetRouteMapKey(c)) errMsg := "Invalid route" c.JSON(http.StatusForbidden, v1.NewNumaflowAPIResponse(&errMsg, nil)) c.Abort() From 75c86767ca864364364b8c9384a3f569423bd0e1 Mon Sep 17 00:00:00 2001 From: veds-g Date: Wed, 6 Dec 2023 10:46:48 +0530 Subject: [PATCH 3/6] rbac test with routeMap initialization Signed-off-by: veds-g --- server/authz/rbac_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/authz/rbac_test.go b/server/authz/rbac_test.go index 3a0f66a563..5352d40afb 100644 --- a/server/authz/rbac_test.go +++ b/server/authz/rbac_test.go @@ -56,6 +56,8 @@ func TestCreateAuthorizer(t *testing.T) { // TestAuthorize is a test implementation of the Authorize functionality. // It tests that the user is authorized correctly for the given request. func TestAuthorize(t *testing.T) { + // Initialize the route map with the base href for the root path + InitializeRouteMapInfo("/") authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) From a80b3ecd16852e674dafe2c82b0fcc0c6fc4a380 Mon Sep 17 00:00:00 2001 From: veds-g Date: Wed, 6 Dec 2023 15:24:20 +0530 Subject: [PATCH 4/6] review comments Signed-off-by: veds-g --- server/authz/rbac.go | 4 ++-- server/authz/route_map.go | 18 +++++++++--------- server/routes/routes.go | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/authz/rbac.go b/server/authz/rbac.go index a1266d6887..0ed0a69ac4 100644 --- a/server/authz/rbac.go +++ b/server/authz/rbac.go @@ -237,8 +237,8 @@ func extractResource(c *gin.Context) string { // extractObject extracts the object from the request. func extractObject(c *gin.Context) string { // Return the object from the route map from context. - if GlobalRouteMap.GetRouteFromContext(c) != nil { - return GlobalRouteMap.GetRouteFromContext(c).Object + if AuthRouteMap.GetRouteFromContext(c) != nil { + return AuthRouteMap.GetRouteFromContext(c).Object } return emptyString } diff --git a/server/authz/route_map.go b/server/authz/route_map.go index 5bd1e8d2b2..533fa135e4 100644 --- a/server/authz/route_map.go +++ b/server/authz/route_map.go @@ -40,18 +40,18 @@ func newRouteInfo(object string, requiresAuthZ bool) *RouteInfo { type routeMap map[string]*RouteInfo -// GlobalRouteMap is a map of routes to their corresponding RouteInfo objects. +// AuthRouteMap is a map of routes to their corresponding RouteInfo objects. // It saves the object corresponding to the route and a boolean to indicate // whether the route requires authorization. -var GlobalRouteMap *routeMap +var AuthRouteMap routeMap -// InitializeRouteMapInfo initializes the GlobalRouteMap. +// InitializeRouteMapInfo initializes the AuthRouteMap. // It is called only once when the server starts. // It adds baseHref to the routeMap keys. // For example, "GET:/api/v1/namespaces" becomes "GET:/baseHref/api/v1/namespaces". func InitializeRouteMapInfo(baseHref string) { - if GlobalRouteMap == nil { - GlobalRouteMap = &routeMap{ + if AuthRouteMap == nil { + AuthRouteMap = routeMap{ "GET:" + baseHref + "api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), "GET:" + baseHref + "api/v1/authinfo": newRouteInfo(ObjectEvents, false), "GET:" + baseHref + "api/v1/namespaces": newRouteInfo(ObjectEvents, false), @@ -80,16 +80,16 @@ func InitializeRouteMapInfo(baseHref string) { } } -// GetRouteMapKey returns the key for the routeMap. +// GetRouteMapKey returns the key for the AuthRouteMap. // The key is a combination of the HTTP method and the path. // The format is "method:path". // For example, "GET:/api/v1/namespaces", "POST:/api/v1/namespaces". -// This key is used to get the RouteInfo object from the routeMap. +// This key is used to get the RouteInfo object from the AuthRouteMap. func GetRouteMapKey(c *gin.Context) string { return fmt.Sprintf("%s:%s", c.Request.Method, c.FullPath()) } -// GetRouteFromKey returns the RouteInfo object from the routeMap based on the key. +// GetRouteFromKey returns the RouteInfo object from the AuthRouteMap based on the key. func (r *routeMap) GetRouteFromKey(key string) *RouteInfo { if routeEntry, ok := (*r)[key]; ok { return routeEntry @@ -97,7 +97,7 @@ func (r *routeMap) GetRouteFromKey(key string) *RouteInfo { return nil } -// GetRouteFromContext returns the RouteInfo object from the routeMap based on the context. +// GetRouteFromContext returns the RouteInfo object from the AuthRouteMap based on the context. func (r *routeMap) GetRouteFromContext(c *gin.Context) *RouteInfo { return r.GetRouteFromKey(GetRouteMapKey(c)) } diff --git a/server/routes/routes.go b/server/routes/routes.go index 6f7811ae69..64e6a017fd 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -155,7 +155,7 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat return } // Check if the route requires authorization. - if authz.GlobalRouteMap.GetRouteFromContext(c) != nil && authz.GlobalRouteMap.GetRouteFromContext(c).RequiresAuthZ { + if authz.AuthRouteMap.GetRouteFromContext(c) != nil && authz.AuthRouteMap.GetRouteFromContext(c).RequiresAuthZ { // Check if the user is authorized to execute the requested action. isAuthorized := authorizer.Authorize(c, userInfo) if isAuthorized { @@ -167,7 +167,7 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat c.JSON(http.StatusForbidden, v1.NewNumaflowAPIResponse(&errMsg, nil)) c.Abort() } - } else if authz.GlobalRouteMap.GetRouteFromContext(c) != nil && !authz.GlobalRouteMap.GetRouteFromContext(c).RequiresAuthZ { + } else if authz.AuthRouteMap.GetRouteFromContext(c) != nil && !authz.AuthRouteMap.GetRouteFromContext(c).RequiresAuthZ { // If the route does not require AuthZ, skip the AuthZ check. c.Next() } else { From 0bdbe9c9afaf23f642018782a2f43c20c9b6ae0a Mon Sep 17 00:00:00 2001 From: veds-g Date: Wed, 6 Dec 2023 23:15:14 +0530 Subject: [PATCH 5/6] nits Signed-off-by: veds-g --- server/authz/route_map.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server/authz/route_map.go b/server/authz/route_map.go index 533fa135e4..8031378f7b 100644 --- a/server/authz/route_map.go +++ b/server/authz/route_map.go @@ -89,15 +89,10 @@ func GetRouteMapKey(c *gin.Context) string { return fmt.Sprintf("%s:%s", c.Request.Method, c.FullPath()) } -// GetRouteFromKey returns the RouteInfo object from the AuthRouteMap based on the key. -func (r *routeMap) GetRouteFromKey(key string) *RouteInfo { - if routeEntry, ok := (*r)[key]; ok { +// GetRouteFromContext returns the RouteInfo object from the AuthRouteMap based on the context. +func (r routeMap) GetRouteFromContext(c *gin.Context) *RouteInfo { + if routeEntry, ok := r[GetRouteMapKey(c)]; ok { return routeEntry } return nil } - -// GetRouteFromContext returns the RouteInfo object from the AuthRouteMap based on the context. -func (r *routeMap) GetRouteFromContext(c *gin.Context) *RouteInfo { - return r.GetRouteFromKey(GetRouteMapKey(c)) -} From 71175fdcd6b32370b5308b7d669fdefc82b57ced Mon Sep 17 00:00:00 2001 From: veds-g Date: Thu, 7 Dec 2023 14:49:14 +0530 Subject: [PATCH 6/6] routeMap refactor Signed-off-by: veds-g --- server/authz/rbac.go | 15 +++++++----- server/authz/rbac_test.go | 14 +++++------ server/authz/route_map.go | 47 ++++-------------------------------- server/cmd/start.go | 36 ++++++++++++++++++++++++++- server/routes/routes.go | 12 ++++----- server/routes/routes_test.go | 4 ++- 6 files changed, 64 insertions(+), 64 deletions(-) diff --git a/server/authz/rbac.go b/server/authz/rbac.go index 0ed0a69ac4..6cb667ae25 100644 --- a/server/authz/rbac.go +++ b/server/authz/rbac.go @@ -46,26 +46,28 @@ const ( ) // CasbinObject is the struct that implements the Authorizer interface. -// It contains the Casbin Enforcer, the current scopes, the default policy and the config reader. +// It contains the Casbin Enforcer, the current scopes, the default policy, the config reader and the route map. // The config reader is used to watch for changes in the config file. // The Casbin Enforcer is used to enforce the authorization policy. // The current scopes are used to determine the user identity token to be used for authorization. // policyDefault is the default policy to be used when the requested resource is not present in the policy. // userPermCount is a cache to store the count of permissions for a user. If the user has permissions in the // policy, we store the count in the cache and return based on the value. +// authRouteMap is a map of routes to their corresponding RouteInfo objects. type CasbinObject struct { enforcer *casbin.Enforcer userPermCount *sync.Map currentScopes []string policyDefault string configReader *viper.Viper + authRouteMap RouteMap opts *options rwMutex *sync.RWMutex } // NewCasbinObject returns a new CasbinObject. It initializes the Casbin Enforcer with the model and policy. // It also initializes the config reader to watch for changes in the config file. -func NewCasbinObject(inputOptions ...Option) (*CasbinObject, error) { +func NewCasbinObject(authRouteMap RouteMap, inputOptions ...Option) (*CasbinObject, error) { // Set the default options. var opts = DefaultOptions() // Apply the input options. @@ -93,6 +95,7 @@ func NewCasbinObject(inputOptions ...Option) (*CasbinObject, error) { currentScopes: currentScopes, policyDefault: policyDefault, configReader: configReader, + authRouteMap: authRouteMap, opts: opts, rwMutex: &sync.RWMutex{}, } @@ -116,7 +119,7 @@ func (cas *CasbinObject) Authorize(c *gin.Context, userInfo *authn.UserInfo) boo scopedList := getSubjectFromScope(currentScopes, userInfo) // Get the resource, object and action from the request. resource := extractResource(c) - object := extractObject(c) + object := extractObject(c, cas.authRouteMap) action := c.Request.Method userHasPolicies := false // Check for the given scoped list if the user is authorized using any of the subjects in the list. @@ -235,10 +238,10 @@ func extractResource(c *gin.Context) string { } // extractObject extracts the object from the request. -func extractObject(c *gin.Context) string { +func extractObject(c *gin.Context, authRouteMap RouteMap) string { // Return the object from the route map from context. - if AuthRouteMap.GetRouteFromContext(c) != nil { - return AuthRouteMap.GetRouteFromContext(c).Object + if authRouteMap.GetRouteFromContext(c) != nil { + return authRouteMap.GetRouteFromContext(c).Object } return emptyString } diff --git a/server/authz/rbac_test.go b/server/authz/rbac_test.go index 5352d40afb..242bfa05fe 100644 --- a/server/authz/rbac_test.go +++ b/server/authz/rbac_test.go @@ -33,7 +33,7 @@ var ( // TestCreateAuthorizer is a test implementation of the NewCasbinObject function. // It checks that the authorizer is created correctly and the policies, configs are loaded correctly. func TestCreateAuthorizer(t *testing.T) { - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil @@ -56,9 +56,7 @@ func TestCreateAuthorizer(t *testing.T) { // TestAuthorize is a test implementation of the Authorize functionality. // It tests that the user is authorized correctly for the given request. func TestAuthorize(t *testing.T) { - // Initialize the route map with the base href for the root path - InitializeRouteMapInfo("/") - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil @@ -94,7 +92,7 @@ func TestAuthorize(t *testing.T) { // Additionally, it tests that the default policy is applied correctly when a user is not found in the policy map. // The default policy is set to "role:readonly" in the test data. func TestDefaultPolicy(t *testing.T) { - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil @@ -121,7 +119,7 @@ func TestDefaultPolicy(t *testing.T) { // It tests that the scopes are set correctly when the authorizer is created. // Additionally, it tests that the required scopes are tested for the user. func TestScopes(t *testing.T) { - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil @@ -163,7 +161,7 @@ func TestScopes(t *testing.T) { // TestNamespaces is a test implementation of the Namespaces based access. // It tests that a user can access a namespace that is in the policy map. func TestNamespaces(t *testing.T) { - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil @@ -188,7 +186,7 @@ func TestNamespaces(t *testing.T) { func TestConfigFileReload(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - authorizer, err := NewCasbinObject(WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyReloadFilePath)) + authorizer, err := NewCasbinObject(RouteMap{}, WithPolicyMap(testPolicyMapPath), WithPropertyFile(testPropertyReloadFilePath)) assert.NoError(t, err) // Test that the authorizer is not nil diff --git a/server/authz/route_map.go b/server/authz/route_map.go index 8031378f7b..601724b88e 100644 --- a/server/authz/route_map.go +++ b/server/authz/route_map.go @@ -30,55 +30,18 @@ type RouteInfo struct { RequiresAuthZ bool } -// newRouteInfo creates a new RouteInfo object. -func newRouteInfo(object string, requiresAuthZ bool) *RouteInfo { +// NewRouteInfo creates a new RouteInfo object. +func NewRouteInfo(object string, requiresAuthZ bool) *RouteInfo { return &RouteInfo{ Object: object, RequiresAuthZ: requiresAuthZ, } } -type routeMap map[string]*RouteInfo - -// AuthRouteMap is a map of routes to their corresponding RouteInfo objects. +// RouteMap type is a map of routes to their corresponding RouteInfo objects. // It saves the object corresponding to the route and a boolean to indicate // whether the route requires authorization. -var AuthRouteMap routeMap - -// InitializeRouteMapInfo initializes the AuthRouteMap. -// It is called only once when the server starts. -// It adds baseHref to the routeMap keys. -// For example, "GET:/api/v1/namespaces" becomes "GET:/baseHref/api/v1/namespaces". -func InitializeRouteMapInfo(baseHref string) { - if AuthRouteMap == nil { - AuthRouteMap = routeMap{ - "GET:" + baseHref + "api/v1/sysinfo": newRouteInfo(ObjectPipeline, false), - "GET:" + baseHref + "api/v1/authinfo": newRouteInfo(ObjectEvents, false), - "GET:" + baseHref + "api/v1/namespaces": newRouteInfo(ObjectEvents, false), - "GET:" + baseHref + "api/v1/cluster-summary": newRouteInfo(ObjectPipeline, false), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "POST:" + baseHref + "api/v1/namespaces/:namespace/pipelines": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/health": newRouteInfo(ObjectPipeline, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "DELETE:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "PATCH:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": newRouteInfo(ObjectPipeline, true), - "POST:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "DELETE:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": newRouteInfo(ObjectISBSvc, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": newRouteInfo(ObjectPipeline, true), - "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/metrics/namespaces/:namespace/pods": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/pods/:pod/logs": newRouteInfo(ObjectPipeline, true), - "GET:" + baseHref + "api/v1/namespaces/:namespace/events": newRouteInfo(ObjectEvents, true), - } - } -} +type RouteMap map[string]*RouteInfo // GetRouteMapKey returns the key for the AuthRouteMap. // The key is a combination of the HTTP method and the path. @@ -90,7 +53,7 @@ func GetRouteMapKey(c *gin.Context) string { } // GetRouteFromContext returns the RouteInfo object from the AuthRouteMap based on the context. -func (r routeMap) GetRouteFromContext(c *gin.Context) *RouteInfo { +func (r RouteMap) GetRouteFromContext(c *gin.Context) *RouteInfo { if routeEntry, ok := r[GetRouteMapKey(c)]; ok { return routeEntry } diff --git a/server/cmd/start.go b/server/cmd/start.go index 313cbec1b7..56d3e48e2e 100644 --- a/server/cmd/start.go +++ b/server/cmd/start.go @@ -68,7 +68,7 @@ func (s *server) Start() { router.Use(gin.LoggerWithConfig(gin.LoggerConfig{SkipPaths: []string{"/livez"}})) router.RedirectTrailingSlash = true // sets the route map for authorization with the base href - authz.InitializeRouteMapInfo(s.options.BaseHref) + authRouteMap := CreateAuthRouteMap(s.options.BaseHref) router.Use(static.Serve(s.options.BaseHref, static.LocalFile("./ui/build", true))) if s.options.BaseHref != "/" { router.NoRoute(func(c *gin.Context) { @@ -88,6 +88,7 @@ func (s *server) Start() { ServerAddr: s.options.ServerAddr, }, s.options.BaseHref, + authRouteMap, ) router.Use(UrlRewrite(router)) server := http.Server{ @@ -141,3 +142,36 @@ func UrlRewrite(r *gin.Engine) gin.HandlerFunc { c.Next() } } + +// CreateAuthRouteMap creates the route map for authorization. +// The key is a combination of the HTTP method and the path along with the baseHref. +// For example, "GET:/api/v1/namespaces" becomes "GET:/baseHref/api/v1/namespaces". +// The value is a RouteInfo object. +func CreateAuthRouteMap(baseHref string) authz.RouteMap { + return authz.RouteMap{ + "GET:" + baseHref + "api/v1/sysinfo": authz.NewRouteInfo(authz.ObjectPipeline, false), + "GET:" + baseHref + "api/v1/authinfo": authz.NewRouteInfo(authz.ObjectEvents, false), + "GET:" + baseHref + "api/v1/namespaces": authz.NewRouteInfo(authz.ObjectEvents, false), + "GET:" + baseHref + "api/v1/cluster-summary": authz.NewRouteInfo(authz.ObjectPipeline, false), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines": authz.NewRouteInfo(authz.ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/pipelines": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/health": authz.NewRouteInfo(authz.ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": authz.NewRouteInfo(authz.ObjectPipeline, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": authz.NewRouteInfo(authz.ObjectPipeline, true), + "PATCH:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline": authz.NewRouteInfo(authz.ObjectPipeline, true), + "POST:" + baseHref + "api/v1/namespaces/:namespace/isb-services": authz.NewRouteInfo(authz.ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services": authz.NewRouteInfo(authz.ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": authz.NewRouteInfo(authz.ObjectISBSvc, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": authz.NewRouteInfo(authz.ObjectISBSvc, true), + "DELETE:" + baseHref + "api/v1/namespaces/:namespace/isb-services/:isb-service": authz.NewRouteInfo(authz.ObjectISBSvc, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/isbs": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/watermarks": authz.NewRouteInfo(authz.ObjectPipeline, true), + "PUT:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/metrics": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pipelines/:pipeline/vertices/:vertex/pods": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/metrics/namespaces/:namespace/pods": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/pods/:pod/logs": authz.NewRouteInfo(authz.ObjectPipeline, true), + "GET:" + baseHref + "api/v1/namespaces/:namespace/events": authz.NewRouteInfo(authz.ObjectEvents, true), + } +} diff --git a/server/routes/routes.go b/server/routes/routes.go index 64e6a017fd..eb92c167d0 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -42,7 +42,7 @@ type AuthInfo struct { var logger = logging.NewLogger().Named("server") -func Routes(r *gin.Engine, sysInfo SystemInfo, authInfo AuthInfo, baseHref string) { +func Routes(r *gin.Engine, sysInfo SystemInfo, authInfo AuthInfo, baseHref string, authRouteMap authz.RouteMap) { r.GET("/livez", func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -58,12 +58,12 @@ func Routes(r *gin.Engine, sysInfo SystemInfo, authInfo AuthInfo, baseHref strin // they share the AuthN/AuthZ middleware. r1Group := r.Group(baseHref + "api/v1") if !authInfo.DisableAuth { - authorizer, err := authz.NewCasbinObject() + authorizer, err := authz.NewCasbinObject(authRouteMap) if err != nil { panic(err) } // Add the AuthN/AuthZ middleware to the group. - r1Group.Use(authMiddleware(authorizer, dexObj)) + r1Group.Use(authMiddleware(authorizer, dexObj, authRouteMap)) v1Routes(r1Group, dexObj) } else { v1Routes(r1Group, nil) @@ -144,7 +144,7 @@ func v1Routes(r gin.IRouter, dexObj *v1.DexObject) { // authMiddleware is the middleware for AuthN/AuthZ. // it ensures the user is authenticated and authorized // to execute the requested action before sending the request to the api handler. -func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticator) gin.HandlerFunc { +func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticator, authRouteMap authz.RouteMap) gin.HandlerFunc { return func(c *gin.Context) { // Authenticate the user. userInfo, err := authenticator.Authenticate(c) @@ -155,7 +155,7 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat return } // Check if the route requires authorization. - if authz.AuthRouteMap.GetRouteFromContext(c) != nil && authz.AuthRouteMap.GetRouteFromContext(c).RequiresAuthZ { + if authRouteMap.GetRouteFromContext(c) != nil && authRouteMap.GetRouteFromContext(c).RequiresAuthZ { // Check if the user is authorized to execute the requested action. isAuthorized := authorizer.Authorize(c, userInfo) if isAuthorized { @@ -167,7 +167,7 @@ func authMiddleware(authorizer authz.Authorizer, authenticator authn.Authenticat c.JSON(http.StatusForbidden, v1.NewNumaflowAPIResponse(&errMsg, nil)) c.Abort() } - } else if authz.AuthRouteMap.GetRouteFromContext(c) != nil && !authz.AuthRouteMap.GetRouteFromContext(c).RequiresAuthZ { + } else if authRouteMap.GetRouteFromContext(c) != nil && !authRouteMap.GetRouteFromContext(c).RequiresAuthZ { // If the route does not require AuthZ, skip the AuthZ check. c.Next() } else { diff --git a/server/routes/routes_test.go b/server/routes/routes_test.go index c965d395bb..97e1b25fcf 100644 --- a/server/routes/routes_test.go +++ b/server/routes/routes_test.go @@ -17,6 +17,7 @@ limitations under the License. package routes import ( + "github.com/numaproj/numaflow/server/authz" "net/http" "net/http/httptest" "testing" @@ -41,7 +42,8 @@ func TestRoutes(t *testing.T) { DisableAuth: false, DexServerAddr: "test-dex-server-addr", } - Routes(router, sysInfo, authInfo, "/") + authRouteMap := authz.RouteMap{} + Routes(router, sysInfo, authInfo, "/", authRouteMap) t.Run("/404", func(t *testing.T) { w := httptest.NewRecorder() req, err := http.NewRequest(http.MethodGet, "/404", nil)