diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http_filters/lua_filter.rst index 627e6bfc80f56..dae243c4258aa 100644 --- a/docs/root/configuration/http_filters/lua_filter.rst +++ b/docs/root/configuration/http_filters/lua_filter.rst @@ -301,6 +301,17 @@ Returns :repo:`information ` related to the Returns a :ref:`request info object `. +connection() +^^^^^^^^^^^^ + +.. code-block:: lua + + connection = handle:connection() + +Returns the current request's underlying :repo:`connection `. + +Returns a :ref:`connection object `. + .. _config_http_filters_lua_header_wrapper: Header object API @@ -401,7 +412,7 @@ get() metadata:get(key) Gets a metadata. *key* is a string that supplies the metadata key. Returns the corresponding -value of the given metadata key. The type of the value can be: *null*, *boolean*, *number*, +value of the given metadata key. The type of the value can be: *nil*, *boolean*, *number*, *string* and *table*. __pairs() @@ -428,4 +439,27 @@ protocol() requestInfo:protocol() Returns the string representation of :repo:`HTTP protocol ` -used by the current request. The possible values are: *HTTP/1.0*, *HTTP/1.1*, and *HTTP/2*. \ No newline at end of file +used by the current request. The possible values are: *HTTP/1.0*, *HTTP/1.1*, and *HTTP/2*. + +.. _config_http_filters_lua_connection_wrapper: + +Connection object API +--------------------- + +ssl() +^^^^^^^^ + +.. code-block:: lua + + if connection:ssl() == nil then + print("plain") + else + print("secure") + end + +Returns :repo:`SSL connection ` object when the connection is +secured and *nil* when it is not. + +.. note:: + + Currently the SSL connection object has no exposed APIs. diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index dbdb78cb2bdba..61e846f3b642c 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -3,11 +3,12 @@ Version history 1.8.0 (Pending) =============== -* access log: added :ref:`response flag filter ` - to filter based on the presence of Envoy response flags. -* admin: added :http:get:`/hystrix_event_stream` as an endpoint for monitoring envoy's statistics +* access log: added :ref:`response flag filter ` + to filter based on the presence of Envoy response flags. +* admin: added :http:get:`/hystrix_event_stream` as an endpoint for monitoring envoy's statistics through `Hystrix dashboard `_. * http: response filters not applied to early error paths such as http_parser generated 400s. +* lua: added :ref:`connection() ` wrapper and *ssl()* API. * lua: added :ref:`requestInfo() ` wrapper and *protocol()* API. * ratelimit: added support for :repo:`api/envoy/service/ratelimit/v2/rls.proto`. Lyft's reference implementation of the `ratelimit `_ service also supports the data-plane-api proto as of v1.1.0. diff --git a/source/extensions/filters/common/lua/wrappers.cc b/source/extensions/filters/common/lua/wrappers.cc index f431bc012fb15..a8573c05a840a 100644 --- a/source/extensions/filters/common/lua/wrappers.cc +++ b/source/extensions/filters/common/lua/wrappers.cc @@ -125,6 +125,20 @@ int MetadataMapWrapper::luaPairs(lua_State* state) { return 1; } +int ConnectionWrapper::luaSsl(lua_State* state) { + const auto& ssl = connection_->ssl(); + if (ssl != nullptr) { + if (ssl_connection_wrapper_.get() != nullptr) { + ssl_connection_wrapper_.pushStack(); + } else { + ssl_connection_wrapper_.reset(SslConnectionWrapper::create(state, ssl), true); + } + } else { + lua_pushnil(state); + } + return 1; +} + } // namespace Lua } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/common/lua/wrappers.h b/source/extensions/filters/common/lua/wrappers.h index 0cc84deb34bd6..d57a2ec396439 100644 --- a/source/extensions/filters/common/lua/wrappers.h +++ b/source/extensions/filters/common/lua/wrappers.h @@ -99,6 +99,39 @@ class MetadataMapWrapper : public BaseLuaObject { friend class MetadataMapIterator; }; +/** + * Lua wrapper for Ssl::Connection. + */ +class SslConnectionWrapper : public BaseLuaObject { +public: + SslConnectionWrapper(const Ssl::Connection*) {} + static ExportedFunctions exportedFunctions() { return {}; } + + // TODO(dio): Add more Lua APIs around Ssl::Connection. +}; + +/** + * Lua wrapper for Network::Connection. + */ +class ConnectionWrapper : public BaseLuaObject { +public: + ConnectionWrapper(const Network::Connection* connection) : connection_{connection} {} + static ExportedFunctions exportedFunctions() { return {{"ssl", static_luaSsl}}; } + +private: + /** + * Get the Ssl::Connection wrapper + * @return object if secured and nil if not. + */ + DECLARE_LUA_FUNCTION(ConnectionWrapper, luaSsl); + + // Envoy::Lua::BaseLuaObject + void onMarkDead() override { ssl_connection_wrapper_.reset(); } + + const Network::Connection* connection_; + LuaDeathRef ssl_connection_wrapper_; +}; + } // namespace Lua } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index d0c1c705973b6..9bb08186d0112 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -377,6 +377,17 @@ int StreamHandleWrapper::luaRequestInfo(lua_State* state) { return 1; } +int StreamHandleWrapper::luaConnection(lua_State* state) { + ASSERT(state_ == State::Running); + if (connection_wrapper_.get() != nullptr) { + connection_wrapper_.pushStack(); + } else { + connection_wrapper_.reset( + Filters::Common::Lua::ConnectionWrapper::create(state, callbacks_.connection()), true); + } + return 1; +} + int StreamHandleWrapper::luaLogTrace(lua_State* state) { const char* message = luaL_checkstring(state, 2); filter_.scriptLog(spdlog::level::trace, message); @@ -419,6 +430,8 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index d020145811054..d4db8225ea77e 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -74,6 +74,11 @@ class FilterCallbacks { * accomodate write API e.g. setDynamicMetadata(). */ virtual RequestInfo::RequestInfo& requestInfo() PURE; + + /** + * @return const const Network::Connection* the current network connection handle. + */ + virtual const Network::Connection* connection() const PURE; }; class Filter; @@ -128,7 +133,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject headers_wrapper_; Filters::Common::Lua::LuaDeathRef body_wrapper_; Filters::Common::Lua::LuaDeathRef trailers_wrapper_; - Filters::Common::Lua::LuaDeathRef request_info_wrapper_; Filters::Common::Lua::LuaDeathRef metadata_wrapper_; + Filters::Common::Lua::LuaDeathRef request_info_wrapper_; + Filters::Common::Lua::LuaDeathRef connection_wrapper_; State state_{State::Running}; std::function yield_callback_; Http::AsyncClient::Request* http_request_{}; @@ -324,6 +337,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable { const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); } RequestInfo::RequestInfo& requestInfo() override { return callbacks_->requestInfo(); } + const Network::Connection* connection() const override { return callbacks_->connection(); } Filter& parent_; Http::StreamDecoderFilterCallbacks* callbacks_{}; @@ -343,6 +357,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable { const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); } RequestInfo::RequestInfo& requestInfo() override { return callbacks_->requestInfo(); } + const Network::Connection* connection() const override { return callbacks_->connection(); } Filter& parent_; Http::StreamEncoderFilterCallbacks* callbacks_{}; diff --git a/test/extensions/filters/common/lua/BUILD b/test/extensions/filters/common/lua/BUILD index 254c6f4a90c91..8ba2dbcd32ade 100644 --- a/test/extensions/filters/common/lua/BUILD +++ b/test/extensions/filters/common/lua/BUILD @@ -27,6 +27,8 @@ envoy_cc_test( ":lua_wrappers_lib", "//source/common/buffer:buffer_lib", "//source/extensions/filters/common/lua:wrappers_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/common/lua/wrappers_test.cc b/test/extensions/filters/common/lua/wrappers_test.cc index 0bbbc282d0148..a540c49b2965e 100644 --- a/test/extensions/filters/common/lua/wrappers_test.cc +++ b/test/extensions/filters/common/lua/wrappers_test.cc @@ -3,6 +3,8 @@ #include "extensions/filters/common/lua/wrappers.h" #include "test/extensions/filters/common/lua/lua_wrappers.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/test_common/utility.h" namespace Envoy { @@ -27,6 +29,42 @@ class LuaMetadataMapWrapperTest : public LuaWrappersTestBase } }; +class LuaConnectionWrapperTest : public LuaWrappersTestBase { +public: + virtual void setup(const std::string& script) { + LuaWrappersTestBase::setup(script); + state_->registerType(); + } + +protected: + void expectSecureConnection(const bool secure) { + const std::string SCRIPT{R"EOF( + function callMe(object) + if object:ssl() == nil then + testPrint("plain") + else + testPrint("secure") + end + testPrint(type(object:ssl())) + end + )EOF"}; + testing::InSequence s; + setup(SCRIPT); + + // Setup secure connection if required. + EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? &ssl_ : nullptr)); + + ConnectionWrapper::create(coroutine_->luaState(), &connection_); + EXPECT_CALL(*this, testPrint(secure ? "secure" : "plain")); + EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? &ssl_ : nullptr)); + EXPECT_CALL(*this, testPrint(secure ? "userdata" : "nil")); + start("callMe"); + } + + NiceMock connection_; + NiceMock ssl_; +}; + // Basic buffer wrapper methods test. TEST_F(LuaBufferWrapperTest, Methods) { const std::string SCRIPT{R"EOF( @@ -224,6 +262,11 @@ TEST_F(LuaMetadataMapWrapperTest, DontFinishIteration) { "[string \"...\"]:5: cannot create a second iterator before completing the first"); } +TEST_F(LuaConnectionWrapperTest, Secure) { + expectSecureConnection(true); + expectSecureConnection(false); +} + } // namespace Lua } // namespace Common } // namespace Filters diff --git a/test/extensions/filters/http/lua/BUILD b/test/extensions/filters/http/lua/BUILD index 8dcfc1654e4fd..f38ed562875db 100644 --- a/test/extensions/filters/http/lua/BUILD +++ b/test/extensions/filters/http/lua/BUILD @@ -18,6 +18,8 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/lua:lua_filter_lib", "//test/mocks/http:http_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index 844fcf5da33e2..8d37b4b318466 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -4,6 +4,8 @@ #include "extensions/filters/http/lua/lua_filter.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/printers.h" @@ -68,6 +70,11 @@ class LuaHttpFilterTest : public testing::Test { filter_->setEncoderFilterCallbacks(encoder_callbacks_); } + void setupSecureConnection(const bool secure) { + EXPECT_CALL(decoder_callbacks_, connection()).WillOnce(Return(&connection_)); + EXPECT_CALL(Const(connection_), ssl()).Times(1).WillOnce(Return(secure ? &ssl_ : nullptr)); + } + void setupMetadata(const std::string& yaml) { MessageUtil::loadFromYaml(yaml, metadata_); EXPECT_CALL(decoder_callbacks_.route_->route_entry_, metadata()) @@ -81,6 +88,8 @@ class LuaHttpFilterTest : public testing::Test { Http::MockStreamDecoderFilterCallbacks decoder_callbacks_; Http::MockStreamEncoderFilterCallbacks encoder_callbacks_; envoy::api::v2::core::Metadata metadata_; + NiceMock ssl_; + NiceMock connection_; NiceMock request_info_; const std::string HEADER_ONLY_SCRIPT{R"EOF( @@ -1463,7 +1472,7 @@ TEST_F(LuaHttpFilterTest, GetMetadataFromHandleNoLuaMetadata) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } -// No available Lua metadata on route. +// Get the current protocol. TEST_F(LuaHttpFilterTest, GetCurrentProtocol) { const std::string SCRIPT{R"EOF( function envoy_on_request(request_handle) @@ -1482,6 +1491,32 @@ TEST_F(LuaHttpFilterTest, GetCurrentProtocol) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +// Check the connection. +TEST_F(LuaHttpFilterTest, CheckConnection) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + if request_handle:connection():ssl() == nil then + request_handle:logTrace("plain") + else + request_handle:logTrace("secure") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + setupSecureConnection(false); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("plain"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + setupSecureConnection(true); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("secure"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 2ac5b6be06457..353e40b82e069 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -109,6 +109,11 @@ name: envoy.lua request_handle:headers():add("request_body_size", body_length) request_handle:headers():add("request_metadata_foo", metadata["foo"]) request_handle:headers():add("request_metadata_baz", metadata["baz"]) + if request_handle:connection():ssl() == nil then + request_handle:headers():add("request_secure", "false") + else + request_handle:headers():add("request_secure", "true") + end request_handle:headers():add("request_protocol", request_handle:requestInfo():protocol()) end @@ -154,6 +159,9 @@ name: envoy.lua .get(Http::LowerCaseString("request_metadata_baz")) ->value() .c_str()); + EXPECT_STREQ( + "false", + upstream_request_->headers().get(Http::LowerCaseString("request_secure"))->value().c_str()); EXPECT_STREQ( "HTTP/1.1",