Skip to content

Commit 841ad99

Browse files
authored
http filters: fix filter_metadata when used with router and lua filters (envoyproxy#10285)
The HTTP router filter and HTTP LUA filter look up metadata attached to route entries using their well-known-filter names, which recently changed. Extend deprecated filter name warnings to these cases so that existing routes may continue to use the deprecated names. Risk Level: low Testing: add unit tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Stephan Zuercher <[email protected]>
1 parent baf1c85 commit 841ad99

File tree

12 files changed

+265
-42
lines changed

12 files changed

+265
-42
lines changed

include/envoy/registry/registry.h

+2-7
Original file line numberDiff line numberDiff line change
@@ -337,13 +337,8 @@ template <class Base> class FactoryRegistry : public Logger::Loggable<Logger::Id
337337
auto it = deprecatedFactoryNames().find(name);
338338
const bool deprecated = it != deprecatedFactoryNames().end();
339339
if (deprecated) {
340-
auto status = Extensions::Common::Utility::ExtensionNameUtil::deprecatedExtensionNameStatus();
341-
if (status == Extensions::Common::Utility::ExtensionNameUtil::Status::Block) {
342-
ENVOY_LOG(error, "{} is deprecated and disabled, use {} instead.", it->first, it->second);
343-
return false;
344-
}
345-
346-
ENVOY_LOG(warn, "{} is deprecated, use {} instead.", it->first, it->second);
340+
return Extensions::Common::Utility::ExtensionNameUtil::allowDeprecatedExtensionName(
341+
"", it->first, it->second);
347342
}
348343

349344
return true;

source/common/router/config_impl.cc

+12-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ convertInternalRedirectAction(const envoy::config::route::v3::RouteAction& route
5757
}
5858
}
5959

60+
const std::string DEPRECATED_ROUTER_NAME = "envoy.router";
61+
6062
} // namespace
6163

