diff --git a/go.mod b/go.mod index 02edab4..509ce14 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 toolchain go1.24.2 require ( + codeberg.org/gone/http-cel v1.0.0 codeberg.org/meta/gzipped/v2 v2.0.0-20231111234332-aa70c3194756 github.com/alphadose/haxmap v1.4.1 github.com/go-jose/go-jose/v4 v4.1.0 @@ -26,9 +27,9 @@ require ( github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/kevinpollet/nego v0.0.0-20211010160919-a65cd48cee43 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/net v0.39.0 // indirect diff --git a/go.sum b/go.sum index 5e4c585..b1e74f6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +codeberg.org/gone/http-cel v1.0.0 h1:flEv/KzEye4W7vjwkdAkwo7VCbuj9xZLjyTn/rjWFDQ= +codeberg.org/gone/http-cel v1.0.0/go.mod h1:uRkxygsQp5EFE3e9dRkJ4HK453G5YZDHCq9DEG5CoDw= codeberg.org/meta/gzipped/v2 v2.0.0-20231111234332-aa70c3194756 h1:bDqEUEYt4UJy8mfLCZeJuXx+xNJvdqTbkE4Ci11NQYU= codeberg.org/meta/gzipped/v2 v2.0.0-20231111234332-aa70c3194756/go.mod h1:aJ/ghJW7viYfwZ6OizDst+uJgbb6r/Hvoqhmi1OPTTw= github.com/alphadose/haxmap v1.4.1 h1:VtD6VCxUkjNIfJk/aWdYFfOzrRddDFjmvmRmILg7x8Q= @@ -39,12 +41,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/lib/challenge/data.go b/lib/challenge/data.go index c5c10dd..6ae7586 100644 --- a/lib/challenge/data.go +++ b/lib/challenge/data.go @@ -1,13 +1,13 @@ package challenge import ( + http_cel "codeberg.org/gone/http-cel" "context" "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "fmt" - "git.gammaspectra.live/git/go-away/lib/condition" "git.gammaspectra.live/git/go-away/utils" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" @@ -73,8 +73,8 @@ func CreateRequestData(r *http.Request, state StateInterface) (*http.Request, *R } } - data.query = condition.NewValuesMap(r.URL.Query()) - data.header = condition.NewMIMEMap(textproto.MIMEHeader(r.Header)) + data.query = http_cel.NewValuesMap(r.URL.Query()) + data.header = http_cel.NewMIMEMap(textproto.MIMEHeader(r.Header)) sum := sha256.New() sum.Write([]byte(r.Host)) diff --git a/lib/challenge/register.go b/lib/challenge/register.go index 054248a..449b975 100644 --- a/lib/challenge/register.go +++ b/lib/challenge/register.go @@ -2,10 +2,10 @@ package challenge import ( "bytes" + http_cel "codeberg.org/gone/http-cel" "crypto/ed25519" "errors" "fmt" - "git.gammaspectra.live/git/go-away/lib/condition" "git.gammaspectra.live/git/go-away/lib/policy" "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4/jwt" @@ -67,11 +67,11 @@ func (r Register) Create(state StateInterface, name string, pol policy.Challenge } if len(conditions) > 0 { - ast, err := condition.FromStrings(state.ProgramEnv(), condition.OperatorOr, conditions...) + ast, err := http_cel.NewAst(state.ProgramEnv(), http_cel.OperatorOr, conditions...) if err != nil { return nil, 0, fmt.Errorf("error compiling conditions: %v", err) } - reg.Condition, err = condition.Program(state.ProgramEnv(), ast) + reg.Condition, err = http_cel.ProgramAst(state.ProgramEnv(), ast) if err != nil { return nil, 0, fmt.Errorf("error compiling program: %v", err) } diff --git a/lib/condition/condition.go b/lib/condition/condition.go deleted file mode 100644 index 44f38c5..0000000 --- a/lib/condition/condition.go +++ /dev/null @@ -1,177 +0,0 @@ -package condition - -import ( - "fmt" - "github.com/google/cel-go/cel" - "github.com/google/cel-go/common/types" - "github.com/google/cel-go/common/types/ref" - "github.com/google/cel-go/ext" - "github.com/yl2chen/cidranger" - "log/slog" - "net" - "strings" -) - -type Condition struct { - Expression *cel.Ast -} - -const ( - OperatorOr = "||" - OperatorAnd = "&&" -) - -func NewRulesEnvironment(networks map[string]cidranger.Ranger) (*cel.Env, error) { - - return cel.NewEnv( - ext.Strings( - ext.StringsLocale("en_US"), - ext.StringsValidateFormatCalls(true), - ), - cel.DefaultUTCTimeZone(true), - //TODO: custom type for remoteAddress - cel.Variable("remoteAddress", cel.BytesType), - cel.Variable("host", cel.StringType), - cel.Variable("method", cel.StringType), - cel.Variable("userAgent", cel.StringType), - cel.Variable("path", cel.StringType), - cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)), - cel.Variable("fp", cel.MapType(cel.StringType, cel.StringType)), - // http.Header - cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)), - //TODO: dynamic type? - cel.Function("inDNSBL", - cel.Overload("inDNSBL_ip", - []*cel.Type{cel.AnyType}, - cel.BoolType, - cel.UnaryBinding(func(val ref.Val) ref.Val { - slog.Error("inDNSBL function has been deprecated, replace with dnsbl challenge") - return types.Bool(false) - }), - ), - ), - cel.Function("network", - cel.MemberOverload("netIP_network_string", - []*cel.Type{cel.BytesType, cel.StringType}, - cel.BoolType, - cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val { - var ip net.IP - switch v := lhs.Value().(type) { - case []byte: - ip = v - case net.IP: - ip = v - } - - if ip == nil { - panic(fmt.Errorf("invalid ip %v", lhs.Value())) - } - - val, ok := rhs.Value().(string) - if !ok { - panic(fmt.Errorf("invalid network value %v", rhs.Value())) - } - - network, ok := networks[val] - if !ok { - _, ipNet, err := net.ParseCIDR(val) - if err != nil { - panic("network not found") - } - return types.Bool(ipNet.Contains(ip)) - } else { - ok, err := network.Contains(ip) - if err != nil { - panic(err) - } - return types.Bool(ok) - } - }), - ), - ), - - cel.Function("inNetwork", - cel.Overload("inNetwork_string_ip", - []*cel.Type{cel.StringType, cel.BytesType}, - cel.BoolType, - cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val { - var ip net.IP - switch v := rhs.Value().(type) { - case []byte: - ip = v - case net.IP: - ip = v - } - - if ip == nil { - panic(fmt.Errorf("invalid ip %v", rhs.Value())) - } - - val, ok := lhs.Value().(string) - if !ok { - panic(fmt.Errorf("invalid value %v", lhs.Value())) - } - slog.Debug(fmt.Sprintf("inNetwork function has been deprecated and will be removed in a future release, use remoteAddress.network(\"%s\") instead", val)) - - network, ok := networks[val] - if !ok { - _, ipNet, err := net.ParseCIDR(val) - if err != nil { - panic("network not found") - } - return types.Bool(ipNet.Contains(ip)) - } else { - ok, err := network.Contains(ip) - if err != nil { - panic(err) - } - return types.Bool(ok) - } - }), - ), - ), - ) -} - -func Program(env *cel.Env, ast *cel.Ast) (cel.Program, error) { - return env.Program(ast, - cel.EvalOptions(cel.OptOptimize), - ) -} - -func FromStrings(env *cel.Env, operator string, conditions ...string) (*cel.Ast, error) { - var asts []*cel.Ast - for _, c := range conditions { - ast, issues := env.Compile(c) - if issues != nil && issues.Err() != nil { - return nil, fmt.Errorf("condition %s: %s", issues.Err(), c) - } - asts = append(asts, ast) - } - - return Merge(env, operator, asts...) -} - -func Merge(env *cel.Env, operator string, conditions ...*cel.Ast) (*cel.Ast, error) { - if len(conditions) == 0 { - return nil, nil - } else if len(conditions) == 1 { - return conditions[0], nil - } - var asts []string - for _, c := range conditions { - ast, err := cel.AstToString(c) - if err != nil { - return nil, err - } - asts = append(asts, "("+ast+")") - } - - condition := strings.Join(asts, " "+operator+" ") - ast, issues := env.Compile(condition) - if issues != nil && issues.Err() != nil { - return nil, issues.Err() - } - - return ast, nil -} diff --git a/lib/condition/map.go b/lib/condition/map.go deleted file mode 100644 index 7bb90d8..0000000 --- a/lib/condition/map.go +++ /dev/null @@ -1,158 +0,0 @@ -package condition - -import ( - "fmt" - "github.com/google/cel-go/common/types" - "github.com/google/cel-go/common/types/ref" - "github.com/google/cel-go/common/types/traits" - "net/textproto" - "reflect" - "strings" -) - -type mimeLike struct { - m textproto.MIMEHeader -} - -func (a mimeLike) ConvertToNative(typeDesc reflect.Type) (any, error) { - return nil, fmt.Errorf("type conversion error from map to '%v'", typeDesc) -} - -func (a mimeLike) ConvertToType(typeVal ref.Type) ref.Val { - switch typeVal { - case types.MapType: - return a - case types.TypeType: - return types.MapType - } - return types.NewErr("type conversion error from '%s' to '%s'", types.MapType, typeVal) -} - -func (a mimeLike) Equal(other ref.Val) ref.Val { - return types.Bool(false) -} - -func (a mimeLike) Type() ref.Type { - return types.MapType -} - -func (a mimeLike) Value() any { - return a.m -} - -func (a mimeLike) Contains(key ref.Val) ref.Val { - _, found := a.Find(key) - return types.Bool(found) -} - -func (a mimeLike) Get(key ref.Val) ref.Val { - v, found := a.Find(key) - if !found { - return types.ValOrErr(v, "no such key: %v", key) - } - return v -} - -func (a mimeLike) Iterator() traits.Iterator { - panic("implement me") -} - -func (a mimeLike) IsZeroValue() bool { - return len(a.m) == 0 -} - -func (a mimeLike) Size() ref.Val { - return types.Int(len(a.m)) -} - -func (a mimeLike) Find(key ref.Val) (ref.Val, bool) { - k, ok := key.(types.String) - if !ok { - return nil, false - } - - return singleVal(a.m.Values(string(k)), true) -} - -type valuesLike struct { - m map[string][]string -} - -func (a valuesLike) ConvertToNative(typeDesc reflect.Type) (any, error) { - return nil, fmt.Errorf("type conversion error from map to '%v'", typeDesc) -} - -func (a valuesLike) ConvertToType(typeVal ref.Type) ref.Val { - switch typeVal { - case types.MapType: - return a - case types.TypeType: - return types.MapType - } - return types.NewErr("type conversion error from '%s' to '%s'", types.MapType, typeVal) -} - -func (a valuesLike) Equal(other ref.Val) ref.Val { - return types.Bool(false) -} - -func (a valuesLike) Type() ref.Type { - return types.MapType -} - -func (a valuesLike) Value() any { - return a.m -} - -func (a valuesLike) Contains(key ref.Val) ref.Val { - _, found := a.Find(key) - return types.Bool(found) -} - -func (a valuesLike) Get(key ref.Val) ref.Val { - v, found := a.Find(key) - if !found { - return types.ValOrErr(v, "no such key: %v", key) - } - return v -} - -func (a valuesLike) Iterator() traits.Iterator { - panic("implement me") -} - -func (a valuesLike) IsZeroValue() bool { - return len(a.m) == 0 -} - -func (a valuesLike) Size() ref.Val { - return types.Int(len(a.m)) -} - -func (a valuesLike) Find(key ref.Val) (ref.Val, bool) { - k, ok := key.(types.String) - if !ok { - return nil, false - } - - val, ok := a.m[string(k)] - return singleVal(val, ok) -} - -func singleVal(values []string, ok bool) (ref.Val, bool) { - if len(values) == 0 || !ok { - return nil, false - } - if len(values) > 1 { - return types.String(strings.Join(values, ",")), true - } - return types.String(values[0]), true -} - -func NewMIMEMap(m textproto.MIMEHeader) traits.Mapper { - return mimeLike{m: m} -} - -func NewValuesMap(m map[string][]string) traits.Mapper { - return valuesLike{m: m} -} diff --git a/lib/conditions.go b/lib/conditions.go index 7916832..5e94b05 100644 --- a/lib/conditions.go +++ b/lib/conditions.go @@ -1,11 +1,111 @@ package lib import ( - "git.gammaspectra.live/git/go-away/lib/condition" + http_cel "codeberg.org/gone/http-cel" + "fmt" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "log/slog" + "net" ) func (state *State) initConditions() (err error) { - state.programEnv, err = condition.NewRulesEnvironment(state.networks) + state.programEnv, err = http_cel.NewEnvironment( + + cel.Variable("fp", cel.MapType(cel.StringType, cel.StringType)), + cel.Function("inDNSBL", + cel.Overload("inDNSBL_ip", + []*cel.Type{cel.AnyType}, + cel.BoolType, + cel.UnaryBinding(func(val ref.Val) ref.Val { + slog.Error("inDNSBL function has been deprecated, replace with dnsbl challenge") + return types.Bool(false) + }), + ), + ), + + cel.Function("network", + cel.MemberOverload("netIP_network_string", + []*cel.Type{cel.BytesType, cel.StringType}, + cel.BoolType, + cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val { + var ip net.IP + switch v := lhs.Value().(type) { + case []byte: + ip = v + case net.IP: + ip = v + } + + if ip == nil { + panic(fmt.Errorf("invalid ip %v", lhs.Value())) + } + + val, ok := rhs.Value().(string) + if !ok { + panic(fmt.Errorf("invalid network value %v", rhs.Value())) + } + + network, ok := state.networks[val] + if !ok { + _, ipNet, err := net.ParseCIDR(val) + if err != nil { + panic("network not found") + } + return types.Bool(ipNet.Contains(ip)) + } else { + ok, err := network.Contains(ip) + if err != nil { + panic(err) + } + return types.Bool(ok) + } + }), + ), + ), + + cel.Function("inNetwork", + cel.Overload("inNetwork_string_ip", + []*cel.Type{cel.StringType, cel.BytesType}, + cel.BoolType, + cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val { + var ip net.IP + switch v := rhs.Value().(type) { + case []byte: + ip = v + case net.IP: + ip = v + } + + if ip == nil { + panic(fmt.Errorf("invalid ip %v", rhs.Value())) + } + + val, ok := lhs.Value().(string) + if !ok { + panic(fmt.Errorf("invalid value %v", lhs.Value())) + } + slog.Debug(fmt.Sprintf("inNetwork function has been deprecated and will be removed in a future release, use remoteAddress.network(\"%s\") instead", val)) + + network, ok := state.networks[val] + if !ok { + _, ipNet, err := net.ParseCIDR(val) + if err != nil { + panic("network not found") + } + return types.Bool(ipNet.Contains(ip)) + } else { + ok, err := network.Contains(ip) + if err != nil { + panic(err) + } + return types.Bool(ok) + } + }), + ), + ), + ) if err != nil { return err } diff --git a/lib/rule.go b/lib/rule.go index ae8faf6..2fd5670 100644 --- a/lib/rule.go +++ b/lib/rule.go @@ -1,12 +1,12 @@ package lib import ( + http_cel "codeberg.org/gone/http-cel" "crypto/sha256" "encoding/hex" "fmt" "git.gammaspectra.live/git/go-away/lib/action" "git.gammaspectra.live/git/go-away/lib/challenge" - "git.gammaspectra.live/git/go-away/lib/condition" "git.gammaspectra.live/git/go-away/lib/policy" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" @@ -66,12 +66,12 @@ func NewRuleState(state challenge.StateInterface, r policy.Rule, replacer *strin conditions = append(conditions, cond) } - ast, err := condition.FromStrings(state.ProgramEnv(), condition.OperatorOr, conditions...) + ast, err := http_cel.NewAst(state.ProgramEnv(), http_cel.OperatorOr, conditions...) if err != nil { return RuleState{}, fmt.Errorf("error compiling conditions: %w", err) } - program, err := condition.Program(state.ProgramEnv(), ast) + program, err := http_cel.ProgramAst(state.ProgramEnv(), ast) if err != nil { return RuleState{}, fmt.Errorf("error compiling program: %w", err) } diff --git a/lib/state.go b/lib/state.go index 7c590a9..2a1ae65 100644 --- a/lib/state.go +++ b/lib/state.go @@ -1,12 +1,12 @@ package lib import ( + http_cel "codeberg.org/gone/http-cel" "crypto/ed25519" "crypto/rand" "encoding/json" "fmt" "git.gammaspectra.live/git/go-away/lib/challenge" - "git.gammaspectra.live/git/go-away/lib/condition" "git.gammaspectra.live/git/go-away/lib/policy" "git.gammaspectra.live/git/go-away/lib/settings" "git.gammaspectra.live/git/go-away/utils" @@ -191,7 +191,7 @@ func NewState(p policy.Policy, opt settings.Settings, settings policy.StateSetti var replacements []string for k, entries := range p.Conditions { - ast, err := condition.FromStrings(state.programEnv, condition.OperatorOr, entries...) + ast, err := http_cel.NewAst(state.programEnv, http_cel.OperatorOr, entries...) if err != nil { return nil, fmt.Errorf("conditions %s: error compiling conditions: %v", k, err) }