Skip to content

Commit 7044561

Browse files
committed
formatter: expose CelExpressionConfig on the CEL formatter
Add `cel_config` to `envoy.formatter.cel` configuration, allowing CEL runtime options to be enabled on a per-formatter basis. This is prerequisite work for #45420: the header mutation filters need to enable CEL string functions through formatter configuration, but the CEL formatter extension did not expose the underlying CEL expression runtime options. The built-in CEL parser still uses the active server context, so existing unconfigured CEL formatter command usage is unchanged. Signed-off-by: Kamal Al Marhubi <kamal@marhubi.com>
1 parent 33840de commit 7044561

7 files changed

Lines changed: 96 additions & 13 deletions

File tree

api/envoy/extensions/formatter/cel/v3/BUILD

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
55
licenses(["notice"]) # Apache 2
66

77
api_proto_package(
8-
deps = ["@xds//udpa/annotations:pkg"],
8+
deps = [
9+
"//envoy/config/core/v3:pkg",
10+
"@xds//udpa/annotations:pkg",
11+
],
912
)

api/envoy/extensions/formatter/cel/v3/cel.proto

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ syntax = "proto3";
22

33
package envoy.extensions.formatter.cel.v3;
44

5+
import "envoy/config/core/v3/cel.proto";
6+
57
import "udpa/annotations/status.proto";
68

79
option java_package = "io.envoyproxy.envoy.extensions.formatter.cel.v3";
@@ -50,7 +52,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
5052
// Configuration for the CEL formatter.
5153
//
5254
// .. warning::
53-
// This extension is treated as built-in extension and will be enabled by default now.
54-
// It is unnecessary to configure this extension.
55+
// This extension is treated as a built-in extension and is enabled by default.
56+
// It is unnecessary to configure this extension unless overriding the CEL expression runtime
57+
// via ``cel_config``.
5558
message Cel {
59+
// Configuration for the CEL expression runtime used by this formatter.
60+
config.core.v3.CelExpressionConfig cel_config = 1;
5661
}

source/extensions/formatter/cel/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ envoy_cc_extension(
5050
],
5151
deps = [
5252
"//envoy/registry",
53+
"//source/common/protobuf:utility_lib",
54+
"//source/extensions/filters/common/expr:evaluator_lib",
5355
"//source/extensions/formatter/cel:cel_lib",
5456
"@envoy_api//envoy/extensions/formatter/cel/v3:pkg_cc_proto",
5557
] + select(

source/extensions/formatter/cel/cel.cc

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ Protobuf::Value CELFormatter::formatValue(const Envoy::Formatter::Context& conte
7676
}
7777
}
7878

