Skip to content
Merged
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
13 changes: 12 additions & 1 deletion docs/root/configuration/http/http_filters/lua_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ verifySignature()

.. code-block:: lua

local ok, error = verifySignature(hashFunction, pubkey, signature, signatureLength, data, dataLength)
local ok, error = handle:verifySignature(hashFunction, pubkey, signature, signatureLength, data, dataLength)

Verify signature using provided parameters. *hashFunction* is the variable for the hash function which be used
for verifying signature. *SHA1*, *SHA224*, *SHA256*, *SHA384* and *SHA512* are supported.
Expand All @@ -512,6 +512,17 @@ Encodes the input string as base64. This can be useful for escaping binary data.

.. _config_http_filters_lua_header_wrapper:

timestamp()
^^^^^^^^^^^

.. code-block:: lua

timestamp = handle:timestamp(format)

High resolution timestamp function. *format* is an optional enum parameter to indicate the format of the timestamp.
*EnvoyTimestampResolution.MILLISECOND* is supported
The function returns timestamp in milliseconds since epoch by default if format is not set.

Header object API
-----------------

Expand Down
2 changes: 2 additions & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Minor Behavior Changes
* http: upstream flood and abuse checks increment the count of opened HTTP/2 streams when Envoy sends
initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received
response HEADERS frame with the END_HEADERS flag set from upstream server.
* lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`.
* oauth filter: added the optional parameter :ref:`auth_scopes <envoy_v3_api_field_extensions.filters.http.oauth2.v3alpha.OAuth2Config.auth_scopes>` with default value of 'user' if not provided. Enables this value to be overridden in the Authorization request to the OAuth provider.
* perf: allow reading more bytes per operation from raw sockets to improve performance.
* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats <config_http_conn_man_headers_custom_request_headers>`.
Expand Down Expand Up @@ -179,3 +180,4 @@ Deprecated
----------

* admin: :ref:`access_log_path <envoy_v3_api_field_config.bootstrap.v3.Admin.access_log_path>` is deprecated in favor for :ref:`access loggers <envoy_v3_api_msg_config.accesslog.v3.AccessLog>`.

8 changes: 6 additions & 2 deletions source/extensions/filters/common/lua/lua.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,14 @@ int ThreadLocalState::getGlobalRef(uint64_t slot) {
return tls.global_slots_[slot];
}

