Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/envoy/extensions/formatter/cel/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
licenses(["notice"]) # Apache 2

api_proto_package(
deps = ["@xds//udpa/annotations:pkg"],
deps = [
"//envoy/config/core/v3:pkg",
"@xds//udpa/annotations:pkg",
],
)
9 changes: 7 additions & 2 deletions api/envoy/extensions/formatter/cel/v3/cel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package envoy.extensions.formatter.cel.v3;

import "envoy/config/core/v3/cel.proto";

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.formatter.cel.v3";
Expand Down Expand Up @@ -50,7 +52,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// Configuration for the CEL formatter.
//
// .. warning::
// This extension is treated as built-in extension and will be enabled by default now.
// It is unnecessary to configure this extension.
// This extension is treated as a built-in extension and is enabled by default.
// It is unnecessary to configure this extension unless overriding the CEL expression runtime
// via ``cel_config``.
message Cel {
// Configuration for the CEL expression runtime used by this formatter.
config.core.v3.CelExpressionConfig cel_config = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Exposed :ref:`cel_config <envoy_v3_api_field_extensions.formatter.cel.v3.Cel.cel_config>` on the
``envoy.formatter.cel`` formatter configuration, allowing CEL runtime options such as string
functions to be enabled for configured CEL formatters.
2 changes: 2 additions & 0 deletions source/extensions/formatter/cel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ envoy_cc_extension(
],
deps = [
"//envoy/registry",
"//source/common/protobuf:utility_lib",
"//source/extensions/filters/common/expr:evaluator_lib",
"//source/extensions/formatter/cel:cel_lib",
"@envoy_api//envoy/extensions/formatter/cel/v3:pkg_cc_proto",
] + select(
Expand Down
21 changes: 18 additions & 3 deletions source/extensions/formatter/cel/cel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ Protobuf::Value CELFormatter::formatValue(const Envoy::Formatter::Context& conte
}
}

CELFormatterCommandParser::CELFormatterCommandParser(
const ::Envoy::LocalInfo::LocalInfo& local_info,
Expr::BuilderInstanceSharedConstPtr expr_builder)
: configured_state_(ConfiguredState{local_info, std::move(expr_builder)}) {
ASSERT(configured_state_->expr_builder != nullptr);
}

absl::StatusOr<Envoy::Formatter::FormatterProviderPtr>
CELFormatterCommandParser::parse(absl::string_view command, absl::string_view subcommand,
std::optional<size_t> max_length) const {
Expand All @@ -85,10 +92,18 @@ CELFormatterCommandParser::parse(absl::string_view command, absl::string_view su
if (!parse_status.ok()) {
throw EnvoyException("Not able to parse expression: " + parse_status.status().ToString());
}
Server::Configuration::ServerFactoryContext& context =
Server::Configuration::ServerFactoryContextInstance::get();

// Lazily resolve the active server context at CEL-command parse time: built-in command parsers
// are process-global, so they cannot capture server-owned CEL state.
if (!configured_state_.has_value()) {
Server::Configuration::ServerFactoryContext& context =
Server::Configuration::ServerFactoryContextInstance::get();
return std::make_unique<CELFormatter>(context.localInfo(), Expr::getBuilder(context),
parse_status.value().expr(), max_length,
command == "TYPED_CEL");
}
return std::make_unique<CELFormatter>(
context.localInfo(), Extensions::Filters::Common::Expr::getBuilder(context),
configured_state_->local_info.get(), configured_state_->expr_builder,
parse_status.value().expr(), max_length, command == "TYPED_CEL");
}

Expand Down
16 changes: 16 additions & 0 deletions source/extensions/formatter/cel/cel.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <functional>
#include <optional>
#include <string>

#include "envoy/config/typed_config.h"
Expand Down Expand Up @@ -33,9 +35,23 @@ class CELFormatter : public ::Envoy::Formatter::FormatterProvider {
class CELFormatterCommandParser : public ::Envoy::Formatter::CommandParser {
public:
CELFormatterCommandParser() = default;
CELFormatterCommandParser(
const ::Envoy::LocalInfo::LocalInfo& local_info,
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder);
absl::StatusOr<Envoy::Formatter::FormatterProviderPtr>
parse(absl::string_view command, absl::string_view subcommand,
std::optional<size_t> max_length) const override;

private:
struct ConfiguredState {
std::reference_wrapper<const ::Envoy::LocalInfo::LocalInfo> local_info;
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder;
};

// Present only for parsers created from explicit `envoy.formatter.cel` config. Keeping the
// server-owned values together avoids a half-configured parser; absence means built-in parser
// mode, where `parse()` resolves the active server context for each CEL command.
std::optional<ConfiguredState> configured_state_;
};

} // namespace Formatter
Expand Down
21 changes: 14 additions & 7 deletions source/extensions/formatter/cel/config.cc
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
#include "source/extensions/formatter/cel/config.h"

#include "envoy/extensions/formatter/cel/v3/cel.pb.h"
#include "envoy/extensions/formatter/cel/v3/cel.pb.validate.h"

#include "source/common/protobuf/utility.h"
#include "source/extensions/formatter/cel/cel.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

::Envoy::Formatter::CommandParserPtr
CELFormatterFactory::createCommandParserFromProto(const Protobuf::Message&,
Server::Configuration::GenericFactoryContext&) {
::Envoy::Formatter::CommandParserPtr CELFormatterFactory::createCommandParserFromProto(
const Protobuf::Message& proto_config, Server::Configuration::GenericFactoryContext& context) {
#if defined(USE_CEL_PARSER)
ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::config), warn,
"'CEL' formatter is treated as a built-in formatter and does not "
"require configuration.");
return std::make_unique<CELFormatterCommandParser>();
const auto& config =
MessageUtil::downcastAndValidate<const envoy::extensions::formatter::cel::v3::Cel&>(
proto_config, context.messageValidationVisitor());
const auto config_ref = config.has_cel_config()
? Envoy::makeOptRef(config.cel_config())
: Envoy::OptRef<const envoy::config::core::v3::CelExpressionConfig>{};
return std::make_unique<CELFormatterCommandParser>(
context.serverFactoryContext().localInfo(),
Filters::Common::Expr::getBuilder(context.serverFactoryContext(), config_ref));
#else
UNREFERENCED_PARAMETER(proto_config);
UNREFERENCED_PARAMETER(context);
throw EnvoyException("CEL is not available for use in this environment.");
#endif
Expand Down
38 changes: 37 additions & 1 deletion test/extensions/formatter/cel/cel_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ TEST_F(CELFormatterTest, TestFormatConversionV1AlphaToDevCel) {
ProtoEq(ValueUtil::stringValue("true")));
}

TEST_F(CELFormatterTest, TestRequestHeaderWithLegacyConfiguration) {
TEST_F(CELFormatterTest, TestConfiguredFormatterWithoutCelConfig) {
const std::string yaml = R"EOF(
text_format_source:
inline_string: "%CEL(request.headers[':method'])%"
Expand All @@ -343,6 +343,42 @@ TEST_F(CELFormatterTest, TestRequestHeaderWithLegacyConfiguration) {
EXPECT_EQ("GET", formatter->format(formatter_context_, stream_info_));
}

TEST_F(CELFormatterTest, TestCelConfigEnablesStringFunctions) {
const std::string yaml = R"EOF(
text_format_source:
inline_string: "%CEL(request.headers['x-envoy-original-path'].replace('/original', '/mutated'))%"
formatters:
- name: envoy.formatter.cel
typed_config:
"@type": type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel
cel_config:
enable_string_functions: true
)EOF";
TestUtility::loadFromYaml(yaml, config_);

auto formatter =
*Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_);
EXPECT_EQ("/mutated/path?secret=parameter", formatter->format(formatter_context_, stream_info_));
}

// Without `cel_config`, string functions default off, so `replace()` is
// unregistered and building the expression fails.
TEST_F(CELFormatterTest, TestStringFunctionExpressionRejectedWithoutCelConfig) {
const std::string yaml = R"EOF(
text_format_source:
inline_string: "%CEL(request.headers['x-envoy-original-path'].replace('/original', '/mutated'))%"
formatters:
- name: envoy.formatter.cel
typed_config:
"@type": type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel
)EOF";
TestUtility::loadFromYaml(yaml, config_);

EXPECT_THROW_WITH_REGEX(
*Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_),
EnvoyException, "failed to create an expression: .*");
}

TEST_F(CELFormatterTest, TestRequestHeader) {
const std::string yaml = R"EOF(
text_format_source:
Expand Down