mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-06-10 06:18:15 +00:00
Merge branch 'main' into prepare-caddy-embedding-core
Signed-off-by: Jason Cameron <git@jasoncameron.dev>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user