Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend bypass middleware with support of wildcard paths #1628

Merged
merged 5 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion management/server/http/middleware/auth_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 38 additions & 4 deletions management/server/http/middleware/bypass/bypass.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package bypass

import (
"net/http"
"path"
"sync"

log "github.com/sirupsen/logrus"
)

var byPassMutex sync.RWMutex
Expand All @@ -11,10 +14,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 err
surik marked this conversation as resolved.
Show resolved Hide resolved
}
bypassPaths[path] = struct{}{}
return nil
}

// RemovePath removes a path from the list of paths that bypass middleware.
Expand All @@ -24,16 +33,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
}
30 changes: 29 additions & 1 deletion management/server/http/middleware/bypass/bypass_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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)
}

Expand Down
Loading