Pulse/internal/api/router_public_paths_inventory_test.go
2026-02-04 13:18:29 +00:00

133 lines
3.9 KiB
Go

package api
import (
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
)
func TestRouterPublicPathsInventory(t *testing.T) {
literalPaths, dynamicPaths := parsePublicPaths(t)
expectedLiterals := sliceToSet(t, publicPathsAllowlist, "public paths allowlist")
expectedDynamics := sliceToSet(t, publicPathsDynamicAllowlist, "public dynamic allowlist")
expectedPublic := sliceToSet(t, publicRouteAllowlist, "public route allowlist")
actualLiterals := sliceToSet(t, literalPaths, "router public paths")
actualDynamics := sliceToSet(t, dynamicPaths, "router public dynamic paths")
if missing := setDifference(actualLiterals, expectedLiterals); len(missing) > 0 {
t.Fatalf("public paths missing from allowlist: %s", strings.Join(sortedKeys(missing), ", "))
}
if stale := setDifference(expectedLiterals, actualLiterals); len(stale) > 0 {
t.Fatalf("public allowlist contains paths not in router.go: %s", strings.Join(sortedKeys(stale), ", "))
}
if missing := setDifference(actualDynamics, expectedDynamics); len(missing) > 0 {
t.Fatalf("public dynamic paths missing from allowlist: %s", strings.Join(sortedKeys(missing), ", "))
}
if stale := setDifference(expectedDynamics, actualDynamics); len(stale) > 0 {
t.Fatalf("public dynamic allowlist contains paths not in router.go: %s", strings.Join(sortedKeys(stale), ", "))
}
if missing := setDifference(expectedLiterals, expectedPublic); len(missing) > 0 {
t.Fatalf("publicPaths entries missing from public route allowlist: %s", strings.Join(sortedKeys(missing), ", "))
}
supportedPublic := sliceToSet(t, append(publicPathsAllowlist, authBypassAllowlist...), "supported public routes")
if stale := setDifference(expectedPublic, supportedPublic); len(stale) > 0 {
t.Fatalf("public route allowlist contains paths not backed by bypass logic: %s", strings.Join(sortedKeys(stale), ", "))
}
}
func parsePublicPaths(t *testing.T) ([]string, []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")
fset := token.NewFileSet()
fileAST, err := parser.ParseFile(fset, routerPath, nil, 0)
if err != nil {
t.Fatalf("parse router.go: %v", err)
}
var literalPaths []string
var dynamicPaths []string
var found bool
ast.Inspect(fileAST, func(node ast.Node) bool {
assign, ok := node.(*ast.AssignStmt)
if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return true
}
ident, ok := assign.Lhs[0].(*ast.Ident)
if !ok || ident.Name != "publicPaths" {
return true
}
comp, ok := assign.Rhs[0].(*ast.CompositeLit)
if !ok {
return true
}
found = true
for _, elt := range comp.Elts {
switch v := elt.(type) {
case *ast.BasicLit:
if v.Kind != token.STRING {
continue
}
unquoted, err := strconv.Unquote(v.Value)
if err != nil {
unquoted = strings.Trim(v.Value, "`\"'")
}
literalPaths = append(literalPaths, unquoted)
case *ast.Ident:
dynamicPaths = append(dynamicPaths, v.Name)
case *ast.SelectorExpr:
dynamicPaths = append(dynamicPaths, selectorName(v))
}
}
return false
})
if !found {
t.Fatalf("publicPaths slice not found in router.go")
}
return literalPaths, dynamicPaths
}
var publicPathsDynamicAllowlist = []string{
"config.DefaultOIDCCallbackPath",
}
var publicPathsAllowlist = []string{
"/api/health",
"/api/security/status",
"/api/security/validate-bootstrap-token",
"/api/security/quick-setup",
"/api/version",
"/api/login",
"/api/oidc/login",
"/install-docker-agent.sh",
"/install-container-agent.sh",
"/download/pulse-docker-agent",
"/install-host-agent.sh",
"/install-host-agent.ps1",
"/uninstall-host-agent.sh",
"/uninstall-host-agent.ps1",
"/download/pulse-host-agent",
"/install.sh",
"/install.ps1",
"/download/pulse-agent",
"/api/agent/version",
"/api/agent/ws",
"/api/server/info",
"/api/install/install-docker.sh",
"/api/ai/oauth/callback",
}