xds: CEL implementation#12770
Conversation
|
Pasting @AgraVator's comment here from #12640 (comment): To support future attributes beyond just headers (like the aforementioned transport info or cluster metadata), we might want to consider a more pluggable way to populate MatchContext. Currently, it has a fixed set of fields. A map-based approach or a registry of attribute providers might make it easier to add new attributes without changing the core MatchContext class every time. |
I don't see what benefit is there in having a different class for each attribute like referer or user-agent. The list of Envoy request attributes is fixed and there is never a need to have some of them provided and other not. Rather than think in terms of organizing our code in terms of plugin, we should think in terms of providing the evaluation context for the Cel runtime. With A106 supporting only request attributes for now we have this in that provides a map to Cel runtime for evaluating request.* variables. That map is backed by |
| if (func == StandardFunction.STRING) { | ||
| return false; | ||
| } | ||
| if (func == StandardFunction.ADD) { | ||
| return !over.equals(AddOverload.ADD_STRING) | ||
| && !over.equals(AddOverload.ADD_LIST); | ||
| } |
There was a problem hiding this comment.
Negative tests need to be added by creating AST and creating runtime with these disallowed expression types.
| return null; | ||
| } | ||
| String headerName = ((String) key).toLowerCase(java.util.Locale.ROOT); | ||
| if ("te".equals(headerName)) { |
There was a problem hiding this comment.
Add some comment why.
| sb.append(","); | ||
| } | ||
| first = false; | ||
| sb.append(BaseEncoding.base64().encode(value)); |
There was a problem hiding this comment.
Add a .omitPadding()
BaseEncoding.base64().omitPadding().encode(value);
to keep it consistent with ext_proc filter that omits padding.
|
|
||
| public MatchContext(Metadata metadata, @Nullable String path, | ||
| @Nullable String host, @Nullable String method, | ||
| @Nullable String id) { |
There was a problem hiding this comment.
The gRFC says this should be metadata["x-request-id"]`.
| private final String id; | ||
|
|
||
| public MatchContext(Metadata metadata, @Nullable String path, | ||
| @Nullable String host, @Nullable String method, |
There was a problem hiding this comment.
I don't think path, host and method can be null. We should remove nullable and add checkNotNull assertions so it fails if the caller passes null values for any of them.
There was a problem hiding this comment.
Pull request overview
Introduces the foundational CEL (Common Expression Language) runtime integration for gRPC xDS matching (per gRFC A106), including a gRPC request/headers CEL environment, a string-extractor utility, and the build/shading wiring needed to ship CEL with the xDS artifact.
Changes:
- Added CEL runtime utilities for allowed-reference validation, program execution, and request attribute resolution (
CelCommon,CelStringExtractor,GrpcCelEnvironment,HeadersWrapper,MatchContext). - Added unit tests covering CEL environment behavior, disabled features, and string extraction behavior.
- Integrated CEL dependencies into Gradle/Bazel builds and updated shading/relocation rules for the xDS artifact.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java | Defines CEL runtime options/functions and validates allowed references. |
| xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java | Compiles and evaluates CEL expressions intended to produce strings (with optional default). |
| xds/src/main/java/io/grpc/xds/internal/matcher/GrpcCelEnvironment.java | Provides request variable resolution for CEL evaluation based on gRPC request context. |
| xds/src/main/java/io/grpc/xds/internal/matcher/HeadersWrapper.java | Exposes request metadata/pseudo-headers as a Map for CEL header lookup. |
| xds/src/main/java/io/grpc/xds/internal/matcher/MatchContext.java | Simple container/builder for request attributes used by the CEL environment. |
| xds/src/test/java/io/grpc/xds/internal/matcher/CelCommonTest.java | Tests CelCommon.checkAllowedReferences() allow/deny behavior. |
| xds/src/test/java/io/grpc/xds/internal/matcher/CelEnvironmentTest.java | Tests the CEL environment and header map behavior, including disabled features. |
| xds/src/test/java/io/grpc/xds/internal/matcher/CelMatcherTestHelper.java | Test helper for compiling CEL ASTs with restricted declarations. |
| xds/src/test/java/io/grpc/xds/internal/matcher/CelStringExtractorTest.java | Tests string extraction behavior (success, non-string, eval error, defaults). |
| xds/build.gradle | Adds CEL dependencies and shading/relocation for the xDS artifact. |
| xds/BUILD.bazel | Adds CEL dependencies and jarjar relocation rules for Bazel builds. |
| repositories.bzl | Adds CEL artifacts to the shared Bazel third-party artifact list. |
| MODULE.bazel | Adds CEL artifacts to Bazel module artifact list. |
| gradle/libs.versions.toml | Declares CEL versions for Gradle dependency management. |
| build.gradle | Adds a Maven snapshots repository to all subprojects’ repositories. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public Set<String> keySet() { | ||
| return ImmutableSet.<String>builder() | ||
| .addAll(context.getMetadata().keys()) | ||
| .addAll(PSEUDO_HEADERS) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public int size() { | ||
| // Metadata.keys() returns a Set of unique keys, so we can just add the sizes. | ||
| // Note: This counts the number of unique header names, which is consistent with | ||
| // keySet().size(). | ||
| return context.getMetadata().keys().size() + PSEUDO_HEADERS.size(); | ||
| } |
| @Override | ||
| public boolean containsKey(Object key) { | ||
| if (!(key instanceof String)) { | ||
| return false; | ||
| } | ||
| String headerName = ((String) key).toLowerCase(java.util.Locale.ROOT); | ||
| if ("te".equals(headerName)) { | ||
| return false; | ||
| } | ||
| if (PSEUDO_HEADERS.contains(headerName)) { | ||
| return true; | ||
| } | ||
| if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { | ||
| return context.getMetadata().containsKey( | ||
| Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER)); | ||
| } | ||
| return context.getMetadata().containsKey( | ||
| Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER)); | ||
| } |
| @Nullable | ||
| private String getHeader(String headerName) { | ||
| if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { | ||
| Iterable<byte[]> values = context.getMetadata().getAll( | ||
| Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER)); | ||
| if (values == null) { |
| private static final Pattern ALLOWED_OVERLOAD_ID_PREFIX_PATTERN = Pattern.compile( | ||
| "^(size|matches|contains|startsWith|endsWith|starts_with|ends_with|" | ||
| + "timestamp|duration|in|index|has|int|uint|double|string|bytes|bool|" | ||
| + "less|less_equals|greater|greater_equals|" | ||
| + "add|subtract|multiply|divide|modulo|negate)" | ||
| + "[0-9]*(_.*)?$"); |
| assertAllowed("int(1) == 1"); | ||
| assertAllowed("uint(1) == 1u"); | ||
| assertAllowed("double(1) == 1.0"); | ||
| assertAllowed("string(1) == '1'"); |
| * fails. | ||
| */ | ||
| public String extract(Object input) throws CelEvaluationException { | ||
| if (input instanceof CelVariableResolver) { | ||
| try { | ||
| Object result = program.eval((CelVariableResolver) input); | ||
|
|
||
| if (result instanceof String) { | ||
| return (String) result; | ||
| } | ||
| } catch (CelEvaluationException e) { | ||
| if (defaultValue == null) { | ||
| throw e; | ||
| } | ||
| } | ||
| } else if (defaultValue == null) { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " | ||
| + (input == null ? "null" : input.getClass().getName())); | ||
| } |
This PR contains the core CEL evaluation logic and gRPC environment setup as defined in gRFC A106.
This is being carved out from #12640