Skip to content

Commit

Permalink
cel: implement list keys for wrappers (#19289)
Browse files Browse the repository at this point in the history
Implement field enumeration for CEL wrappers. This allows expressions that need to 
enumerate over all keys in a wrapper (including headers).

Risk Level: low (change in header size to match its accessor by de-duplicating header keys)
Testing: unit

Signed-off-by: Kuat Yessenov <kuat@google.com>
  • Loading branch information
kyessenov authored Jan 11, 2022
1 parent 68e581b commit b2a9429
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 8 deletions.
2 changes: 2 additions & 0 deletions source/extensions/filters/common/expr/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ envoy_cc_library(
"//source/common/grpc:common_lib",
"//source/common/http:header_map_lib",
"//source/common/http:utility_lib",
"//source/common/singleton:const_singleton",
"//source/common/stream_info:utility_lib",
"@com_google_cel_cpp//eval/public:cel_value",
"@com_google_cel_cpp//eval/public:cel_value_producer",
"@com_google_cel_cpp//eval/public/containers:container_backed_list_impl",
"@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
],
Expand Down
102 changes: 97 additions & 5 deletions source/extensions/filters/common/expr/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#include "source/common/grpc/status.h"
#include "source/common/http/header_utility.h"
#include "source/common/http/headers.h"
#include "source/common/singleton/const_singleton.h"

#include "eval/public/cel_value.h"
#include "eval/public/cel_value_producer.h"
#include "eval/public/containers/container_backed_list_impl.h"
#include "eval/public/structs/cel_proto_wrapper.h"

namespace Envoy {
Expand Down Expand Up @@ -77,6 +79,63 @@ constexpr absl::string_view Upstream = "upstream";
constexpr absl::string_view UpstreamLocalAddress = "local_address";
constexpr absl::string_view UpstreamTransportFailureReason = "transport_failure_reason";

// Enumeration of all properties. Any new property symbol must be added here.
class WrapperFieldValues {
public:
using ContainerBackedListImpl = google::api::expr::runtime::ContainerBackedListImpl;
const ContainerBackedListImpl Request{
{CelValue::CreateStringView(Path), CelValue::CreateStringView(UrlPath),
CelValue::CreateStringView(Host), CelValue::CreateStringView(Scheme),
CelValue::CreateStringView(Method), CelValue::CreateStringView(Referer),
CelValue::CreateStringView(Headers), CelValue::CreateStringView(Time),
CelValue::CreateStringView(ID), CelValue::CreateStringView(UserAgent),
CelValue::CreateStringView(Size), CelValue::CreateStringView(TotalSize),
CelValue::CreateStringView(Duration), CelValue::CreateStringView(Protocol)}};
const ContainerBackedListImpl Response{{
CelValue::CreateStringView(Code),
CelValue::CreateStringView(CodeDetails),
CelValue::CreateStringView(Headers),
CelValue::CreateStringView(Trailers),
CelValue::CreateStringView(Flags),
CelValue::CreateStringView(GrpcStatus),
CelValue::CreateStringView(Size),
CelValue::CreateStringView(TotalSize),
}};
const ContainerBackedListImpl Connection{{
CelValue::CreateStringView(MTLS),
CelValue::CreateStringView(RequestedServerName),
CelValue::CreateStringView(ID),
CelValue::CreateStringView(ConnectionTerminationDetails),
CelValue::CreateStringView(TLSVersion),
CelValue::CreateStringView(SubjectLocalCertificate),
CelValue::CreateStringView(SubjectPeerCertificate),
CelValue::CreateStringView(URISanLocalCertificate),
CelValue::CreateStringView(URISanPeerCertificate),
CelValue::CreateStringView(DNSSanLocalCertificate),
CelValue::CreateStringView(DNSSanPeerCertificate),
}};
const ContainerBackedListImpl Upstream{{
CelValue::CreateStringView(Address),
CelValue::CreateStringView(Port),
CelValue::CreateStringView(UpstreamLocalAddress),
CelValue::CreateStringView(UpstreamTransportFailureReason),
CelValue::CreateStringView(TLSVersion),
CelValue::CreateStringView(SubjectLocalCertificate),
CelValue::CreateStringView(SubjectPeerCertificate),
CelValue::CreateStringView(URISanLocalCertificate),
CelValue::CreateStringView(URISanPeerCertificate),
CelValue::CreateStringView(DNSSanLocalCertificate),
CelValue::CreateStringView(DNSSanPeerCertificate),
}};
const ContainerBackedListImpl Peer{{
CelValue::CreateStringView(Address),
CelValue::CreateStringView(Port),
}};
const ContainerBackedListImpl Empty{{}};
};

using WrapperFields = ConstSingleton<WrapperFieldValues>;

class RequestWrapper;

absl::optional<CelValue> convertHeaderEntry(const Http::HeaderEntry* header);
Expand All @@ -99,9 +158,25 @@ template <class T> class HeadersWrapper : public google::api::expr::runtime::Cel
return convertHeaderEntry(
arena_, Http::HeaderUtility::getAllOfHeaderAsString(*value_, Http::LowerCaseString(str)));
}
int size() const override { return value_ == nullptr ? 0 : value_->size(); }
int size() const override { return ListKeys()->size(); }
bool empty() const override { return value_ == nullptr ? true : value_->empty(); }
const google::api::expr::runtime::CelList* ListKeys() const override { return nullptr; }
const google::api::expr::runtime::CelList* ListKeys() const override {
if (value_ == nullptr) {
return &WrapperFields::get().Empty;
}
absl::flat_hash_set<absl::string_view> keys;
value_->iterate([&keys](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate {
keys.insert(header.key().getStringView());
return Http::HeaderMap::Iterate::Continue;
});
std::vector<CelValue> values;
values.reserve(keys.size());
for (const auto& key : keys) {
values.push_back(CelValue::CreateStringView(key));
}
return Protobuf::Arena::Create<google::api::expr::runtime::ContainerBackedListImpl>(&arena_,
values);
}

private:
friend class RequestWrapper;
Expand All @@ -116,9 +191,7 @@ template <class T> class HeadersWrapper : public google::api::expr::runtime::Cel
class BaseWrapper : public google::api::expr::runtime::CelMap,
public google::api::expr::runtime::CelValueProducer {
public:
int size() const override { return 0; }
bool empty() const override { return false; }
const google::api::expr::runtime::CelList* ListKeys() const override { return nullptr; }
int size() const override { return ListKeys()->size(); }
CelValue Produce(ProtobufWkt::Arena* arena) override {
// Producer is unique per evaluation arena since activation is re-created.
arena_ = arena;
Expand All @@ -135,6 +208,9 @@ class RequestWrapper : public BaseWrapper {
const StreamInfo::StreamInfo& info)
: headers_(arena, headers), info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Request;
}

private:
const HeadersWrapper<Http::RequestHeaderMap> headers_;
Expand All @@ -147,6 +223,9 @@ class ResponseWrapper : public BaseWrapper {
const Http::ResponseTrailerMap* trailers, const StreamInfo::StreamInfo& info)
: headers_(arena, headers), trailers_(arena, trailers), info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Response;
}

private:
const HeadersWrapper<Http::ResponseHeaderMap> headers_;
Expand All @@ -158,6 +237,9 @@ class ConnectionWrapper : public BaseWrapper {
public:
ConnectionWrapper(const StreamInfo::StreamInfo& info) : info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Connection;
}

private:
const StreamInfo::StreamInfo& info_;
Expand All @@ -167,6 +249,9 @@ class UpstreamWrapper : public BaseWrapper {
public:
UpstreamWrapper(const StreamInfo::StreamInfo& info) : info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Upstream;
}

private:
const StreamInfo::StreamInfo& info_;
Expand All @@ -176,6 +261,9 @@ class PeerWrapper : public BaseWrapper {
public:
PeerWrapper(const StreamInfo::StreamInfo& info, bool local) : info_(info), local_(local) {}
absl::optional<CelValue> operator[](CelValue key) const override;
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Peer;
}

private:
const StreamInfo::StreamInfo& info_;
Expand All @@ -197,6 +285,10 @@ class FilterStateWrapper : public BaseWrapper {
public:
FilterStateWrapper(const StreamInfo::FilterState& filter_state) : filter_state_(filter_state) {}
absl::optional<CelValue> operator[](CelValue key) const override;
// TODO(kyessenov) FilterState should allow enumeration of keys.
const google::api::expr::runtime::CelList* ListKeys() const override {
return &WrapperFields::get().Empty;
}

private:
const StreamInfo::FilterState& filter_state_;
Expand Down
24 changes: 21 additions & 3 deletions test/extensions/filters/common/expr/context_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ TEST(Context, EmptyHeadersAttributes) {
EXPECT_FALSE(header.has_value());
EXPECT_EQ(0, headers.size());
EXPECT_TRUE(headers.empty());
EXPECT_EQ(0, headers.ListKeys()->size());
}

TEST(Context, InvalidRequest) {
Expand Down Expand Up @@ -62,8 +63,7 @@ TEST(Context, RequestAttributes) {
EXPECT_CALL(info, requestComplete()).WillRepeatedly(Return(dur));
EXPECT_CALL(info, protocol()).WillRepeatedly(Return(Http::Protocol::Http2));

// stub methods
EXPECT_EQ(0, request.size());
EXPECT_EQ(14, request.size());
EXPECT_FALSE(request.empty());

{
Expand Down Expand Up @@ -173,7 +173,8 @@ TEST(Context, RequestAttributes) {
ASSERT_TRUE(value.value().IsMap());
auto& map = *value.value().MapOrDie();
EXPECT_FALSE(map.empty());
EXPECT_EQ(10, map.size());
EXPECT_EQ(9, map.size());
EXPECT_EQ(9, map.ListKeys()->size());

auto header = map[CelValue::CreateStringView(Referer)];
EXPECT_TRUE(header.has_value());
Expand Down Expand Up @@ -257,6 +258,8 @@ TEST(Context, ResponseAttributes) {
const absl::optional<std::string> code_details = "unauthorized";
EXPECT_CALL(info, responseCodeDetails()).WillRepeatedly(ReturnRef(code_details));

EXPECT_EQ(8, response.size());
EXPECT_FALSE(response.empty());
{
auto value = response[CelValue::CreateStringView(Undefined)];
EXPECT_FALSE(value.has_value());
Expand Down Expand Up @@ -483,6 +486,18 @@ TEST(Context, ConnectionAttributes) {
.WillRepeatedly(ReturnRef(subject_peer));
EXPECT_CALL(*upstream_ssl_info, subjectPeerCertificate()).WillRepeatedly(ReturnRef(subject_peer));

EXPECT_EQ(11, connection.size());
EXPECT_FALSE(connection.empty());

EXPECT_EQ(11, upstream.size());
EXPECT_FALSE(connection.empty());

EXPECT_EQ(2, source.size());
EXPECT_FALSE(connection.empty());

EXPECT_EQ(2, destination.size());
EXPECT_FALSE(connection.empty());

{
auto value = connection[CelValue::CreateStringView(Undefined)];
EXPECT_FALSE(value.has_value());
Expand Down Expand Up @@ -699,6 +714,9 @@ TEST(Context, FilterStateAttributes) {
auto accessor = std::make_shared<Envoy::Router::StringAccessorImpl>(serialized);
filter_state.setData(key, accessor, StreamInfo::FilterState::StateType::ReadOnly);

EXPECT_EQ(0, wrapper.size());
EXPECT_TRUE(wrapper.empty());

{
auto value = wrapper[CelValue::CreateStringView(missing)];
EXPECT_FALSE(value.has_value());
Expand Down

0 comments on commit b2a9429

Please sign in to comment.