mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-09-09 20:55:41 -04:00
refactor: move CEL checker to its own package
Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
590d8303ad
commit
e98d749bf2
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/data"
|
"github.com/TecharoHQ/anubis/data"
|
||||||
"github.com/TecharoHQ/anubis/internal"
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
libanubis "github.com/TecharoHQ/anubis/lib"
|
libanubis "github.com/TecharoHQ/anubis/lib"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/headerexists"
|
||||||
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||||
"github.com/TecharoHQ/anubis/lib/thoth"
|
"github.com/TecharoHQ/anubis/lib/thoth"
|
||||||
@ -323,7 +324,7 @@ func main() {
|
|||||||
if *debugBenchmarkJS {
|
if *debugBenchmarkJS {
|
||||||
policy.Bots = []botPolicy.Bot{{
|
policy.Bots = []botPolicy.Bot{{
|
||||||
Name: "",
|
Name: "",
|
||||||
Rules: botPolicy.NewHeaderExistsChecker("User-Agent"),
|
Rules: headerexists.New("User-Agent"),
|
||||||
Action: config.RuleBenchmark,
|
Action: config.RuleBenchmark,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||||
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
@ -37,11 +38,11 @@ type RobotsRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AnubisRule struct {
|
type AnubisRule struct {
|
||||||
Expression *config.ExpressionOrList `yaml:"expression,omitempty" json:"expression,omitempty"`
|
Expression *expression.Config `yaml:"expression,omitempty" json:"expression,omitempty"`
|
||||||
Challenge *config.ChallengeRules `yaml:"challenge,omitempty" json:"challenge,omitempty"`
|
Challenge *config.ChallengeRules `yaml:"challenge,omitempty" json:"challenge,omitempty"`
|
||||||
Weight *config.Weight `yaml:"weight,omitempty" json:"weight,omitempty"`
|
Weight *config.Weight `yaml:"weight,omitempty" json:"weight,omitempty"`
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Action string `yaml:"action" json:"action"`
|
Action string `yaml:"action" json:"action"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -224,11 +225,11 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userAgent == "*" {
|
if userAgent == "*" {
|
||||||
rule.Expression = &config.ExpressionOrList{
|
rule.Expression = &expression.Config{
|
||||||
All: []string{"true"}, // Always applies
|
All: []string{"true"}, // Always applies
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rule.Expression = &config.ExpressionOrList{
|
rule.Expression = &expression.Config{
|
||||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,11 +250,11 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
|||||||
rule.Name = fmt.Sprintf("%s-global-restriction-%d", *policyName, ruleCounter)
|
rule.Name = fmt.Sprintf("%s-global-restriction-%d", *policyName, ruleCounter)
|
||||||
rule.Action = "WEIGH"
|
rule.Action = "WEIGH"
|
||||||
rule.Weight = &config.Weight{Adjust: 20} // Increase difficulty significantly
|
rule.Weight = &config.Weight{Adjust: 20} // Increase difficulty significantly
|
||||||
rule.Expression = &config.ExpressionOrList{
|
rule.Expression = &expression.Config{
|
||||||
All: []string{"true"}, // Always applies
|
All: []string{"true"}, // Always applies
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rule.Expression = &config.ExpressionOrList{
|
rule.Expression = &expression.Config{
|
||||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,7 +286,7 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
|||||||
pathCondition := buildPathCondition(disallow)
|
pathCondition := buildPathCondition(disallow)
|
||||||
conditions = append(conditions, pathCondition)
|
conditions = append(conditions, pathCondition)
|
||||||
|
|
||||||
rule.Expression = &config.ExpressionOrList{
|
rule.Expression = &expression.Config{
|
||||||
All: conditions,
|
All: conditions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,7 @@ import (
|
|||||||
_ "github.com/TecharoHQ/anubis/lib/checker/all"
|
_ "github.com/TecharoHQ/anubis/lib/checker/all"
|
||||||
|
|
||||||
// challenge implementations
|
// challenge implementations
|
||||||
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
|
_ "github.com/TecharoHQ/anubis/lib/challenge/all"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
6
lib/challenge/all/all.go
Normal file
6
lib/challenge/all/all.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
|
||||||
|
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
|
||||||
|
)
|
@ -2,6 +2,7 @@
|
|||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/checker/headerexists"
|
_ "github.com/TecharoHQ/anubis/lib/checker/headerexists"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
_ "github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/checker/path"
|
_ "github.com/TecharoHQ/anubis/lib/checker/path"
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package policy
|
package expression
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/internal"
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
|
||||||
"github.com/TecharoHQ/anubis/lib/policy/expressions"
|
"github.com/TecharoHQ/anubis/lib/policy/expressions"
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CELChecker struct {
|
type Checker struct {
|
||||||
program cel.Program
|
program cel.Program
|
||||||
src string
|
src string
|
||||||
|
hash string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCELChecker(cfg *config.ExpressionOrList) (*CELChecker, error) {
|
func New(cfg *Config) (*Checker, error) {
|
||||||
env, err := expressions.BotEnvironment()
|
env, err := expressions.BotEnvironment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -27,17 +27,18 @@ func NewCELChecker(cfg *config.ExpressionOrList) (*CELChecker, error) {
|
|||||||
return nil, fmt.Errorf("can't compile CEL program: %w", err)
|
return nil, fmt.Errorf("can't compile CEL program: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CELChecker{
|
return &Checker{
|
||||||
src: cfg.String(),
|
src: cfg.String(),
|
||||||
|
hash: internal.FastHash(cfg.String()),
|
||||||
program: program,
|
program: program,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *CELChecker) Hash() string {
|
func (cc *Checker) Hash() string {
|
||||||
return internal.FastHash(cc.src)
|
return cc.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *CELChecker) Check(r *http.Request) (bool, error) {
|
func (cc *Checker) Check(r *http.Request) (bool, error) {
|
||||||
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r})
|
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,4 +1,4 @@
|
|||||||
package config
|
package expression
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -9,18 +9,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrExpressionOrListMustBeStringOrObject = errors.New("config: this must be a string or an object")
|
ErrExpressionOrListMustBeStringOrObject = errors.New("expression: this must be a string or an object")
|
||||||
ErrExpressionEmpty = errors.New("config: this expression is empty")
|
ErrExpressionEmpty = errors.New("expression: this expression is empty")
|
||||||
ErrExpressionCantHaveBoth = errors.New("config: expression block can't contain multiple expression types")
|
ErrExpressionCantHaveBoth = errors.New("expression: expression block can't contain multiple expression types")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExpressionOrList struct {
|
type Config struct {
|
||||||
Expression string `json:"-" yaml:"-"`
|
Expression string `json:"-" yaml:"-"`
|
||||||
All []string `json:"all,omitempty" yaml:"all,omitempty"`
|
All []string `json:"all,omitempty" yaml:"all,omitempty"`
|
||||||
Any []string `json:"any,omitempty" yaml:"any,omitempty"`
|
Any []string `json:"any,omitempty" yaml:"any,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol ExpressionOrList) String() string {
|
func (eol Config) String() string {
|
||||||
switch {
|
switch {
|
||||||
case len(eol.Expression) != 0:
|
case len(eol.Expression) != 0:
|
||||||
return eol.Expression
|
return eol.Expression
|
||||||
@ -46,7 +46,7 @@ func (eol ExpressionOrList) String() string {
|
|||||||
panic("this should not happen")
|
panic("this should not happen")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool {
|
func (eol Config) Equal(rhs *Config) bool {
|
||||||
if eol.Expression != rhs.Expression {
|
if eol.Expression != rhs.Expression {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol *ExpressionOrList) MarshalYAML() (any, error) {
|
func (eol *Config) MarshalYAML() (any, error) {
|
||||||
switch {
|
switch {
|
||||||
case len(eol.All) == 1 && len(eol.Any) == 0:
|
case len(eol.All) == 1 && len(eol.Any) == 0:
|
||||||
eol.Expression = eol.All[0]
|
eol.Expression = eol.All[0]
|
||||||
@ -76,11 +76,11 @@ func (eol *ExpressionOrList) MarshalYAML() (any, error) {
|
|||||||
return eol.Expression, nil
|
return eol.Expression, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawExpressionOrList ExpressionOrList
|
type RawExpressionOrList Config
|
||||||
return RawExpressionOrList(*eol), nil
|
return RawExpressionOrList(*eol), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol *ExpressionOrList) MarshalJSON() ([]byte, error) {
|
func (eol *Config) MarshalJSON() ([]byte, error) {
|
||||||
switch {
|
switch {
|
||||||
case len(eol.All) == 1 && len(eol.Any) == 0:
|
case len(eol.All) == 1 && len(eol.Any) == 0:
|
||||||
eol.Expression = eol.All[0]
|
eol.Expression = eol.All[0]
|
||||||
@ -94,17 +94,17 @@ func (eol *ExpressionOrList) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(string(eol.Expression))
|
return json.Marshal(string(eol.Expression))
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawExpressionOrList ExpressionOrList
|
type RawExpressionOrList Config
|
||||||
val := RawExpressionOrList(*eol)
|
val := RawExpressionOrList(*eol)
|
||||||
return json.Marshal(val)
|
return json.Marshal(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol *ExpressionOrList) UnmarshalJSON(data []byte) error {
|
func (eol *Config) UnmarshalJSON(data []byte) error {
|
||||||
switch string(data[0]) {
|
switch string(data[0]) {
|
||||||
case `"`: // string
|
case `"`: // string
|
||||||
return json.Unmarshal(data, &eol.Expression)
|
return json.Unmarshal(data, &eol.Expression)
|
||||||
case "{": // object
|
case "{": // object
|
||||||
type RawExpressionOrList ExpressionOrList
|
type RawExpressionOrList Config
|
||||||
var val RawExpressionOrList
|
var val RawExpressionOrList
|
||||||
if err := json.Unmarshal(data, &val); err != nil {
|
if err := json.Unmarshal(data, &val); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -118,7 +118,7 @@ func (eol *ExpressionOrList) UnmarshalJSON(data []byte) error {
|
|||||||
return ErrExpressionOrListMustBeStringOrObject
|
return ErrExpressionOrListMustBeStringOrObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eol *ExpressionOrList) Valid() error {
|
func (eol *Config) Valid() error {
|
||||||
if eol.Expression == "" && len(eol.All) == 0 && len(eol.Any) == 0 {
|
if eol.Expression == "" && len(eol.All) == 0 && len(eol.Any) == 0 {
|
||||||
return ErrExpressionEmpty
|
return ErrExpressionEmpty
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package config
|
package expression
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -12,13 +12,13 @@ import (
|
|||||||
func TestExpressionOrListMarshalJSON(t *testing.T) {
|
func TestExpressionOrListMarshalJSON(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
input *ExpressionOrList
|
input *Config
|
||||||
output []byte
|
output []byte
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single expression",
|
name: "single expression",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Expression: "true",
|
Expression: "true",
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -26,7 +26,7 @@ func TestExpressionOrListMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all",
|
name: "all",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
All: []string{"true", "true"},
|
All: []string{"true", "true"},
|
||||||
},
|
},
|
||||||
output: []byte(`{"all":["true","true"]}`),
|
output: []byte(`{"all":["true","true"]}`),
|
||||||
@ -34,7 +34,7 @@ func TestExpressionOrListMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all one",
|
name: "all one",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
All: []string{"true"},
|
All: []string{"true"},
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -42,7 +42,7 @@ func TestExpressionOrListMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any",
|
name: "any",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Any: []string{"true", "false"},
|
Any: []string{"true", "false"},
|
||||||
},
|
},
|
||||||
output: []byte(`{"any":["true","false"]}`),
|
output: []byte(`{"any":["true","false"]}`),
|
||||||
@ -50,7 +50,7 @@ func TestExpressionOrListMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any one",
|
name: "any one",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Any: []string{"true"},
|
Any: []string{"true"},
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -75,13 +75,13 @@ func TestExpressionOrListMarshalJSON(t *testing.T) {
|
|||||||
func TestExpressionOrListMarshalYAML(t *testing.T) {
|
func TestExpressionOrListMarshalYAML(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
input *ExpressionOrList
|
input *Config
|
||||||
output []byte
|
output []byte
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single expression",
|
name: "single expression",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Expression: "true",
|
Expression: "true",
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -89,7 +89,7 @@ func TestExpressionOrListMarshalYAML(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all",
|
name: "all",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
All: []string{"true", "true"},
|
All: []string{"true", "true"},
|
||||||
},
|
},
|
||||||
output: []byte(`all:
|
output: []byte(`all:
|
||||||
@ -99,7 +99,7 @@ func TestExpressionOrListMarshalYAML(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all one",
|
name: "all one",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
All: []string{"true"},
|
All: []string{"true"},
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -107,7 +107,7 @@ func TestExpressionOrListMarshalYAML(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any",
|
name: "any",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Any: []string{"true", "false"},
|
Any: []string{"true", "false"},
|
||||||
},
|
},
|
||||||
output: []byte(`any:
|
output: []byte(`any:
|
||||||
@ -117,7 +117,7 @@ func TestExpressionOrListMarshalYAML(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any one",
|
name: "any one",
|
||||||
input: &ExpressionOrList{
|
input: &Config{
|
||||||
Any: []string{"true"},
|
Any: []string{"true"},
|
||||||
},
|
},
|
||||||
output: []byte(`"true"`),
|
output: []byte(`"true"`),
|
||||||
@ -145,14 +145,14 @@ func TestExpressionOrListUnmarshalJSON(t *testing.T) {
|
|||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
err error
|
err error
|
||||||
validErr error
|
validErr error
|
||||||
result *ExpressionOrList
|
result *Config
|
||||||
name string
|
name string
|
||||||
inp string
|
inp string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple",
|
name: "simple",
|
||||||
inp: `"\"User-Agent\" in headers"`,
|
inp: `"\"User-Agent\" in headers"`,
|
||||||
result: &ExpressionOrList{
|
result: &Config{
|
||||||
Expression: `"User-Agent" in headers`,
|
Expression: `"User-Agent" in headers`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -161,7 +161,7 @@ func TestExpressionOrListUnmarshalJSON(t *testing.T) {
|
|||||||
inp: `{
|
inp: `{
|
||||||
"all": ["\"User-Agent\" in headers"]
|
"all": ["\"User-Agent\" in headers"]
|
||||||
}`,
|
}`,
|
||||||
result: &ExpressionOrList{
|
result: &Config{
|
||||||
All: []string{
|
All: []string{
|
||||||
`"User-Agent" in headers`,
|
`"User-Agent" in headers`,
|
||||||
},
|
},
|
||||||
@ -172,7 +172,7 @@ func TestExpressionOrListUnmarshalJSON(t *testing.T) {
|
|||||||
inp: `{
|
inp: `{
|
||||||
"any": ["\"User-Agent\" in headers"]
|
"any": ["\"User-Agent\" in headers"]
|
||||||
}`,
|
}`,
|
||||||
result: &ExpressionOrList{
|
result: &Config{
|
||||||
Any: []string{
|
Any: []string{
|
||||||
`"User-Agent" in headers`,
|
`"User-Agent" in headers`,
|
||||||
},
|
},
|
||||||
@ -195,7 +195,7 @@ func TestExpressionOrListUnmarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var eol ExpressionOrList
|
var eol Config
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(tt.inp), &eol); !errors.Is(err, tt.err) {
|
if err := json.Unmarshal([]byte(tt.inp), &eol); !errors.Is(err, tt.err) {
|
||||||
t.Errorf("wanted unmarshal error: %v but got: %v", tt.err, err)
|
t.Errorf("wanted unmarshal error: %v but got: %v", tt.err, err)
|
||||||
@ -217,40 +217,40 @@ func TestExpressionOrListUnmarshalJSON(t *testing.T) {
|
|||||||
func TestExpressionOrListString(t *testing.T) {
|
func TestExpressionOrListString(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
in ExpressionOrList
|
in Config
|
||||||
out string
|
out string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single expression",
|
name: "single expression",
|
||||||
in: ExpressionOrList{
|
in: Config{
|
||||||
Expression: "true",
|
Expression: "true",
|
||||||
},
|
},
|
||||||
out: "true",
|
out: "true",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all",
|
name: "all",
|
||||||
in: ExpressionOrList{
|
in: Config{
|
||||||
All: []string{"true"},
|
All: []string{"true"},
|
||||||
},
|
},
|
||||||
out: "( true )",
|
out: "( true )",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all with &&",
|
name: "all with &&",
|
||||||
in: ExpressionOrList{
|
in: Config{
|
||||||
All: []string{"true", "true"},
|
All: []string{"true", "true"},
|
||||||
},
|
},
|
||||||
out: "( true ) && ( true )",
|
out: "( true ) && ( true )",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any",
|
name: "any",
|
||||||
in: ExpressionOrList{
|
in: Config{
|
||||||
All: []string{"true"},
|
All: []string{"true"},
|
||||||
},
|
},
|
||||||
out: "( true )",
|
out: "( true )",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "any with ||",
|
name: "any with ||",
|
||||||
in: ExpressionOrList{
|
in: Config{
|
||||||
Any: []string{"true", "true"},
|
Any: []string{"true", "true"},
|
||||||
},
|
},
|
||||||
out: "( true ) || ( true )",
|
out: "( true ) || ( true )",
|
43
lib/checker/expression/factory.go
Normal file
43
lib/checker/expression/factory.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package expression
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
checker.Register("expression", Factory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Factory struct{}
|
||||||
|
|
||||||
|
func (f Factory) Build(ctx context.Context, data json.RawMessage) (checker.Interface, error) {
|
||||||
|
var fc = &Config{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(data), fc); err != nil {
|
||||||
|
return nil, errors.Join(checker.ErrUnparseableConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fc.Valid(); err != nil {
|
||||||
|
return nil, errors.Join(checker.ErrInvalidConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Factory) Valid(ctx context.Context, data json.RawMessage) error {
|
||||||
|
var fc = &Config{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(data), fc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fc.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/data"
|
"github.com/TecharoHQ/anubis/data"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
"github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/path"
|
"github.com/TecharoHQ/anubis/lib/checker/path"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/remoteaddress"
|
"github.com/TecharoHQ/anubis/lib/checker/remoteaddress"
|
||||||
@ -58,15 +59,15 @@ func (r Rule) Valid() error {
|
|||||||
const DefaultAlgorithm = "fast"
|
const DefaultAlgorithm = "fast"
|
||||||
|
|
||||||
type BotConfig struct {
|
type BotConfig struct {
|
||||||
UserAgentRegex *string `json:"user_agent_regex,omitempty" yaml:"user_agent_regex,omitempty"`
|
UserAgentRegex *string `json:"user_agent_regex,omitempty" yaml:"user_agent_regex,omitempty"`
|
||||||
PathRegex *string `json:"path_regex,omitempty" yaml:"path_regex,omitempty"`
|
PathRegex *string `json:"path_regex,omitempty" yaml:"path_regex,omitempty"`
|
||||||
HeadersRegex map[string]string `json:"headers_regex,omitempty" yaml:"headers_regex,omitempty"`
|
HeadersRegex map[string]string `json:"headers_regex,omitempty" yaml:"headers_regex,omitempty"`
|
||||||
Expression *ExpressionOrList `json:"expression,omitempty" yaml:"expression,omitempty"`
|
Expression *expression.Config `json:"expression,omitempty" yaml:"expression,omitempty"`
|
||||||
Challenge *ChallengeRules `json:"challenge,omitempty" yaml:"challenge,omitempty"`
|
Challenge *ChallengeRules `json:"challenge,omitempty" yaml:"challenge,omitempty"`
|
||||||
Weight *Weight `json:"weight,omitempty" yaml:"weight,omitempty"`
|
Weight *Weight `json:"weight,omitempty" yaml:"weight,omitempty"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Action Rule `json:"action" yaml:"action"`
|
Action Rule `json:"action" yaml:"action"`
|
||||||
RemoteAddr []string `json:"remote_addresses,omitempty" yaml:"remote_addresses,omitempty"`
|
RemoteAddr []string `json:"remote_addresses,omitempty" yaml:"remote_addresses,omitempty"`
|
||||||
|
|
||||||
// Thoth features
|
// Thoth features
|
||||||
GeoIP *GeoIP `json:"geoip,omitempty"`
|
GeoIP *GeoIP `json:"geoip,omitempty"`
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -17,7 +18,7 @@ var (
|
|||||||
DefaultThresholds = []Threshold{
|
DefaultThresholds = []Threshold{
|
||||||
{
|
{
|
||||||
Name: "legacy-anubis-behaviour",
|
Name: "legacy-anubis-behaviour",
|
||||||
Expression: &ExpressionOrList{
|
Expression: &expression.Config{
|
||||||
Expression: "weight > 0",
|
Expression: "weight > 0",
|
||||||
},
|
},
|
||||||
Action: RuleChallenge,
|
Action: RuleChallenge,
|
||||||
@ -31,10 +32,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Threshold struct {
|
type Threshold struct {
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Expression *ExpressionOrList `json:"expression" yaml:"expression"`
|
Expression *expression.Config `json:"expression" yaml:"expression"`
|
||||||
Action Rule `json:"action" yaml:"action"`
|
Action Rule `json:"action" yaml:"action"`
|
||||||
Challenge *ChallengeRules `json:"challenge" yaml:"challenge"`
|
Challenge *ChallengeRules `json:"challenge" yaml:"challenge"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Threshold) Valid() error {
|
func (t Threshold) Valid() error {
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestThresholdValid(t *testing.T) {
|
func TestThresholdValid(t *testing.T) {
|
||||||
@ -18,7 +20,7 @@ func TestThresholdValid(t *testing.T) {
|
|||||||
name: "basic allow",
|
name: "basic allow",
|
||||||
input: &Threshold{
|
input: &Threshold{
|
||||||
Name: "basic-allow",
|
Name: "basic-allow",
|
||||||
Expression: &ExpressionOrList{Expression: "true"},
|
Expression: &expression.Config{Expression: "true"},
|
||||||
Action: RuleAllow,
|
Action: RuleAllow,
|
||||||
},
|
},
|
||||||
err: nil,
|
err: nil,
|
||||||
@ -27,7 +29,7 @@ func TestThresholdValid(t *testing.T) {
|
|||||||
name: "basic challenge",
|
name: "basic challenge",
|
||||||
input: &Threshold{
|
input: &Threshold{
|
||||||
Name: "basic-challenge",
|
Name: "basic-challenge",
|
||||||
Expression: &ExpressionOrList{Expression: "true"},
|
Expression: &expression.Config{Expression: "true"},
|
||||||
Action: RuleChallenge,
|
Action: RuleChallenge,
|
||||||
Challenge: &ChallengeRules{
|
Challenge: &ChallengeRules{
|
||||||
Algorithm: "fast",
|
Algorithm: "fast",
|
||||||
@ -50,9 +52,9 @@ func TestThresholdValid(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid expression",
|
name: "invalid expression",
|
||||||
input: &Threshold{
|
input: &Threshold{
|
||||||
Expression: &ExpressionOrList{},
|
Expression: &expression.Config{},
|
||||||
},
|
},
|
||||||
err: ErrExpressionEmpty,
|
err: expression.ErrExpressionEmpty,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid action",
|
name: "invalid action",
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/lib/checker"
|
"github.com/TecharoHQ/anubis/lib/checker"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/checker/expression"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
"github.com/TecharoHQ/anubis/lib/checker/headermatches"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/path"
|
"github.com/TecharoHQ/anubis/lib/checker/path"
|
||||||
"github.com/TecharoHQ/anubis/lib/checker/remoteaddress"
|
"github.com/TecharoHQ/anubis/lib/checker/remoteaddress"
|
||||||
@ -115,7 +116,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.Expression != nil {
|
if b.Expression != nil {
|
||||||
c, err := NewCELChecker(b.Expression)
|
c, err := expression.New(b.Expression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
|
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user