Skip to content

Commit

Permalink
Extend bypass middleware with support of wildcard paths (netbirdio#1628)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Viktor Liu <viktor@netbird.io>
  • Loading branch information
surik and lixmal authored Feb 26, 2024
1 parent feffc0d commit 3c46874
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 6 deletions.
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
43 changes: 39 additions & 4 deletions management/server/http/middleware/bypass/bypass.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package bypass

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

log "github.com/sirupsen/logrus"
)

var byPassMutex sync.RWMutex
Expand All @@ -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.
Expand All @@ -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
}
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

0 comments on commit 3c46874

Please sign in to comment.