diff --git a/management/server/http/middleware/auth_middleware_test.go b/management/server/http/middleware/auth_middleware_test.go index 3e3e8419446..588bcaf02e3 100644 --- a/management/server/http/middleware/auth_middleware_test.go +++ b/management/server/http/middleware/auth_middleware_test.go @@ -177,7 +177,10 @@ func TestAuthMiddleware_Handler(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { if tc.shouldBypassAuth { - bypass.AddBypassPath(tc.path) + err := bypass.AddBypassPath(tc.path) + if err != nil { + t.Fatalf("failed to add bypass path: %v", err) + } } req := httptest.NewRequest("GET", "http://testing"+tc.path, nil) diff --git a/management/server/http/middleware/bypass/bypass.go b/management/server/http/middleware/bypass/bypass.go index 2f2652eb61d..87b41c6fc51 100644 --- a/management/server/http/middleware/bypass/bypass.go +++ b/management/server/http/middleware/bypass/bypass.go @@ -1,8 +1,12 @@ package bypass import ( + "fmt" "net/http" + "path" "sync" + + log "github.com/sirupsen/logrus" ) var byPassMutex sync.RWMutex @@ -11,10 +15,16 @@ var byPassMutex sync.RWMutex var bypassPaths = make(map[string]struct{}) // AddBypassPath adds an exact path to the list of paths that bypass middleware. -func AddBypassPath(path string) { +// Paths can include wildcards, such as /api/*. Paths are matched using path.Match. +// Returns an error if the path has invalid pattern. +func AddBypassPath(path string) error { byPassMutex.Lock() defer byPassMutex.Unlock() + if err := validatePath(path); err != nil { + return fmt.Errorf("validate: %w", err) + } bypassPaths[path] = struct{}{} + return nil } // RemovePath removes a path from the list of paths that bypass middleware. @@ -24,16 +34,41 @@ func RemovePath(path string) { delete(bypassPaths, path) } +// GetList returns a list of all bypass paths. +func GetList() []string { + byPassMutex.RLock() + defer byPassMutex.RUnlock() + + list := make([]string, 0, len(bypassPaths)) + for k := range bypassPaths { + list = append(list, k) + } + + return list +} + // ShouldBypass checks if the request path is one of the auth bypass paths and returns true if the middleware should be bypassed. // This can be used to bypass authz/authn middlewares for certain paths, such as webhooks that implement their own authentication. func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *http.Request) bool { byPassMutex.RLock() defer byPassMutex.RUnlock() - if _, ok := bypassPaths[requestPath]; ok { - h.ServeHTTP(w, r) - return true + for bypassPath := range bypassPaths { + matched, err := path.Match(bypassPath, requestPath) + if err != nil { + log.Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err) + continue + } + if matched { + h.ServeHTTP(w, r) + return true + } } return false } + +func validatePath(p string) error { + _, err := path.Match(p, "") + return err +} diff --git a/management/server/http/middleware/bypass/bypass_test.go b/management/server/http/middleware/bypass/bypass_test.go index efcfe1c3d88..c65e6fa1f24 100644 --- a/management/server/http/middleware/bypass/bypass_test.go +++ b/management/server/http/middleware/bypass/bypass_test.go @@ -11,6 +11,19 @@ import ( "github.com/netbirdio/netbird/management/server/http/middleware/bypass" ) +func TestGetList(t *testing.T) { + bypassPaths := []string{"/path1", "/path2", "/path3"} + + for _, path := range bypassPaths { + err := bypass.AddBypassPath(path) + require.NoError(t, err, "Adding bypass path should not fail") + } + + list := bypass.GetList() + + assert.ElementsMatch(t, bypassPaths, list, "Bypass path list did not match expected paths") +} + func TestAuthBypass(t *testing.T) { dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -31,6 +44,13 @@ func TestAuthBypass(t *testing.T) { expectBypass: true, expectHTTPCode: http.StatusOK, }, + { + name: "Wildcard path added to bypass", + pathToAdd: "/bypass/*", + testPath: "/bypass/extra", + expectBypass: true, + expectHTTPCode: http.StatusOK, + }, { name: "Path not added to bypass", testPath: "/no-bypass", @@ -59,6 +79,13 @@ func TestAuthBypass(t *testing.T) { expectBypass: false, expectHTTPCode: http.StatusOK, }, + { + name: "Wildcard subpath does not match bypass", + pathToAdd: "/webhook/*", + testPath: "/webhook/extra/path", + expectBypass: false, + expectHTTPCode: http.StatusOK, + }, { name: "Similar path does not match bypass", pathToAdd: "/webhook", @@ -78,7 +105,8 @@ func TestAuthBypass(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if tc.pathToAdd != "" { - bypass.AddBypassPath(tc.pathToAdd) + err := bypass.AddBypassPath(tc.pathToAdd) + require.NoError(t, err, "Adding bypass path should not fail") defer bypass.RemovePath(tc.pathToAdd) }