79+
CELFormatterCommandParser::CELFormatterCommandParser(
80+
const ::Envoy::LocalInfo::LocalInfo& local_info,
81+
Expr::BuilderInstanceSharedConstPtr expr_builder)
82+
: configured_state_(ConfiguredState{local_info, std::move(expr_builder)}) {
83+
ASSERT(configured_state_->expr_builder != nullptr);
84+
}
85+
7986
::Envoy::Formatter::FormatterProviderPtr
8087
CELFormatterCommandParser::parse(absl::string_view command, absl::string_view subcommand,
8188
absl::optional<size_t> max_length) const {
@@ -85,10 +92,18 @@ CELFormatterCommandParser::parse(absl::string_view command, absl::string_view su
8592
if (!parse_status.ok()) {
8693
throw EnvoyException("Not able to parse expression: " + parse_status.status().ToString());
8794
}
88-
Server::Configuration::ServerFactoryContext& context =
89-
Server::Configuration::ServerFactoryContextInstance::get();
95+
96+
// Lazily resolve the active server context at CEL-command parse time: built-in command parsers
97+
// are process-global, so they cannot capture server-owned CEL state.
98+
if (!configured_state_.has_value()) {
99+
Server::Configuration::ServerFactoryContext& context =
100+
Server::Configuration::ServerFactoryContextInstance::get();
101+
return std::make_unique<CELFormatter>(context.localInfo(), Expr::getBuilder(context),
102+
parse_status.value().expr(), max_length,
103+
command == "TYPED_CEL");
104+
}
90105
return std::make_unique<CELFormatter>(
91-
context.localInfo(), Extensions::Filters::Common::Expr::getBuilder(context),
106+
configured_state_->local_info.get(), configured_state_->expr_builder,
92107
parse_status.value().expr(), max_length, command == "TYPED_CEL");
93108
}
94109

source/extensions/formatter/cel/cel.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <functional>
34
#include <string>
45

56
#include "envoy/config/typed_config.h"
@@ -8,6 +9,8 @@
89
#include "source/common/formatter/substitution_formatter.h"
910
#include "source/extensions/filters/common/expr/evaluator.h"
1011

12+
#include "absl/types/optional.h"
13+
1114
namespace Envoy {
1215
namespace Extensions {
1316
namespace Formatter {
@@ -33,9 +36,22 @@ class CELFormatter : public ::Envoy::Formatter::FormatterProvider {
3336
class CELFormatterCommandParser : public ::Envoy::Formatter::CommandParser {
3437
public:
3538
CELFormatterCommandParser() = default;
39+
CELFormatterCommandParser(
40+
const ::Envoy::LocalInfo::LocalInfo& local_info,
41+
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder);
3642
::Envoy::Formatter::FormatterProviderPtr parse(absl::string_view command,
3743
absl::string_view subcommand,
3844
absl::optional<size_t> max_length) const override;
45+
46+
private:
47+
struct ConfiguredState {
48+
std::reference_wrapper<const ::Envoy::LocalInfo::LocalInfo> local_info;
49+
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder;
50+
};
51+
52+
// Empty means built-in parser mode; `parse()` then falls back to the active server context to
53+
// preserve existing unconfigured `%CEL(...)%` behavior.
54+
absl::optional<ConfiguredState> configured_state_;
3955
};
4056

4157
} // namespace Formatter

source/extensions/formatter/cel/config.cc

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
#include "source/extensions/formatter/cel/config.h"
22

33
#include "envoy/extensions/formatter/cel/v3/cel.pb.h"
4+
#include "envoy/extensions/formatter/cel/v3/cel.pb.validate.h"
45

6+
#include "source/common/protobuf/utility.h"
57
#include "source/extensions/formatter/cel/cel.h"
68

79
namespace Envoy {
810
namespace Extensions {
911
namespace Formatter {
1012

11-
::Envoy::Formatter::CommandParserPtr
12-
CELFormatterFactory::createCommandParserFromProto(const Protobuf::Message&,
13-
Server::Configuration::GenericFactoryContext&) {
13+
::Envoy::Formatter::CommandParserPtr CELFormatterFactory::createCommandParserFromProto(
14+
const Protobuf::Message& proto_config, Server::Configuration::GenericFactoryContext& context) {
1415
#if defined(USE_CEL_PARSER)
15-
ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::config), warn,
16-
"'CEL' formatter is treated as a built-in formatter and does not "
17-
"require configuration.");
18-
return std::make_unique<CELFormatterCommandParser>();
16+
const auto& config =
17+
MessageUtil::downcastAndValidate<const envoy::extensions::formatter::cel::v3::Cel&>(
18+
proto_config, context.messageValidationVisitor());
19+
const auto config_ref = config.has_cel_config()
20+
? Envoy::makeOptRef(config.cel_config())
21+
: Envoy::OptRef<const envoy::config::core::v3::CelExpressionConfig>{};
22+
return std::make_unique<CELFormatterCommandParser>(
23+
context.serverFactoryContext().localInfo(),
24+
Filters::Common::Expr::getBuilder(context.serverFactoryContext(), config_ref));
1925
#else
26+
UNREFERENCED_PARAMETER(proto_config);
2027
UNREFERENCED_PARAMETER(context);
2128
throw EnvoyException("CEL is not available for use in this environment.");
2229
#endif

test/extensions/formatter/cel/cel_test.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,41 @@ TEST_F(CELFormatterTest, TestRequestHeaderWithLegacyConfiguration) {
329329
EXPECT_EQ("GET", formatter->format(formatter_context_, stream_info_));
330330
}
331331

332+
TEST_F(CELFormatterTest, TestCelConfigEnablesStringFunctions) {
333+
const std::string yaml = R"EOF(
334+
text_format_source:
335+
inline_string: "%CEL(request.headers['x-envoy-original-path'].replace('/original', '/mutated'))%"
336+
formatters:
337+
- name: envoy.formatter.cel
338+
typed_config:
339+
"@type": type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel
340+
cel_config:
341+
enable_string_functions: true
342+
)EOF";
343+
TestUtility::loadFromYaml(yaml, config_);
344+
345+
auto formatter =
346+
*Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_);
347+
EXPECT_EQ("/mutated/path?secret=parameter", formatter->format(formatter_context_, stream_info_));
348+
}
349+
350+
// Same expression as above, but without cel_config: string functions default
351+
// off, so replace() is unregistered and building the expression fails.
352+
TEST_F(CELFormatterTest, TestStringFunctionsDisabledWithoutCelConfig) {
353+
const std::string yaml = R"EOF(
354+
text_format_source:
355+
inline_string: "%CEL(request.headers['x-envoy-original-path'].replace('/original', '/mutated'))%"
356+
formatters:
357+
- name: envoy.formatter.cel
358+
typed_config:
359+
"@type": type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel
360+
)EOF";
361+
TestUtility::loadFromYaml(yaml, config_);
362+
363+
EXPECT_THROW(*Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_),
364+
EnvoyException);
365+
}
366+
332367
TEST_F(CELFormatterTest, TestRequestHeader) {
333368
const std::string yaml = R"EOF(
334369
text_format_source:

0 commit comments

Comments
 (0)