Skip to content

Conversation

@tdesrosi
Copy link

Upstream CIDR and IP-related functions from kubernetes into cel-go

This is part of a broader effort to bring network functions from the kubernetes
project into CEL specifications upstream. This is related directly to
issues/1237.

These are currently locked inside k8s.io/apiserver, but they are generally
useful for any policy engine dealing with network logic (firewalls, access lists, etc.).

@tdesrosi
Copy link
Author

/gcbrun

1 similar comment
@TristonianJones
Copy link
Collaborator

/gcbrun

@TristonianJones
Copy link
Collaborator

FYI @cici37 @jpbetz

@TristonianJones
Copy link
Collaborator

/gcbrun

@TristonianJones
Copy link
Collaborator

/gcbrun

@tdesrosi
Copy link
Author

tdesrosi commented Dec 8, 2025

A note about these changes: To maintain strict AST-level compatibility with k8s, I'm going with a split registration pattern (declaring signatures in CompileOptions and bindings in ProgramOptions), rather than bundling them in CompileOptions via cel.Function.

When using the modern cel-go helper cel.Function() to register both the global overload (ip(string)) and the member overload (cidr.ip()) simultaneously, the internal dispatcher validation incorrectly flags the self-referencing overload ID "ip" as a collision (overload already exists).

To resolve this collision with the modern helper, we would be forced to rename the overload ID to something distinct like "ip_string". While functionally equivalent, this breaks strict parity with the Kubernetes AST reference data. If requested, we can go this route (and maintain parity minus the "ip" --> "ip_string" difference in overload IDs).

@TristonianJones
Copy link
Collaborator

/gcbrun

@TristonianJones
Copy link
Collaborator

A note about these changes: To maintain strict AST-level compatibility with k8s, I'm going with a split registration pattern (declaring signatures in CompileOptions and bindings in ProgramOptions), rather than bundling them in CompileOptions via cel.Function.

Actually, there really isn't any difference here between how the bindings are configured. K8s stages it a little differently, but it's materially identical between the two approaches.

When using the modern cel-go helper cel.Function() to register both the global overload (ip(string)) and the member overload (cidr.ip()) simultaneously, the internal dispatcher validation incorrectly flags the self-referencing overload ID "ip" as a collision (overload already exists).

It looks like the function ip overloads aren't properly configured as the overload_id values don't match those in K8s. Take another look as the two overloads in K8s are cidr_ip and string_to_ip.

It's worth looking at the other overloads as well.

@tdesrosi
Copy link
Author

Thanks Tristan, here's one more pass rechecking overloads and member overloads.

Copy link
Collaborator

@TristonianJones TristonianJones left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've raised google/cel-spec#507 to cel-spec, so shortly after both artifacts are checked in and a new cel-spec release cut, let's enable the conformance tests

// cidr('192.168.1.5/24').ip() == ip('192.168.1.5')
// cidr('192.168.1.5/24').masked() == cidr('192.168.1.0/24')
// cidr('192.168.1.0/24').prefixLength() == 24
func Network(version uint32) cel.EnvOption {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing version directly, prefer a functional NetworkOption that indicates the NetworkVersion(int), similar to how the BindingsOption is configured:

cel-go/ext/bindings.go

Lines 69 to 78 in e9f15ea

// BindingsOption declares a functional operator for configuring the Bindings library behavior.
type BindingsOption func(*celBindings) *celBindings
// BindingsVersion sets the version of the bindings library to an explicit version.
func BindingsVersion(version uint32) BindingsOption {
return func(lib *celBindings) *celBindings {
lib.version = version
return lib
}
}

cel-go/ext/bindings.go

Lines 54 to 56 in e9f15ea

func Bindings(options ...BindingsOption) cel.EnvOption {
b := &celBindings{version: math.MaxUint32}
for _, o := range options {

type networkLib struct {
version uint32
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add comments to all exported (UpperCase) functions

),

// 2. Register Functions
cel.Function(isIPFunc,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit, but could you order these functions by name alphabetically? The K8s files do this and it makes the review diff a bit easier to reason about. Perhaps group by CIDR first, then IP?

netip.Prefix
}

func (c CIDR) ConvertToNative(typeDesc reflect.Type) (any, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add unit tests for conversion functions.

func (c CIDR) Equal(other ref.Val) ref.Val {
o, ok := other.(CIDR)
if !ok {
return types.ValOrErr(other, "no such overload")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CEL types no longer error when the type is not the same. The current semantic is to treat the runtime equality check as 'false'

func (i IP) Equal(other ref.Val) ref.Val {
o, ok := other.(IP)
if !ok {
return types.ValOrErr(other, "no such overload")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CEL types should return false if the types aren't the same. This is a shift in behavior from how CEL originally started.

cel.Function(prefixLengthFunc,
cel.MemberOverload("cidr_prefix_length", []*cel.Type{CIDRType}, cel.IntType,
cel.UnaryBinding(netCIDRPrefixLength)),
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, but consider introducing static validators for ip and cidr conversions from string literals as this will save people a lot of trouble with runtime errors which could have been caught at compile time.

cel-go/cel/validator.go

Lines 261 to 279 in e9f15ea

// Validate searches the AST for uses of a given function name with a constant argument and performs a check
// on whether the argument is a valid literal value.
func (v formatValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, iss *Issues) {
root := ast.NavigateAST(a)
funcCalls := ast.MatchDescendants(root, ast.FunctionMatcher(v.funcName))
for _, call := range funcCalls {
callArgs := call.AsCall().Args()
if len(callArgs) <= v.argNum {
continue
}
litArg := callArgs[v.argNum]
if litArg.Kind() != ast.LiteralKind {
continue
}
if err := v.check(e, call, litArg); err != nil {
iss.ReportErrorAtID(litArg.ID(), "invalid %s argument", v.funcName)
}
}
}

}

const (
// Function names matching Kubernetes implementation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Function names matching Kubernetes implementation
// Function names matching the original Kubernetes implementation of this networking library

?

@jpbetz
Copy link
Contributor

jpbetz commented Jan 13, 2026

@lalitc375

@@ -0,0 +1,526 @@
// Copyright 2025 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cost calculations for this library exist in https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go in the Kuberhetes code. Is it possible to also upstream the cost calculations?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll tackle this after the initial implementation is in

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants