Skip to content

Commit 11c4adc

Browse files
fix: add cel iterator (#1465)
* fix: enable CEL iterators Signed-off-by: Jason Cameron <jason.cameron@stanwith.me> * test: add unit tests for CELChecker map iteration Signed-off-by: Jason Cameron <jason.cameron@stanwith.me> * fix: implement map iterators for HTTPHeaders and URLValues to resolve CEL internal errors Signed-off-by: Jason Cameron <jason.cameron@stanwith.me> * fix: replace checker.NewMapIterator with newMapIterator for HTTPHeaders and URLValues Signed-off-by: Jason Cameron <jason.cameron@stanwith.me> --------- Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
1 parent edbfd18 commit 11c4adc

5 files changed

Lines changed: 111 additions & 5 deletions

File tree

docs/docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Fixed mixed tab/space indentation in Caddy documentation code block
1717

1818
<!-- This changes the project to: -->
19+
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
1920

2021
## v1.25.0: Necron
2122

lib/policy/celchecker_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package policy
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/TecharoHQ/anubis/internal/dns"
8+
"github.com/TecharoHQ/anubis/lib/config"
9+
"github.com/TecharoHQ/anubis/lib/store/memory"
10+
)
11+
12+
func newTestDNS(t *testing.T) *dns.Dns {
13+
t.Helper()
14+
15+
ctx := t.Context()
16+
memStore := memory.New(ctx)
17+
cache := dns.NewDNSCache(300, 300, memStore)
18+
return dns.New(ctx, cache)
19+
}
20+
21+
func TestCELChecker_MapIterationWrappers(t *testing.T) {
22+
cfg := &config.ExpressionOrList{
23+
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
24+
}
25+
26+
checker, err := NewCELChecker(cfg, newTestDNS(t))
27+
if err != nil {
28+
t.Fatalf("creating CEL checker failed: %v", err)
29+
}
30+
31+
req, err := http.NewRequest(http.MethodGet, "https://example.com/?format=json", nil)
32+
if err != nil {
33+
t.Fatalf("making request failed: %v", err)
34+
}
35+
req.Header.Set("Accept", "application/json")
36+
37+
got, err := checker.Check(req)
38+
if err != nil {
39+
t.Fatalf("checking expression failed: %v", err)
40+
}
41+
if !got {
42+
t.Fatal("expected expression to evaluate true")
43+
}
44+
}

lib/policy/expressions/http_headers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ func (h HTTPHeaders) Get(key ref.Val) ref.Val {
6666
return result
6767
}
6868

69-
func (h HTTPHeaders) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
69+
func (h HTTPHeaders) Iterator() traits.Iterator {
70+
return newMapIterator(h.Header)
71+
}
7072

7173
func (h HTTPHeaders) IsZeroValue() bool {
7274
return len(h.Header) == 0
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package expressions
2+
3+
import (
4+
"errors"
5+
"maps"
6+
"reflect"
7+
"slices"
8+
9+
"github.com/google/cel-go/common/types"
10+
"github.com/google/cel-go/common/types/ref"
11+
"github.com/google/cel-go/common/types/traits"
12+
)
13+
14+
var ErrNotImplemented = errors.New("expressions: not implemented")
15+
16+
type stringSliceIterator struct {
17+
keys []string
18+
idx int
19+
}
20+
21+
func (s *stringSliceIterator) Value() any {
22+
return s
23+
}
24+
25+
func (s *stringSliceIterator) ConvertToNative(typeDesc reflect.Type) (any, error) {
26+
return nil, ErrNotImplemented
27+
}
28+
29+
func (s *stringSliceIterator) ConvertToType(typeValue ref.Type) ref.Val {
30+
return types.NewErr("can't convert from %q to %q", types.IteratorType, typeValue)
31+
}
32+
33+
func (s *stringSliceIterator) Equal(other ref.Val) ref.Val {
34+
return types.NewErr("can't compare %q to %q", types.IteratorType, other.Type())
35+
}
36+
37+
func (s *stringSliceIterator) Type() ref.Type {
38+
return types.IteratorType
39+
}
40+
41+
func (s *stringSliceIterator) HasNext() ref.Val {
42+
return types.Bool(s.idx < len(s.keys))
43+
}
44+
45+
func (s *stringSliceIterator) Next() ref.Val {
46+
if s.HasNext() != types.True {
47+
return nil
48+
}
49+
50+
val := s.keys[s.idx]
51+
s.idx++
52+
return types.String(val)
53+
}
54+
55+
func newMapIterator(m map[string][]string) traits.Iterator {
56+
return &stringSliceIterator{
57+
keys: slices.Collect(maps.Keys(m)),
58+
idx: 0,
59+
}
60+
}

lib/policy/expressions/url_values.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package expressions
22

33
import (
4-
"errors"
54
"net/url"
65
"reflect"
76
"strings"
@@ -11,8 +10,6 @@ import (
1110
"github.com/google/cel-go/common/types/traits"
1211
)
1312

14-
var ErrNotImplemented = errors.New("expressions: not implemented")
15-
1613
// URLValues is a type wrapper to expose url.Values into CEL programs.
1714
type URLValues struct {
1815
url.Values
@@ -69,7 +66,9 @@ func (u URLValues) Get(key ref.Val) ref.Val {
6966
return result
7067
}
7168

72-
func (u URLValues) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
69+
func (u URLValues) Iterator() traits.Iterator {
70+
return newMapIterator(u.Values)
71+
}
7372

7473
func (u URLValues) IsZeroValue() bool {
7574
return len(u.Values) == 0

0 commit comments

Comments
 (0)