-
Notifications
You must be signed in to change notification settings - Fork 4k
xds: Implementation of Unified Matcher and CEL Integration #12640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 32 commits
f472c63
99aeef1
db4d885
33eff21
e28c8f5
78895db
19989eb
118d7af
77b01b9
310677d
49031b4
5a31706
c62d193
0c2d771
91d6bea
22f65ca
5c04ac3
82d9a8b
7b50380
4b626ad
c5bf128
c0b2b1d
e07d612
020f9ff
f20c5f0
0bd57e9
3b6129f
7cad139
7688bd8
8c53456
b379a9e
60a9356
8515729
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package io.grpc.xds.internal.matcher; | ||
|
|
||
| import com.google.common.collect.ImmutableSet; | ||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.CelOptions; | ||
| import dev.cel.common.ast.CelReference; | ||
| import dev.cel.runtime.CelRuntime; | ||
| import dev.cel.runtime.CelRuntimeFactory; | ||
| import dev.cel.runtime.CelStandardFunctions; | ||
| import dev.cel.runtime.CelStandardFunctions.StandardFunction; | ||
| import dev.cel.runtime.standard.AddOperator.AddOverload; | ||
| import java.util.Map; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| /** | ||
| * Shared utilities for CEL-based matchers and extractors. | ||
| */ | ||
| final class CelCommon { | ||
| private static final CelOptions CEL_OPTIONS = CelOptions.newBuilder() | ||
| .enableComprehension(false) | ||
| .maxRegexProgramSize(100) | ||
|
shivaspeaks marked this conversation as resolved.
|
||
| .build(); | ||
| private static final String REQUEST_VARIABLE = "request"; | ||
| private static final CelStandardFunctions FUNCTIONS = | ||
| CelStandardFunctions.newBuilder() | ||
| .filterFunctions((func, over) -> { | ||
| if (func == StandardFunction.STRING) { | ||
| return false; | ||
| } | ||
| if (func == StandardFunction.ADD) { | ||
| return !over.equals(AddOverload.ADD_STRING) | ||
| && !over.equals(AddOverload.ADD_LIST); | ||
| } | ||
| return true; | ||
|
shivaspeaks marked this conversation as resolved.
|
||
| }) | ||
| .build(); | ||
|
|
||
| /** | ||
| * Set of allowed function names based on gRFC A106. | ||
| */ | ||
| private static final ImmutableSet<String> ALLOWED_FUNCTIONS = ImmutableSet.of( | ||
| "size", "matches", "contains", "startsWith", "endsWith", "timestamp", "duration", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The overload ids for standard functions will have suffixes like size_string, size_int64, etc and we should check that the overload id starts with one of these rather than use |
||
| "int", "uint", "double", "string", "bytes", "bool", "==", "!=", ">", "<", ">=", "<=", | ||
| "&&", "||", "!", "+", "-", "*", "/", "%", "in", "has", "or"); | ||
|
|
||
| /** | ||
| * Regular expression pattern to validate internal CEL overload IDs. | ||
| * | ||
| * <p> | ||
| * Standard CEL operators and conversion functions often have empty names in the | ||
| * AST | ||
| * and are identified solely by their overload IDs (e.g., {@code equals} for | ||
| * {@code ==}, | ||
| * {@code divide_int64} for {@code /}). | ||
| * | ||
| * <p> | ||
| * This pattern matches allowed overload IDs by their prefixes (e.g., | ||
| * {@code divide}, | ||
| * {@code size}), optionally followed by numeric types (e.g., {@code int64}) and | ||
| * type-specific | ||
| * suffixes (e.g., {@code _string}, {@code _int64}). | ||
| */ | ||
| private static final Pattern ALLOWED_OVERLOAD_ID_PATTERN = Pattern.compile( | ||
| "^(size|matches|contains|startsWith|endsWith|starts_with|ends_with|" | ||
| + "timestamp|duration|in|index|has|int|uint|double|string|bytes|bool|" | ||
| + "equals|not_equals|less|less_equals|greater|greater_equals|" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. equals, not equals, logical and/or/not never have suffixes. We should therefore do exact matching for these. Have another set and do |
||
| + "add|subtract|multiply|divide|modulo|logical_and|logical_or|logical_not)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add negate to this list and unit test for it |
||
| + "[0-9]*(_.*)?$"); | ||
|
|
||
| static final CelRuntime RUNTIME = CelRuntimeFactory.standardCelRuntimeBuilder() | ||
| .setStandardEnvironmentEnabled(false) | ||
| .setStandardFunctions(FUNCTIONS) | ||
| .setOptions(CEL_OPTIONS) | ||
| .build(); | ||
|
|
||
| private CelCommon() {} | ||
|
|
||
| /** | ||
| * Validates that the AST only references the allowed variable ("request") | ||
| * and supported functions as defined in gRFC A106. | ||
| */ | ||
| static void checkAllowedReferences(CelAbstractSyntaxTree ast) { | ||
| for (Map.Entry<Long, CelReference> entry : ast.getReferenceMap().entrySet()) { | ||
| CelReference ref = entry.getValue(); | ||
|
|
||
| // Check for variables (where overloadIds is empty) | ||
| if (!ref.value().isPresent() && ref.overloadIds().isEmpty()) { | ||
| if (!REQUEST_VARIABLE.equals(ref.name())) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression references unknown variable: " + ref.name()); | ||
| } | ||
| } else if (!ref.overloadIds().isEmpty()) { | ||
| String name = ref.name(); | ||
| if (name.isEmpty()) { | ||
| boolean allowed = false; | ||
| for (String id : ref.overloadIds()) { | ||
| if (ALLOWED_OVERLOAD_ID_PATTERN.matcher(id).matches()) { | ||
| allowed = true; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should check for all overload ids in the list. |
||
| break; | ||
| } | ||
| } | ||
| if (!allowed) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression references unknown function with overload IDs: " | ||
| + ref.overloadIds()); | ||
| } | ||
| } else if (!ALLOWED_FUNCTIONS.contains(name)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the logic to reject any non-empty name when overloadIds is present. Because if a reference has a name, it's not a standard operator or a variable (handled by the first if), so it must be something we don't support, such as custom functions.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, so standard allowed functions never have a name in the AST, so we should remove ALLOWED_FUNCTIONS, which is now an obsolete list here? |
||
| throw new IllegalArgumentException( | ||
| "CEL expression references unknown function: " + name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert the whitespace changes in this file ?