Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/rulemanager/cel/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,16 @@ func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error
eventObj, eventTyp := xcel.NewObject(&utils.CelEventImpl{})
xcel.RegisterObject(ta, tp, eventObj, eventTyp, utils.CelFields)

// Register the nested request accessor type
requestObj, requestTyp := xcel.NewObject(utils.HttpRequestAccessor{})
xcel.RegisterObject(ta, tp, requestObj, requestTyp, utils.HttpRequestFields)

// Set the request field's type now that requestTyp is available
utils.CelFields["request"].Type = requestTyp

envOptions := []cel.EnvOption{
cel.Variable("event", eventTyp), // All events accessible via "event" variable
cel.Variable("http", eventTyp), // HTTP events also accessible via "http" variable
cel.Variable("eventType", cel.StringType),
cel.CustomTypeAdapter(ta),
cel.CustomTypeProvider(tp),
Expand Down
166 changes: 166 additions & 0 deletions pkg/utils/cel.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package utils

import (
"bytes"
"fmt"
"io"
"net/http"

celtypes "github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
Expand All @@ -12,6 +15,7 @@ type CelEvent interface {
CapabilitiesEvent
DNSEvent
ExecEvent
HttpEvent
IOUring
LinkEvent
NetworkEvent
Expand All @@ -27,6 +31,12 @@ type CelEventImpl struct {
CelEvent
}

// HttpRequestAccessor provides access to HTTP request fields
// It's a lightweight wrapper around CelEvent that avoids allocations
type HttpRequestAccessor struct {
HttpEvent CelEvent
}

var isSet = ref.FieldTester(func(target any) bool {
x := target.(*xcel.Object[CelEvent])
if x.Raw == nil {
Expand All @@ -35,6 +45,27 @@ var isSet = ref.FieldTester(func(target any) bool {
return true
})

var requestIsSet = ref.FieldTester(func(target any) bool {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return false
}
req := x.Raw.HttpEvent.GetRequest()
return req != nil
})

var urlIsSet = ref.FieldTester(func(target any) bool {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return false
}
req := x.Raw.HttpEvent.GetRequest()
if req == nil || req.URL == nil {
return false
}
return req.URL.String() != ""
})

var CelFields = map[string]*celtypes.FieldType{
"args": {
Type: celtypes.ListType,
Expand Down Expand Up @@ -388,4 +419,139 @@ var CelFields = map[string]*celtypes.FieldType{
return x.Raw.GetUid(), nil
}),
},
// HTTP request nested object (no allocation - just wraps the event)
"request": {
Type: nil, // Will be set during registration
IsSet: isSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[CelEvent])
if x.Raw == nil {
return nil, fmt.Errorf("celval: object is nil")
}
// Return a wrapped accessor - xcel.NewObject is lightweight (just pointer wrapping)
accessor := HttpRequestAccessor{HttpEvent: x.Raw}
obj, _ := xcel.NewObject(accessor)
return obj, nil
}),
},
"direction": {
Type: celtypes.StringType,
IsSet: isSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[CelEvent])
if x.Raw == nil {
return nil, fmt.Errorf("celval: object is nil")
}
return string(x.Raw.GetDirection()), nil
}),
},
}

// HttpRequestFields defines CEL fields for the nested http.request object
var HttpRequestFields = map[string]*celtypes.FieldType{
"headers": {
Type: celtypes.MapType,
IsSet: requestIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
req := x.Raw.HttpEvent.GetRequest()
if req != nil {
return req.Header, nil
}
return http.Header{}, nil
}),
},
"host": {
Type: celtypes.StringType,
IsSet: requestIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
req := x.Raw.HttpEvent.GetRequest()
if req != nil {
return req.Host, nil
}
return "", nil
}),
},
"method": {
Type: celtypes.StringType,
IsSet: requestIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
req := x.Raw.HttpEvent.GetRequest()
if req != nil {
return req.Method, nil
}
return "", nil
}),
},
"url": {
Type: celtypes.StringType,
IsSet: urlIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
req := x.Raw.HttpEvent.GetRequest()
if req != nil && req.URL != nil {
return req.URL.String(), nil
}
return "", nil
}),
},
"path": {
Type: celtypes.StringType,
IsSet: urlIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
req := x.Raw.HttpEvent.GetRequest()
if req != nil && req.URL != nil {
return req.URL.Path, nil
}
return "", nil
}),
},
"body": {
Type: celtypes.StringType,
IsSet: requestIsSet,
GetFrom: ref.FieldGetter(func(target any) (any, error) {
x := target.(*xcel.Object[HttpRequestAccessor])
if x.Raw.HttpEvent == nil {
return nil, fmt.Errorf("celval: object is nil")
}
// Try GetBuf() first (for eBPF events)
buf := x.Raw.HttpEvent.GetBuf()
if len(buf) > 0 {
return string(buf), nil
}
// Fallback to reading from Request.Body (for test events)
req := x.Raw.HttpEvent.GetRequest()
if req != nil && req.Body != nil {
// Read with size limit (10MB) and restore body for downstream readers
const maxBodySize = 10 * 1024 * 1024 // 10MB
limitedReader := io.LimitReader(req.Body, maxBodySize)
bodyBytes, err := io.ReadAll(limitedReader)
req.Body.Close() // Close the original body
req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) // Restore for downstream
if err != nil {
return "", err
}
return string(bodyBytes), nil
}
return "", nil
}),
},
}
Loading