uint64_t ThreadLocalState::registerGlobal(const std::string& global) {
tls_slot_->runOnAllThreads([global](OptRef<LuaThreadLocal> tls) {
uint64_t ThreadLocalState::registerGlobal(const std::string& global,
const InitializerList& initializers) {
tls_slot_->runOnAllThreads([global, initializers](OptRef<LuaThreadLocal> tls) {
lua_getglobal(tls->state_.get(), global.c_str());
if (lua_isfunction(tls->state_.get(), -1)) {
for (const auto& initialize : initializers) {
initialize(tls->state_.get());
}
tls->global_slots_.push_back(luaL_ref(tls->state_.get(), LUA_REGISTRYINDEX));
} else {
ENVOY_LOG(debug, "definition for '{}' not found in script", global);
Expand Down
13 changes: 12 additions & 1 deletion source/extensions/filters/common/lua/lua.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ namespace Lua {
*/
#define DECLARE_LUA_CLOSURE(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, lua_upvalueindex(1))

/**
* Declare a Lua function in which values are added to a table to approximate an enum.
*/
#define LUA_ENUM(state, name, val) \
lua_pushlstring(state, #name, sizeof(#name) - 1); \
lua_pushnumber(state, val); \
lua_settable(state, -3);

/**
* Calculate the maximum space needed to be aligned.
*/
Expand Down Expand Up @@ -352,6 +360,8 @@ class Coroutine : Logger::Loggable<Logger::Id::lua> {
};

using CoroutinePtr = std::unique_ptr<Coroutine>;
using Initializer = std::function<void(lua_State*)>;
using InitializerList = std::vector<Initializer>;

/**
* This class wraps a Lua state that can be used safely across threads. The model is that every
Expand All @@ -377,9 +387,10 @@ class ThreadLocalState : Logger::Loggable<Logger::Id::lua> {
/**
* Register a global for later use.
* @param global supplies the name of the global.
* @param initializers supplies a collection of initializers.
* @return a slot/index for later use with getGlobalRef().
*/
uint64_t registerGlobal(const std::string& global);
uint64_t registerGlobal(const std::string& global, const InitializerList& initializers);

/**
* Register a type with the thread local state. After this call the type will be available on
Expand Down
5 changes: 3 additions & 2 deletions source/extensions/filters/http/lua/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Http::FilterFactoryCb LuaFilterConfig::createFilterFactoryFromProtoTyped(
Server::Configuration::FactoryContext& context) {
FilterConfigConstSharedPtr filter_config(new FilterConfig{
proto_config, context.threadLocal(), context.clusterManager(), context.api()});
return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(std::make_shared<Filter>(filter_config));
auto& time_source = context.dispatcher().timeSource();
return [filter_config, &time_source](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(std::make_shared<Filter>(filter_config, time_source));
};
}

Expand Down
47 changes: 41 additions & 6 deletions source/extensions/filters/http/lua/lua_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Http::AsyncClient::Request* makeHttpCall(lua_State* state, Filter& filter,
const auto thread_local_cluster = filter.clusterManager().getThreadLocalCluster(cluster);
if (thread_local_cluster == nullptr) {
luaL_error(state, "http call cluster invalid. Must be configured");
return nullptr;
}

auto headers = Http::RequestHeaderMapImpl::create();
Expand All @@ -148,6 +149,7 @@ Http::AsyncClient::Request* makeHttpCall(lua_State* state, Filter& filter,
auto options = Http::AsyncClient::RequestOptions().setTimeout(timeout).setParentSpan(parent_span);
return thread_local_cluster->httpAsyncClient().send(std::move(message), callbacks, options);
}

} // namespace

PerLuaCodeSetup::PerLuaCodeSetup(const std::string& lua_code, ThreadLocal::SlotAllocator& tls)
Expand All @@ -165,26 +167,38 @@ PerLuaCodeSetup::PerLuaCodeSetup(const std::string& lua_code, ThreadLocal::SlotA
lua_state_.registerType<StreamHandleWrapper>();
lua_state_.registerType<PublicKeyWrapper>();

request_function_slot_ = lua_state_.registerGlobal("envoy_on_request");
const Filters::Common::Lua::InitializerList initializers(
// EnvoyTimestampResolution "enum".
{
[](lua_State* state) {
lua_newtable(state);
{ LUA_ENUM(state, MILLISECOND, Timestamp::Resolution::Millisecond); }
lua_setglobal(state, "EnvoyTimestampResolution");
},
// Add more initializers here.
});

request_function_slot_ = lua_state_.registerGlobal("envoy_on_request", initializers);
if (lua_state_.getGlobalRef(request_function_slot_) == LUA_REFNIL) {
ENVOY_LOG(info, "envoy_on_request() function not found. Lua filter will not hook requests.");
}

response_function_slot_ = lua_state_.registerGlobal("envoy_on_response");
response_function_slot_ = lua_state_.registerGlobal("envoy_on_response", initializers);
if (lua_state_.getGlobalRef(response_function_slot_) == LUA_REFNIL) {
ENVOY_LOG(info, "envoy_on_response() function not found. Lua filter will not hook responses.");
}
}

StreamHandleWrapper::StreamHandleWrapper(Filters::Common::Lua::Coroutine& coroutine,
Http::HeaderMap& headers, bool end_stream, Filter& filter,
FilterCallbacks& callbacks)
FilterCallbacks& callbacks, TimeSource& time_source)
: coroutine_(coroutine), headers_(headers), end_stream_(end_stream), filter_(filter),
callbacks_(callbacks), yield_callback_([this]() {
if (state_ == State::Running) {
throw Filters::Common::Lua::LuaException("script performed an unexpected yield");
}
}) {}
}),
time_source_(time_source) {}

Http::FilterHeadersStatus StreamHandleWrapper::start(int function_ref) {
// We are on the top of the stack.
Expand Down Expand Up @@ -314,7 +328,7 @@ int StreamHandleWrapper::luaHttpCall(lua_State* state) {

int StreamHandleWrapper::doSynchronousHttpCall(lua_State* state, Tracing::Span& span) {
http_request_ = makeHttpCall(state, filter_, span, *this);
if (http_request_) {
if (http_request_ != nullptr) {
state_ = State::HttpCall;
return lua_yield(state, 0);
} else {
Expand Down Expand Up @@ -643,6 +657,27 @@ int StreamHandleWrapper::luaBase64Escape(lua_State* state) {
return 1;
}

int StreamHandleWrapper::luaTimestamp(lua_State* state) {
auto now = time_source_.systemTime().time_since_epoch();

absl::string_view unit_parameter = luaL_optstring(state, 2, "");

auto milliseconds_since_epoch =
std::chrono::duration_cast<std::chrono::milliseconds>(now).count();

absl::uint128 resolution_as_int_from_state = 0;
if (unit_parameter.empty()) {
lua_pushnumber(state, milliseconds_since_epoch);
} else if (absl::SimpleAtoi(unit_parameter, &resolution_as_int_from_state) &&
resolution_as_int_from_state == enumToInt(Timestamp::Resolution::Millisecond)) {
lua_pushnumber(state, milliseconds_since_epoch);
} else {
luaL_error(state, "timestamp format must be MILLISECOND.");
}

return 1;
}

FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua& proto_config,
ThreadLocal::SlotAllocator& tls,
Upstream::ClusterManager& cluster_manager, Api::Api& api)
Expand Down Expand Up @@ -697,7 +732,7 @@ Http::FilterHeadersStatus Filter::doHeaders(StreamHandleRef& handle,
coroutine = setup->createCoroutine();

handle.reset(StreamHandleWrapper::create(coroutine->luaState(), *coroutine, headers, end_stream,
*this, callbacks),
*this, callbacks, time_source_),
true);

Http::FilterHeadersStatus status = Http::FilterHeadersStatus::Continue;
Expand Down
21 changes: 18 additions & 3 deletions source/extensions/filters/http/lua/lua_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
};

StreamHandleWrapper(Filters::Common::Lua::Coroutine& coroutine, Http::HeaderMap& headers,
bool end_stream, Filter& filter, FilterCallbacks& callbacks);
bool end_stream, Filter& filter, FilterCallbacks& callbacks,
TimeSource& time_source);

Http::FilterHeadersStatus start(int function_ref);
Http::FilterDataStatus onData(Buffer::Instance& data, bool end_stream);
Expand Down Expand Up @@ -167,7 +168,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
{"connection", static_luaConnection},
{"importPublicKey", static_luaImportPublicKey},
{"verifySignature", static_luaVerifySignature},
{"base64Escape", static_luaBase64Escape}};
{"base64Escape", static_luaBase64Escape},
{"timestamp", static_luaTimestamp}};
}

private:
Expand Down Expand Up @@ -277,9 +279,19 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
*/
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaBase64Escape);

/**
* Timestamp.
* @param1 (string) optional format (e.g. milliseconds_from_epoch, nanoseconds_from_epoch).
* Defaults to milliseconds_from_epoch.
* @return timestamp
*/
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaTimestamp);

int doSynchronousHttpCall(lua_State* state, Tracing::Span& span);
int doAsynchronousHttpCall(lua_State* state, Tracing::Span& span);

int timestamp(int timestamp, absl::uint128 resolution);

// Filters::Common::Lua::BaseLuaObject
void onMarkDead() override {
// Headers/body/trailers wrappers do not survive any yields. The user can request them
Expand Down Expand Up @@ -317,6 +329,7 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
State state_{State::Running};
std::function<void()> yield_callback_;
Http::AsyncClient::Request* http_request_{};
TimeSource& time_source_;

// The inserted crypto object pointers will not be removed from this map.
absl::flat_hash_map<std::string, Envoy::Common::Crypto::CryptoObjectPtr> public_key_storage_;
Expand Down Expand Up @@ -425,7 +438,8 @@ PerLuaCodeSetup* getPerLuaCodeSetup(const FilterConfig* filter_config,
*/
class Filter : public Http::StreamFilter, Logger::Loggable<Logger::Id::lua> {
public:
Filter(FilterConfigConstSharedPtr config) : config_(config) {}
Filter(FilterConfigConstSharedPtr config, TimeSource& time_source)
: config_(config), time_source_(time_source) {}

Upstream::ClusterManager& clusterManager() { return config_->cluster_manager_; }
void scriptError(const Filters::Common::Lua::LuaException& e);
Expand Down Expand Up @@ -537,6 +551,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable<Logger::Id::lua> {
StreamHandleRef request_stream_wrapper_;
StreamHandleRef response_stream_wrapper_;
bool destroyed_{};
TimeSource& time_source_;

// These coroutines used to be owned by the stream handles. After investigating #3570, it
// became clear that there is a circular memory reference when a coroutine yields. Basically,
Expand Down
5 changes: 5 additions & 0 deletions source/extensions/filters/http/lua/wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ class PublicKeyWrapper : public Filters::Common::Lua::BaseLuaObject<PublicKeyWra
const std::string public_key_;
};

class Timestamp {
public:
enum Resolution { Millisecond };
};

} // namespace Lua
} // namespace HttpFilters
} // namespace Extensions
Expand Down
12 changes: 7 additions & 5 deletions test/extensions/filters/common/lua/lua_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class LuaTest : public testing::Test {
ThreadLocalStatePtr state_;
std::function<void()> yield_callback_;
ReadyWatcher on_yield_;
InitializerList initializers_;
};

// Basic ref counting between coroutines.
Expand All @@ -62,8 +63,8 @@ TEST_F(LuaTest, CoroutineRefCounting) {

InSequence s;
setup(SCRIPT);
EXPECT_EQ(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("not here")));
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMe")));
EXPECT_EQ(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("not here", initializers_)));
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMe", initializers_)));

// Start a coroutine but do not hold a reference to the object we pass.
CoroutinePtr cr1(state_->createCoroutine());
Expand Down Expand Up @@ -97,7 +98,7 @@ TEST_F(LuaTest, YieldAndResume) {

InSequence s;
setup(SCRIPT);
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMe")));
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMe", initializers_)));

CoroutinePtr cr(state_->createCoroutine());
LuaRef<TestObject> ref(TestObject::create(cr->luaState()), true);
Expand Down Expand Up @@ -132,8 +133,9 @@ TEST_F(LuaTest, MarkDead) {

InSequence s;
setup(SCRIPT);
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMeFirst")));
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMeSecond")));
EXPECT_NE(LUA_REFNIL, state_->getGlobalRef(state_->registerGlobal("callMeFirst", initializers_)));
EXPECT_NE(LUA_REFNIL,
state_->getGlobalRef(state_->registerGlobal("callMeSecond", initializers_)));

CoroutinePtr cr1(state_->createCoroutine());
LuaDeathRef<TestObject> ref(TestObject::create(cr1->luaState()), true);
Expand Down
4 changes: 3 additions & 1 deletion test/extensions/filters/common/lua/lua_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ template <class T> class LuaWrappersTestBase : public testing::Test {
void TearDown() override { testing::Mock::VerifyAndClear(&printer_); }

void start(const std::string& method) {
coroutine_->start(state_->getGlobalRef(state_->registerGlobal(method)), 1, yield_callback_);
coroutine_->start(state_->getGlobalRef(state_->registerGlobal(method, initializers_)), 1,
yield_callback_);
}

static int luaTestPrint(lua_State* state) {
Expand All @@ -53,6 +54,7 @@ template <class T> class LuaWrappersTestBase : public testing::Test {
std::function<void()> yield_callback_;
CoroutinePtr coroutine_;
Printer& printer_{getPrinter()};
InitializerList initializers_;
};

} // namespace Lua
Expand Down
Loading