6264
std::string SslRedirector::newPath(const Http::RequestHeaderMap& headers) const {
@@ -647,10 +649,18 @@ std::multimap<std::string, std::string>
647649
RouteEntryImplBase::parseOpaqueConfig(const envoy::config::route::v3::Route& route) {
648650
std::multimap<std::string, std::string> ret;
649651
if (route.has_metadata()) {
650-
const auto filter_metadata = route.metadata().filter_metadata().find(
652+
auto filter_metadata = route.metadata().filter_metadata().find(
651653
Extensions::HttpFilters::HttpFilterNames::get().Router);
652654
if (filter_metadata == route.metadata().filter_metadata().end()) {
653-
return ret;
655+
// TODO(zuercher): simply return `ret` when deprecated filter names are removed.
656+
filter_metadata = route.metadata().filter_metadata().find(DEPRECATED_ROUTER_NAME);
657+
if (filter_metadata == route.metadata().filter_metadata().end()) {
658+
return ret;
659+
}
660+
661+
Extensions::Common::Utility::ExtensionNameUtil::checkDeprecatedExtensionName(
662+
"http filter", DEPRECATED_ROUTER_NAME,
663+
Extensions::HttpFilters::HttpFilterNames::get().Router);
654664
}
655665
for (const auto& it : filter_metadata->second.fields()) {
656666
if (it.second.kind_case() == ProtobufWkt::Value::kStringValue) {

source/extensions/common/utility.h

+61-13
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ class ExtensionNameUtil {
2020
enum class Status { Warn, Block };
2121

2222
/**
23-
* Returns the status of deprecated extension names.
23+
* Checks the status of deprecated extension names and increments the deprecated feature stats
24+
* counter if deprecated names are allowed.
2425
*
25-
* @return Status indicating whether deprecated names trigger warnings or are blocked.
26+
* @param runtime Runtime::Loader used to determine if deprecated extension names are allowed.
27+
* @return Status::Warn (allowed, warn) or Status::Block (disallowed, error)
2628
*/
2729
static Status deprecatedExtensionNameStatus(
2830
Runtime::Loader* runtime = Runtime::LoaderSingleton::getExisting()) {
29-
3031
#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES
3132
UNREFERENCED_PARAMETER(runtime);
3233
return Status::Block;
@@ -43,9 +44,42 @@ class ExtensionNameUtil {
4344
}
4445

4546
/**
46-
* Checks the status of deprecated extension and either generates a log message or throws.
47-
* The passed strings are used only to generate the log or exception message.
47+
* Checks the status of deprecated extension names. If deprecated extension names are allowed,
48+
* it increments the deprecated feature stats counter. Generates a warning or error log message
49+
* based on whether the name is allowed (warning) or not (error). The string parameters are used
50+
* only to generate the log message.
51+
*
52+
* @param extension_type absl::string_view that contains the extension type, for logging
53+
* @param deprecated_name absl::string_view that contains the deprecated name, for logging
54+
* @param canonical_name absl::string_view that contains the canonical name, for logging
55+
* @param runtime Runtime::Loader used to determine if deprecated extension names are allowed.
56+
* @return true if deprecated extensions are allowed, false otherwise.
57+
*/
58+
static bool
59+
allowDeprecatedExtensionName(absl::string_view extension_type, absl::string_view deprecated_name,
60+
absl::string_view canonical_name,
61+
Runtime::Loader* runtime = Runtime::LoaderSingleton::getExisting()) {
62+
auto status = deprecatedExtensionNameStatus(runtime);
63+
64+
if (status == Status::Warn) {
65+
ENVOY_LOG_MISC(warn, "{}", message(extension_type, deprecated_name, canonical_name));
66+
return true;
67+
}
68+
69+
ENVOY_LOG_MISC(error, "{}", fatalMessage(extension_type, deprecated_name, canonical_name));
70+
return false;
71+
}
72+
73+
/**
74+
* Checks the status of deprecated extension names. If deprecated extension names are allowed,
75+
* it increments the deprecated feature stats counter and generates a log message. If not allowed,
76+
* an exception is thrown. The passed strings are used only to generate the log or exception
77+
* message.
4878
*
79+
* @param extension_type absl::string_view that contains the extension type, for logging
80+
* @param deprecated_name absl::string_view that contains the deprecated name, for logging
81+
* @param canonical_name absl::string_view that contains the canonical name, for logging
82+
* @param runtime Runtime::Loader used to determine if deprecated extension names are allowed.
4983
* @throw EnvoyException if the use of deprecated extension names is not allowed.
5084
*/
5185
static void
@@ -54,24 +88,38 @@ class ExtensionNameUtil {
5488
Runtime::Loader* runtime = Runtime::LoaderSingleton::getExisting()) {
5589
auto status = deprecatedExtensionNameStatus(runtime);
5690

57-
std::string err = fmt::format(
58-
"Using deprecated {} extension name '{}' for '{}'. This name will be removed from Envoy "
59-
"soon. Please see "
60-
"https://www.envoyproxy.io/docs/envoy/latest/intro/deprecated for details.",
61-
extension_type, deprecated_name, canonical_name);
62-
6391
if (status == Status::Warn) {
64-
ENVOY_LOG_MISC(warn, "{}", err);
92+
ENVOY_LOG_MISC(warn, "{}", message(extension_type, deprecated_name, canonical_name));
6593
return;
6694
}
6795

96+
throw EnvoyException(fatalMessage(extension_type, deprecated_name, canonical_name));
97+
}
98+
99+
private:
100+
static std::string message(absl::string_view extension_type, absl::string_view deprecated_name,
101+
absl::string_view canonical_name) {
102+
absl::string_view spacing = extension_type.empty() ? "" : " ";
103+
104+
return fmt::format(
105+
"Using deprecated {}{}extension name '{}' for '{}'. This name will be removed from Envoy "
106+
"soon. Please see "
107+
"https://www.envoyproxy.io/docs/envoy/latest/intro/deprecated for details.",
108+
extension_type, spacing, deprecated_name, canonical_name);
109+
}
110+
111+
static std::string fatalMessage(absl::string_view extension_type,
112+
absl::string_view deprecated_name,
113+
absl::string_view canonical_name) {
114+
std::string err = message(extension_type, deprecated_name, canonical_name);
115+
68116
const char fatal_error[] =
69117
" If continued use of this filter name is absolutely necessary, see "
70118
"https://www.envoyproxy.io/docs/envoy/latest/configuration/operations/runtime"
71119
"#using-runtime-overrides-for-deprecated-features for how to apply a temporary and "
72120
"highly discouraged override.";
73121

74-
throw EnvoyException(err + fatal_error);
122+
return err + fatal_error;
75123
}
76124
};
77125

source/extensions/filters/http/lua/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ envoy_cc_library(
2525
"//source/common/common:enum_to_int",
2626
"//source/common/crypto:utility_lib",
2727
"//source/common/http:message_lib",
28+
"//source/extensions/common:utility_lib",
2829
"//source/extensions/filters/common/lua:lua_lib",
2930
"//source/extensions/filters/common/lua:wrappers_lib",
3031
"//source/extensions/filters/http:well_known_names",

source/extensions/filters/http/lua/lua_filter.cc

+60
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "extensions/filters/http/lua/lua_filter.h"
22

3+
#include <atomic>
34
#include <memory>
45

56
#include "envoy/http/codes.h"
@@ -16,6 +17,57 @@ namespace HttpFilters {
1617
namespace Lua {
1718

1819
namespace {
20+
21+
const std::string DEPRECATED_LUA_NAME = "envoy.lua";
22+
23+
std::atomic<bool>& deprecatedNameLogged() {
24+
MUTABLE_CONSTRUCT_ON_FIRST_USE(std::atomic<bool>, false);
25+
}
26+
27+
// Checks if deprecated metadata names are allowed. On the first check only it will log either
28+
// a warning (indicating the name should be updated) or an error (the feature is off and the
29+
// name is not allowed). When warning, the deprecated feature stat is incremented. Subsequent
30+
// checks do not log since this check is done in potentially high-volume request paths.
31+
bool allowDeprecatedMetadataName() {
32+
if (!deprecatedNameLogged().exchange(true)) {
33+
// Have not logged yet, so use the logging test.
34+
return Extensions::Common::Utility::ExtensionNameUtil::allowDeprecatedExtensionName(
35+
"http filter", DEPRECATED_LUA_NAME, Extensions::HttpFilters::HttpFilterNames::get().Lua);
36+
}
37+
38+
// We have logged (or another thread will do so momentarily), so just check whether the
39+
// deprecated name is allowed.
40+
auto status = Extensions::Common::Utility::ExtensionNameUtil::deprecatedExtensionNameStatus();
41+
return status == Extensions::Common::Utility::ExtensionNameUtil::Status::Warn;
42+
}
43+
44+
const ProtobufWkt::Struct& getMetadata(Http::StreamFilterCallbacks* callbacks) {
45+
if (callbacks->route() == nullptr || callbacks->route()->routeEntry() == nullptr) {
46+
return ProtobufWkt::Struct::default_instance();
47+
}
48+
const auto& metadata = callbacks->route()->routeEntry()->metadata();
49+
50+
{
51+
const auto& filter_it = metadata.filter_metadata().find(HttpFilterNames::get().Lua);
52+
if (filter_it != metadata.filter_metadata().end()) {
53+
return filter_it->second;
54+
}
55+
}
56+
57+
// TODO(zuercher): Remove this block when deprecated filter names are removed.
58+
{
59+
const auto& filter_it = metadata.filter_metadata().find(DEPRECATED_LUA_NAME);
60+
if (filter_it != metadata.filter_metadata().end()) {
61+
// Use the non-throwing check here because this happens at request time.
62+
if (allowDeprecatedMetadataName()) {
63+
return filter_it->second;
64+
}
65+
}
66+
}
67+
68+
return ProtobufWkt::Struct::default_instance();
69+
}
70+
1971
// Okay to return non-const reference because this doesn't ever get changed.
2072
NoopCallbacks& noopCallbacks() {
2173
static NoopCallbacks* callbacks = new NoopCallbacks();
@@ -646,13 +698,21 @@ void Filter::DecoderCallbacks::respond(Http::ResponseHeaderMapPtr&& headers, Buf
646698
}
647699
}
648700

701+
const ProtobufWkt::Struct& Filter::DecoderCallbacks::metadata() const {
702+
return getMetadata(callbacks_);
703+
}
704+
649705
void Filter::EncoderCallbacks::respond(Http::ResponseHeaderMapPtr&&, Buffer::Instance*,
650706
lua_State* state) {
651707
// TODO(mattklein123): Support response in response path if nothing has been continued
652708
// yet.
653709
luaL_error(state, "respond not currently supported in the response path");
654710
}
655711

712+
const ProtobufWkt::Struct& Filter::EncoderCallbacks::metadata() const {
713+
return getMetadata(callbacks_);
714+
}
715+
656716
} // namespace Lua
657717
} // namespace HttpFilters
658718
} // namespace Extensions

source/extensions/filters/http/lua/lua_filter.h

+3-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "common/crypto/utility.h"
77

8+
#include "extensions/common/utility.h"
89
#include "extensions/filters/common/lua/wrappers.h"
910
#include "extensions/filters/http/lua/wrappers.h"
1011
#include "extensions/filters/http/well_known_names.h"
@@ -14,20 +15,6 @@ namespace Extensions {
1415
namespace HttpFilters {
1516
namespace Lua {
1617

17-
namespace {
18-
const ProtobufWkt::Struct& getMetadata(Http::StreamFilterCallbacks* callbacks) {
19-
if (callbacks->route() == nullptr || callbacks->route()->routeEntry() == nullptr) {
20-
return ProtobufWkt::Struct::default_instance();
21-
}
22-
const auto& metadata = callbacks->route()->routeEntry()->metadata();
23-
const auto& filter_it = metadata.filter_metadata().find(HttpFilterNames::get().Lua);
24-
if (filter_it == metadata.filter_metadata().end()) {
25-
return ProtobufWkt::Struct::default_instance();
26-
}
27-
return filter_it->second;
28-
}
29-
} // namespace
30-
3118
/**
3219
* Callbacks used by a stream handler to access the filter.
3320
*/
@@ -391,7 +378,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable<Logger::Id::lua> {
391378
void respond(Http::ResponseHeaderMapPtr&& headers, Buffer::Instance* body,
392379
lua_State* state) override;
393380

394-
const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); }
381+
const ProtobufWkt::Struct& metadata() const override;
395382
StreamInfo::StreamInfo& streamInfo() override { return callbacks_->streamInfo(); }
396383
const Network::Connection* connection() const override { return callbacks_->connection(); }
397384

@@ -412,7 +399,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable<Logger::Id::lua> {
412399
void respond(Http::ResponseHeaderMapPtr&& headers, Buffer::Instance* body,
413400
lua_State* state) override;
414401

415-
const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); }
402+
const ProtobufWkt::Struct& metadata() const override;
416403
StreamInfo::StreamInfo& streamInfo() override { return callbacks_->streamInfo(); }
417404
const Network::Connection* connection() const override { return callbacks_->connection(); }
418405

test/common/config/registry_test.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ TEST(RegistryTest, DEPRECATED_FEATURE_TEST(WithDeprecatedFactoryPublished)) {
9595
"testing.published.deprecated_name")
9696
->name());
9797
EXPECT_LOG_CONTAINS("warn",
98-
fmt::format("{} is deprecated, use {} instead.",
98+
fmt::format("Using deprecated extension name '{}' for '{}'.",
9999
"testing.published.deprecated_name",
100100
"testing.published.instead_name"),
101101
Envoy::Registry::FactoryRegistry<PublishedFactory>::getFactory(
@@ -152,7 +152,7 @@ TEST(RegistryTest, DEPRECATED_FEATURE_TEST(VersionedWithDeprecatedNamesFactory))
152152
"testing.published.versioned.deprecated_name")
153153
->name());
154154
EXPECT_LOG_CONTAINS("warn",
155-
fmt::format("{} is deprecated, use {} instead.",
155+
fmt::format("Using deprecated extension name '{}' for '{}'.",
156156
"testing.published.versioned.deprecated_name",
157157
"testing.published.versioned.instead_name"),
158158
Envoy::Registry::FactoryRegistry<PublishedFactory>::getFactory(

test/common/router/config_impl_test.cc

+28
Original file line numberDiff line numberDiff line change
@@ -4857,6 +4857,34 @@ TEST_F(RouteMatcherTest, TestOpaqueConfig) {
48574857
EXPECT_EQ(opaque_config.find("name2")->second, "value2");
48584858
}
48594859

4860+
// Test that the deprecated name works for opaque configs.
4861+
TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestOpaqueConfigUsingDeprecatedName)) {
4862+
const std::string yaml = R"EOF(
4863+
virtual_hosts:
4864+
- name: default
4865+
domains:
4866+
- "*"
4867+
routes:
4868+
- match:
4869+
prefix: "/api"
4870+
route:
4871+
cluster: ats
4872+
metadata:
4873+
filter_metadata:
4874+
envoy.router:
4875+
name1: value1
4876+
name2: value2
4877+
)EOF";
4878+
4879+
TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true);
4880+
4881+
const std::multimap<std::string, std::string>& opaque_config =
4882+
config.route(genHeaders("api.lyft.com", "/api", "GET"), 0)->routeEntry()->opaqueConfig();
4883+
4884+
EXPECT_EQ(opaque_config.find("name1")->second, "value1");
4885+
EXPECT_EQ(opaque_config.find("name2")->second, "value2");
4886+
}
4887+
48604888
class RoutePropertyTest : public testing::Test, public ConfigImplTestBase {};
48614889

48624890
TEST_F(RoutePropertyTest, ExcludeVHRateLimits) {

0 commit comments

Comments
 (0)