Merge branch 'main' into prepare-caddy-embedding-core

Signed-off-by: Jason Cameron <git@jasoncameron.dev>
This commit is contained in:
Jason Cameron
2026-05-25 14:31:48 -04:00
committed by GitHub
20 changed files with 868 additions and 399 deletions
+17 -6
View File
@@ -13,11 +13,12 @@ import (
)
type CELChecker struct {
program cel.Program
src string
program cel.Program
src string
subRequestMode bool
}
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELChecker, error) {
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns, subRequestMode bool) (*CELChecker, error) {
env, err := expressions.BotEnvironment(dnsObj)
if err != nil {
return nil, err
@@ -29,8 +30,9 @@ func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELChecker,
}
return &CELChecker{
src: cfg.String(),
program: program,
src: cfg.String(),
program: program,
subRequestMode: subRequestMode,
}, nil
}
@@ -39,7 +41,7 @@ func (cc *CELChecker) Hash() string {
}
func (cc *CELChecker) Check(r *http.Request) (bool, error) {
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r})
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r, cc.subRequestMode})
if err != nil {
return false, err
@@ -54,6 +56,7 @@ func (cc *CELChecker) Check(r *http.Request) (bool, error) {
type CELRequest struct {
*http.Request
subRequestMode bool
}
func (cr *CELRequest) Parent() cel.Activation { return nil }
@@ -71,6 +74,14 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
case "userAgent":
return cr.UserAgent(), true
case "path":
if cr.subRequestMode {
if xou := cr.Header.Get("X-Original-URI"); xou != "" {
return xou, true
}
if xfu := cr.Header.Get("X-Forwarded-Uri"); xfu != "" {
return xfu, true
}
}
return cr.URL.Path, true
case "query":
return expressions.URLValues{Values: cr.URL.Query()}, true
+75 -1
View File
@@ -23,7 +23,7 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
}
checker, err := NewCELChecker(cfg, newTestDNS(t))
checker, err := NewCELChecker(cfg, newTestDNS(t), false)
if err != nil {
t.Fatalf("creating CEL checker failed: %v", err)
}
@@ -42,3 +42,77 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
t.Fatal("expected expression to evaluate true")
}
}
func TestCELChecker_PathWithForwardedUri(t *testing.T) {
tests := []struct {
name string
expression string
xForwardedUri string
urlPath string
subRequestMode bool
want bool
}{
{
name: "path matches X-Forwarded-Uri in subrequest mode",
expression: `path.startsWith("/admin")`,
xForwardedUri: "/admin/secret",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "path with query string",
expression: `path.startsWith("/api/secret")`,
xForwardedUri: "/api/secret?token=abc",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "path falls back to url path when no header",
expression: `path == "/public/page"`,
urlPath: "/public/page",
subRequestMode: true,
want: true,
},
{
name: "non-subrequest mode ignores X-Forwarded-Uri",
expression: `path.startsWith("/admin")`,
xForwardedUri: "/admin/secret",
urlPath: "/public/page",
subRequestMode: false,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.ExpressionOrList{
Expression: tt.expression,
}
checker, err := NewCELChecker(cfg, newTestDNS(t), tt.subRequestMode)
if err != nil {
t.Fatalf("NewCELChecker() error: %v", err)
}
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
if err != nil {
t.Fatalf("http.NewRequest: %v", err)
}
if tt.xForwardedUri != "" {
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
}
got, err := checker.Check(req)
if err != nil {
t.Fatalf("Check() error: %v", err)
}
if got != tt.want {
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q)",
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri)
}
})
}
}
+3
View File
@@ -110,6 +110,9 @@ func NewPathChecker(rexStr string, subrequestMode bool) (checker.Impl, error) {
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
if pc.subRequestMode {
originalUrl := r.Header.Get("X-Original-URI")
if originalUrl == "" {
originalUrl = r.Header.Get("X-Forwarded-Uri")
}
if originalUrl != "" {
if pc.regexp.MatchString(originalUrl) {
return true, nil
+116
View File
@@ -410,3 +410,119 @@ func TestPathChecker_GHSA_6wcg_mqvh_fcvg(t *testing.T) {
})
}
}
func TestPathChecker_XForwardedUri(t *testing.T) {
tests := []struct {
name string
regex string
xForwardedUri string
xOriginalURI string
urlPath string
subRequestMode bool
want bool
}{
{
name: "X-Forwarded-Uri matches regex in subrequest mode",
regex: "^/admin/.*",
xForwardedUri: "/admin/users",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "X-Forwarded-Uri with query string",
regex: "^/admin/.*",
xForwardedUri: "/admin/users?page=1",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "X-Original-URI takes priority over X-Forwarded-Uri",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
xOriginalURI: "/admin/users",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "falls back to X-Forwarded-Uri when no X-Original-URI",
regex: "^/admin/.*",
xForwardedUri: "/admin/dashboard",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "neither header matches, url path matches",
regex: "^/public/.*",
xForwardedUri: "/admin/users",
urlPath: "/public/page",
subRequestMode: true,
want: true,
},
{
name: "nothing matches",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: false,
},
{
name: "non-subrequest mode ignores X-Forwarded-Uri",
regex: "^/admin/.*",
xForwardedUri: "/admin/users",
urlPath: "/public/page",
subRequestMode: false,
want: false,
},
{
name: "non-subrequest mode uses url path",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
urlPath: "/admin/secret",
subRequestMode: false,
want: true,
},
{
name: "empty X-Forwarded-Uri falls back to url path",
regex: "^/check$",
urlPath: "/check",
subRequestMode: true,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pc, err := NewPathChecker(tt.regex, tt.subRequestMode)
if err != nil {
t.Fatalf("NewPathChecker(%q, %v) returned error: %v", tt.regex, tt.subRequestMode, err)
}
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
if err != nil {
t.Fatalf("http.NewRequest: %v", err)
}
if tt.xForwardedUri != "" {
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
}
if tt.xOriginalURI != "" {
req.Header.Set("X-Original-URI", tt.xOriginalURI)
}
got, err := pc.Check(req)
if err != nil {
t.Fatalf("Check() unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q, X-Original-URI=%q)",
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri, tt.xOriginalURI)
}
})
}
}
+1 -1
View File
@@ -170,7 +170,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
}
if b.Expression != nil {
c, err := NewCELChecker(b.Expression, result.Dns)
c, err := NewCELChecker(b.Expression, result.Dns, subrequestMode)
if err != nil {
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
} else {