-
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 29 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,107 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * Shared utilities for CEL-based matchers and extractors. | ||
| */ | ||
| final class CelCommon { | ||
| private static final CelOptions CEL_OPTIONS = CelOptions.newBuilder() | ||
| .enableComprehension(false) | ||
| .maxRegexProgramSize(100) | ||
| .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", "bytes", "bool", "==", "!=", ">", "<", ">=", "<=", | ||
| "&&", "||", "!", "+", "-", "*", "/", "%", "in", "has", "or", "equals", | ||
| "index_map", "divide_int64", "int64_to_int64", "uint64_to_int64", | ||
| "double_to_int64", "string_to_int64", "timestamp_to_int64"); | ||
|
|
||
| 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_FUNCTIONS.contains(id)) { | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /* | ||
| * 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 dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.types.SimpleType; | ||
| import dev.cel.runtime.CelEvaluationException; | ||
| import dev.cel.runtime.CelRuntime; | ||
| import dev.cel.runtime.CelVariableResolver; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions. | ||
| */ | ||
| public final class CelMatcher { | ||
| private final CelRuntime.Program program; | ||
|
|
||
| private CelMatcher(CelRuntime.Program program) { | ||
| this.program = program; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the AST into a CelMatcher. | ||
| * Throws an Exception if evaluation fails during compilation setup. | ||
| */ | ||
| public static CelMatcher compile(CelAbstractSyntaxTree ast) | ||
| throws CelEvaluationException { | ||
| // CelEvaluationException -> inside cel-runtime -> Allowed in production signatures | ||
| // CelValidationException -> inside cel-compiler -> Forbidden in production signatures | ||
| if (ast.getResultType() != SimpleType.BOOL) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression must evaluate to boolean, got: " + ast.getResultType()); | ||
| } | ||
| CelCommon.checkAllowedReferences(ast); | ||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelMatcher(program); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation. | ||
| */ | ||
| public boolean match(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof CelVariableResolver) { | ||
| result = program.eval((CelVariableResolver) input); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " + input.getClass().getName()); | ||
| } | ||
|
|
||
| if (result instanceof Boolean) { | ||
| return (Boolean) result; | ||
| } | ||
| throw new CelEvaluationException( | ||
| "CEL expression must evaluate to boolean, got: " + result.getClass().getName()); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.