mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
Add auth bypass inventory coverage
This commit is contained in:
@@ -210,15 +210,14 @@ var publicRouteAllowlist = []string{
|
||||
"/api/security/status",
|
||||
"/api/security/validate-bootstrap-token",
|
||||
"/api/security/quick-setup",
|
||||
"/api/security/recovery",
|
||||
"/api/login",
|
||||
"/api/logout",
|
||||
"/api/oidc/login",
|
||||
"/api/ai/oauth/callback",
|
||||
"/api/setup-script",
|
||||
"/api/system/verify-temperature-ssh",
|
||||
"/api/system/ssh-config",
|
||||
"/api/auto-register",
|
||||
"/api/install/install-docker.sh",
|
||||
"/api/install/install.sh",
|
||||
"/api/install/install.ps1",
|
||||
"/install-docker-agent.sh",
|
||||
"/install-container-agent.sh",
|
||||
"/download/pulse-docker-agent",
|
||||
@@ -227,7 +226,6 @@ var publicRouteAllowlist = []string{
|
||||
"/uninstall-host-agent.sh",
|
||||
"/uninstall-host-agent.ps1",
|
||||
"/download/pulse-host-agent",
|
||||
"/download/pulse-host-agent.sha256",
|
||||
"/install.sh",
|
||||
"/install.ps1",
|
||||
"/download/pulse-agent",
|
||||
|
||||
80
internal/api/router_auth_bypass_inventory_test.go
Normal file
80
internal/api/router_auth_bypass_inventory_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRouterAuthBypassInventory(t *testing.T) {
|
||||
bypassPaths := parseAuthBypassPaths(t)
|
||||
|
||||
expected := sliceToSet(t, authBypassAllowlist, "auth bypass allowlist")
|
||||
actual := sliceToSet(t, bypassPaths, "router auth bypass paths")
|
||||
publicRoutes := sliceToSet(t, publicRouteAllowlist, "public route allowlist")
|
||||
|
||||
if missing := setDifference(actual, expected); len(missing) > 0 {
|
||||
t.Fatalf("auth bypass paths missing from allowlist: %s", strings.Join(sortedKeys(missing), ", "))
|
||||
}
|
||||
if stale := setDifference(expected, actual); len(stale) > 0 {
|
||||
t.Fatalf("auth bypass allowlist contains paths not in router.go: %s", strings.Join(sortedKeys(stale), ", "))
|
||||
}
|
||||
if missing := setDifference(expected, publicRoutes); len(missing) > 0 {
|
||||
t.Fatalf("auth bypass paths must be listed as public: %s", strings.Join(sortedKeys(missing), ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func parseAuthBypassPaths(t *testing.T) []string {
|
||||
t.Helper()
|
||||
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatalf("failed to locate test file path")
|
||||
}
|
||||
routerPath := filepath.Join(filepath.Dir(file), "router.go")
|
||||
data, err := os.ReadFile(routerPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read router.go: %v", err)
|
||||
}
|
||||
src := string(data)
|
||||
|
||||
start := strings.Index(src, "normalizedPath := path.Clean")
|
||||
if start == -1 {
|
||||
t.Fatalf("normalizedPath block not found in router.go")
|
||||
}
|
||||
end := strings.Index(src[start:], "Dev mode bypass for admin endpoints")
|
||||
if end == -1 {
|
||||
t.Fatalf("auth bypass block end not found in router.go")
|
||||
}
|
||||
block := src[start : start+end]
|
||||
|
||||
re := regexp.MustCompile(`normalizedPath == "([^"]+)"`)
|
||||
matches := re.FindAllStringSubmatch(block, -1)
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("no auth bypass paths found in router.go")
|
||||
}
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
paths := make([]string, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
path := match[1]
|
||||
if _, ok := seen[path]; ok {
|
||||
continue
|
||||
}
|
||||
seen[path] = struct{}{}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
return paths
|
||||
}
|
||||
|
||||
var authBypassAllowlist = []string{
|
||||
"/api/auto-register",
|
||||
"/api/setup-script",
|
||||
"/api/system/ssh-config",
|
||||
"/api/system/verify-temperature-ssh",
|
||||
}
|
||||
@@ -3268,8 +3268,6 @@ func TestInstallScriptEndpointsBypassAuth(t *testing.T) {
|
||||
|
||||
paths := []string{
|
||||
"/api/install/install-docker.sh",
|
||||
"/api/install/install.sh",
|
||||
"/api/install/install.ps1",
|
||||
}
|
||||
|
||||
for idx, path := range paths {
|
||||
@@ -3285,6 +3283,30 @@ func TestInstallScriptEndpointsBypassAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallScriptAPIRoutesRequireAuth(t *testing.T) {
|
||||
cfg := newTestConfigWithTokens(t)
|
||||
cfg.AuthUser = "admin"
|
||||
cfg.AuthPass = "hashed"
|
||||
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
|
||||
|
||||
paths := []string{
|
||||
"/api/install/install.sh",
|
||||
"/api/install/install.ps1",
|
||||
}
|
||||
|
||||
for idx, path := range paths {
|
||||
ip := "203.0.113." + strconv.Itoa(80+idx)
|
||||
ResetRateLimitForIP(ip)
|
||||
req := httptest.NewRequest(http.MethodPost, path, nil)
|
||||
req.RemoteAddr = ip + ":1234"
|
||||
rec := httptest.NewRecorder()
|
||||
router.Handler().ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401 for protected install script %s, got %d", path, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutRequiresAuthInAPIMode(t *testing.T) {
|
||||
record := newTokenRecord(t, "logout-token-123.12345678", []string{config.ScopeSettingsRead}, nil)
|
||||
cfg := newTestConfigWithTokens(t, record)
|
||||
|
||||
Reference in New Issue
Block a user