mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-16 18:21:24 +01:00
Extend bypass middleware with support of wildcard paths (#1628)
--------- Co-authored-by: Viktor Liu <viktor@netbird.io>
This commit is contained in:
parent
e1c50248d9
commit
d8ce08d898
@ -177,7 +177,10 @@ func TestAuthMiddleware_Handler(t *testing.T) {
|
|||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.shouldBypassAuth {
|
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)
|
req := httptest.NewRequest("GET", "http://testing"+tc.path, nil)
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package bypass
|
package bypass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var byPassMutex sync.RWMutex
|
var byPassMutex sync.RWMutex
|
||||||
@ -11,10 +15,16 @@ var byPassMutex sync.RWMutex
|
|||||||
var bypassPaths = make(map[string]struct{})
|
var bypassPaths = make(map[string]struct{})
|
||||||
|
|
||||||
// AddBypassPath adds an exact path to the list of paths that bypass middleware.
|
// 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()
|
byPassMutex.Lock()
|
||||||
defer byPassMutex.Unlock()
|
defer byPassMutex.Unlock()
|
||||||
|
if err := validatePath(path); err != nil {
|
||||||
|
return fmt.Errorf("validate: %w", err)
|
||||||
|
}
|
||||||
bypassPaths[path] = struct{}{}
|
bypassPaths[path] = struct{}{}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePath removes a path from the list of paths that bypass middleware.
|
// RemovePath removes a path from the list of paths that bypass middleware.
|
||||||
@ -24,16 +34,41 @@ func RemovePath(path string) {
|
|||||||
delete(bypassPaths, path)
|
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.
|
// 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.
|
// 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 {
|
func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *http.Request) bool {
|
||||||
byPassMutex.RLock()
|
byPassMutex.RLock()
|
||||||
defer byPassMutex.RUnlock()
|
defer byPassMutex.RUnlock()
|
||||||
|
|
||||||
if _, ok := bypassPaths[requestPath]; ok {
|
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)
|
h.ServeHTTP(w, r)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validatePath(p string) error {
|
||||||
|
_, err := path.Match(p, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -11,6 +11,19 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
"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) {
|
func TestAuthBypass(t *testing.T) {
|
||||||
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
@ -31,6 +44,13 @@ func TestAuthBypass(t *testing.T) {
|
|||||||
expectBypass: true,
|
expectBypass: true,
|
||||||
expectHTTPCode: http.StatusOK,
|
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",
|
name: "Path not added to bypass",
|
||||||
testPath: "/no-bypass",
|
testPath: "/no-bypass",
|
||||||
@ -59,6 +79,13 @@ func TestAuthBypass(t *testing.T) {
|
|||||||
expectBypass: false,
|
expectBypass: false,
|
||||||
expectHTTPCode: http.StatusOK,
|
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",
|
name: "Similar path does not match bypass",
|
||||||
pathToAdd: "/webhook",
|
pathToAdd: "/webhook",
|
||||||
@ -78,7 +105,8 @@ func TestAuthBypass(t *testing.T) {
|
|||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.pathToAdd != "" {
|
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)
|
defer bypass.RemovePath(tc.pathToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user