diff --git a/bazel/foreign_cc/luajit.patch b/bazel/foreign_cc/luajit.patch index 15c65c3672fb4..c39f116aabbb3 100644 --- a/bazel/foreign_cc/luajit.patch +++ b/bazel/foreign_cc/luajit.patch @@ -65,8 +65,8 @@ index 0000000..9c71271 + + os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.6" + os.environ["DEFAULT_CC"] = os.environ.get("CC", "") -+ os.environ["TARGET_CFLAGS"] = os.environ.get("CFLAGS", "") -+ os.environ["TARGET_LDFLAGS"] = os.environ.get("CFLAGS", "") ++ os.environ["TARGET_CFLAGS"] = os.environ.get("CFLAGS", "") + " -fno-function-sections -fno-data-sections" ++ os.environ["TARGET_LDFLAGS"] = os.environ.get("CFLAGS", "") + " -fno-function-sections -fno-data-sections" + os.environ["CFLAGS"] = "" + # LuaJIT compile process build a tool `buildvm` and use it, building `buildvm` with ASAN + # will cause LSAN detect its leak and fail the build, set exitcode to 0 to make LSAN doesn't diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http_filters/lua_filter.rst index 815e5aef96322..23f4d2a65ad6b 100644 --- a/docs/root/configuration/http_filters/lua_filter.rst +++ b/docs/root/configuration/http_filters/lua_filter.rst @@ -312,6 +312,32 @@ Returns the current request's underlying :repo:`connection `. +importPublicKey() +^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + pubkey = handle:importPublicKey(keyder, keyderLength) + +Returns public key which is used by :ref:`verifySignature ` to verify digital signature. + +.. _verify_signature: + +verifySignature() +^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + ok, error = verifySignature(hashFunction, pubkey, signature, signatureLength, data, dataLength) + +Verify signature using provided parameters. *hashFunction* is the variable for hash function which be used +for verifying signature. *SHA1*, *SHA224*, *SHA256*, *SHA384* and *SHA512* are supported. +*pubkey* is the public key. *signature* is the signature to be verified. *signatureLength* is +the length of the signature. *data* is the content which will be hashed. *dataLength* is the length of data. + +The function returns a pair. If the first element is *true*, the second element will be empty +which means signature is verified; otherwise, the second element will store the error message. + .. _config_http_filters_lua_header_wrapper: Header object API diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index fa875e0ebe772..220514b18cf13 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -21,6 +21,12 @@ Version history * http: fixed a crashing bug where gRPC local replies would cause segfaults when upstream access logging was on. * http: mitigated a race condition with the :ref:`delayed_close_timeout` where it could trigger while actively flushing a pending write buffer for a downstream connection. * jwt_authn: make filter's parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` +* listener: added :ref:`source IP ` + and :ref:`source port ` filter + chain matching. +* lua: exposed functions to Lua to verify digital signature. +* original_src filter: added the :ref:`filter`. +* rbac: migrated from v2alpha to v2. * redis: add support for Redis cluster custom cluster type. * redis: added :ref:`prefix routing ` to enable routing commands based on their key's prefix to different upstream. * redis: add support for zpopmax and zpopmin commands. diff --git a/source/common/crypto/utility.cc b/source/common/crypto/utility.cc index 885b4c0bbcaba..fbaff483eb3e2 100644 --- a/source/common/crypto/utility.cc +++ b/source/common/crypto/utility.cc @@ -3,7 +3,9 @@ #include "common/common/assert.h" #include "common/common/stack_array.h" -#include "openssl/evp.h" +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" @@ -41,6 +43,61 @@ std::vector Utility::getSha256Hmac(const std::vector& key, return hmac; } +const VerificationOutput Utility::verifySignature(absl::string_view hash, EVP_PKEY* pubKey, + const std::vector& signature, + const std::vector& text) { + // Step 1: initialize EVP_MD_CTX + bssl::ScopedEVP_MD_CTX ctx; + + // Step 2: initialize EVP_MD + const EVP_MD* md = Utility::getHashFunction(hash); + + if (md == nullptr) { + return {false, absl::StrCat(hash, " is not supported.")}; + } + + // Step 3: initialize EVP_DigestVerify + int ok = EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pubKey); + if (!ok) { + return {false, "Failed to initialize digest verify."}; + } + + // Step 4: verify signature + ok = EVP_DigestVerify(ctx.get(), signature.data(), signature.size(), text.data(), text.size()); + + // Step 5: check result + if (ok == 1) { + return {true, ""}; + } + + return {false, absl::StrCat("Failed to verify digest. Error code: ", ok)}; +} + +PublicKeyPtr Utility::importPublicKey(const std::vector& key) { + CBS cbs({key.data(), key.size()}); + return PublicKeyPtr(EVP_parse_public_key(&cbs)); +} + +const EVP_MD* Utility::getHashFunction(absl::string_view name) { + const std::string hash = absl::AsciiStrToLower(name); + + // Hash algorithms set refers + // https://github.com/google/boringssl/blob/master/include/openssl/digest.h + if (hash == "sha1") { + return EVP_sha1(); + } else if (hash == "sha224") { + return EVP_sha224(); + } else if (hash == "sha256") { + return EVP_sha256(); + } else if (hash == "sha384") { + return EVP_sha384(); + } else if (hash == "sha512") { + return EVP_sha512(); + } else { + return nullptr; + } +} + } // namespace Crypto } // namespace Common } // namespace Envoy diff --git a/source/common/crypto/utility.h b/source/common/crypto/utility.h index f755aaf0a7dc4..9649f74b45190 100644 --- a/source/common/crypto/utility.h +++ b/source/common/crypto/utility.h @@ -6,11 +6,27 @@ #include "envoy/buffer/buffer.h" #include "absl/strings/string_view.h" +#include "openssl/evp.h" namespace Envoy { namespace Common { namespace Crypto { +struct VerificationOutput { + /** + * Verification result. If result_ is true, error_message_ is empty. + */ + bool result_; + + /** + * Error message when verification failed. + * TODO(crazyxy): switch to absl::StatusOr when available + */ + std::string error_message_; +}; + +typedef bssl::UniquePtr PublicKeyPtr; + class Utility { public: /** @@ -28,6 +44,29 @@ class Utility { */ static std::vector getSha256Hmac(const std::vector& key, absl::string_view message); + + /** + * Verify cryptographic signatures. + * @param hash hash function(including SHA1, SHA224, SHA256, SHA384, SHA512) + * @param key pointer to public key + * @param signature signature + * @param text clear text + * @return If the result_ is true, the error_message_ is empty; otherwise, + * the error_message_ stores the error message + */ + static const VerificationOutput verifySignature(absl::string_view hash, EVP_PKEY* key, + const std::vector& signature, + const std::vector& text); + + /** + * Import public key. + * @param key key string + * @return pointer to public key + */ + static PublicKeyPtr importPublicKey(const std::vector& key); + +private: + static const EVP_MD* getHashFunction(absl::string_view name); }; } // namespace Crypto diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index 0f4dff8bd8ab8..caae81485fccd 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -78,6 +78,11 @@ class CodecClient : Logger::Loggable, */ uint64_t id() { return connection_->id(); } + /** + * @return the underlying codec protocol. + */ + Protocol protocol() { return codec_->protocol(); } + /** * @return the underlying connection error. */ diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index d8ad735f9d249..5789ca5f0c699 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -203,8 +203,8 @@ void ConnPoolImpl::onResponseComplete(ActiveClient& client) { if (!client.stream_wrapper_->encode_complete_) { ENVOY_CONN_LOG(debug, "response before request complete", *client.codec_client_); onDownstreamReset(client); - } else if (client.stream_wrapper_->saw_close_header_ || client.codec_client_->remoteClosed()) { - ENVOY_CONN_LOG(debug, "saw upstream connection: close", *client.codec_client_); + } else if (client.stream_wrapper_->close_connection_ || client.codec_client_->remoteClosed()) { + ENVOY_CONN_LOG(debug, "saw upstream close connection", *client.codec_client_); onDownstreamReset(client); } else if (client.remaining_requests_ > 0 && --client.remaining_requests_ == 0) { ENVOY_CONN_LOG(debug, "maximum requests per connection", *client.codec_client_); @@ -273,17 +273,21 @@ ConnPoolImpl::StreamWrapper::~StreamWrapper() { void ConnPoolImpl::StreamWrapper::onEncodeComplete() { encode_complete_ = true; } void ConnPoolImpl::StreamWrapper::decodeHeaders(HeaderMapPtr&& headers, bool end_stream) { - if (headers->Connection() && - absl::EqualsIgnoreCase(headers->Connection()->value().getStringView(), - Headers::get().ConnectionValues.Close)) { - saw_close_header_ = true; - parent_.parent_.host_->cluster().stats().upstream_cx_close_notify_.inc(); - } - if (!saw_close_header_ && headers->ProxyConnection() && - absl::EqualsIgnoreCase(headers->ProxyConnection()->value().getStringView(), - Headers::get().ConnectionValues.Close)) { - saw_close_header_ = true; + // If Connection: close OR + // Http/1.0 and not Connection: keep-alive OR + // Proxy-Connection: close + if ((headers->Connection() && + (absl::EqualsIgnoreCase(headers->Connection()->value().getStringView(), + Headers::get().ConnectionValues.Close))) || + (parent_.codec_client_->protocol() == Protocol::Http10 && + (!headers->Connection() || + !absl::EqualsIgnoreCase(headers->Connection()->value().getStringView(), + Headers::get().ConnectionValues.KeepAlive))) || + (headers->ProxyConnection() && + (absl::EqualsIgnoreCase(headers->ProxyConnection()->value().getStringView(), + Headers::get().ConnectionValues.Close)))) { parent_.parent_.host_->cluster().stats().upstream_cx_close_notify_.inc(); + close_connection_ = true; } StreamDecoderWrapper::decodeHeaders(std::move(headers), end_stream); diff --git a/source/common/http/http1/conn_pool.h b/source/common/http/http1/conn_pool.h index 9f28b0ac8c587..5aec697ac31b8 100644 --- a/source/common/http/http1/conn_pool.h +++ b/source/common/http/http1/conn_pool.h @@ -73,7 +73,7 @@ class ConnPoolImpl : public ConnectionPool::Instance, public ConnPoolImplBase { ActiveClient& parent_; bool encode_complete_{}; - bool saw_close_header_{}; + bool close_connection_{}; bool decode_complete_{}; }; diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index e7f7039e9a405..0bad71017980b 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -281,6 +281,44 @@ bool Utility::isLocalConnection(const Network::ConnectionSocket& socket) { return false; } +bool Utility::isLocalIpAddress(const Address::Instance& address) { + if (address.type() != Envoy::Network::Address::Type::Ip) { + return false; + } + + if (isLoopbackAddress(address)) { + return false; + } + + struct ifaddrs* ifaddr; + const int rc = getifaddrs(&ifaddr); + Cleanup ifaddr_cleanup([ifaddr] { + if (ifaddr) { + freeifaddrs(ifaddr); + } + }); + RELEASE_ASSERT(rc == 0, ""); + + auto af_look_up = (address.ip()->version() == Address::IpVersion::v4) ? AF_INET : AF_INET6; + + for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) { + continue; + } + + if (ifa->ifa_addr->sa_family == af_look_up) { + const auto* addr = reinterpret_cast(ifa->ifa_addr); + auto local_address = Address::addressFromSockAddr( + *addr, (af_look_up == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); + + if (address == *local_address) { + return true; + } + } + } + return false; +} + bool Utility::isInternalAddress(const Address::Instance& address) { if (address.type() != Address::Type::Ip) { return false; diff --git a/source/common/network/utility.h b/source/common/network/utility.h index 68f56d7ec5491..a75b682a3540c 100644 --- a/source/common/network/utility.h +++ b/source/common/network/utility.h @@ -139,6 +139,12 @@ class Utility { */ static bool isLocalConnection(const Network::ConnectionSocket& socket); + /** + * Determine whether this is a local address. + * @return bool the address is a local address. + */ + static bool isLocalIpAddress(const Address::Instance& address); + /** * Determine whether this is an internal (RFC1918) address. * @return bool the address is an RFC1918 address. diff --git a/source/extensions/filters/http/lua/BUILD b/source/extensions/filters/http/lua/BUILD index de27cd3a70471..646636b28098e 100644 --- a/source/extensions/filters/http/lua/BUILD +++ b/source/extensions/filters/http/lua/BUILD @@ -22,6 +22,7 @@ envoy_cc_library( "//include/envoy/upstream:cluster_manager_interface", "//source/common/buffer:buffer_lib", "//source/common/common:enum_to_int", + "//source/common/crypto:utility_lib", "//source/common/http:message_lib", "//source/extensions/filters/common/lua:lua_lib", "//source/extensions/filters/common/lua:wrappers_lib", @@ -36,6 +37,7 @@ envoy_cc_library( deps = [ "//include/envoy/http:header_map_interface", "//include/envoy/stream_info:stream_info_interface", + "//source/common/crypto:utility_lib", "//source/common/http:utility_lib", "//source/extensions/filters/common/lua:lua_lib", "//source/extensions/filters/common/lua:wrappers_lib", diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 38b997dd27e7e..1dd5edff1fad7 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -7,6 +7,7 @@ #include "common/buffer/buffer_impl.h" #include "common/common/assert.h" #include "common/common/enum_to_int.h" +#include "common/crypto/utility.h" #include "common/http/message_impl.h" namespace Envoy { @@ -428,6 +429,52 @@ int StreamHandleWrapper::luaLogCritical(lua_State* state) { return 0; } +int StreamHandleWrapper::luaVerifySignature(lua_State* state) { + // Step 1: get hash function + absl::string_view hash = luaL_checkstring(state, 2); + + // Step 2: get key pointer + auto ptr = lua_touserdata(state, 3); + + // Step 3: get signature + const char* signature = luaL_checkstring(state, 4); + int sig_len = luaL_checknumber(state, 5); + const std::vector sig_vec(signature, signature + sig_len); + + // Step 4: get clear text + const char* clear_text = luaL_checkstring(state, 6); + int text_len = luaL_checknumber(state, 7); + const std::vector text_vec(clear_text, clear_text + text_len); + + // Step 5: verify signature + auto output = Common::Crypto::Utility::verifySignature(hash, reinterpret_cast(ptr), + sig_vec, text_vec); + + lua_pushboolean(state, output.result_); + if (output.result_) { + lua_pushnil(state); + } else { + lua_pushlstring(state, output.error_message_.data(), output.error_message_.length()); + } + return 2; +} + +int StreamHandleWrapper::luaImportPublicKey(lua_State* state) { + // Get byte array and the length. + const char* str = luaL_checkstring(state, 2); + int n = luaL_checknumber(state, 3); + std::vector key(str, str + n); + + if (public_key_wrapper_.get() != nullptr) { + public_key_wrapper_.pushStack(); + } else { + public_key_wrapper_.reset( + PublicKeyWrapper::create(state, Common::Crypto::Utility::importPublicKey(key)), true); + } + + return 1; +} + FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocator& tls, Upstream::ClusterManager& cluster_manager) : cluster_manager_(cluster_manager), lua_state_(lua_code, tls) { @@ -442,6 +489,7 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); request_function_slot_ = lua_state_.registerGlobal("envoy_on_request"); if (lua_state_.getGlobalRef(request_function_slot_) == LUA_REFNIL) { diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index c46d90a803d76..94e19af08a1bd 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -127,14 +127,23 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject metadata_wrapper_; Filters::Common::Lua::LuaDeathRef stream_info_wrapper_; Filters::Common::Lua::LuaDeathRef connection_wrapper_; + Filters::Common::Lua::LuaDeathRef public_key_wrapper_; State state_{State::Running}; std::function yield_callback_; Http::AsyncClient::Request* http_request_{}; diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index f675d34cfa598..332211d4c8bb0 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -174,6 +174,15 @@ int DynamicMetadataMapWrapper::luaPairs(lua_State* state) { return 1; } +int PublicKeyWrapper::luaGet(lua_State* state) { + if (public_key_ == nullptr || public_key_.get() == nullptr) { + lua_pushnil(state); + } else { + lua_pushlightuserdata(state, public_key_.get()); + } + return 1; +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index 91f1a400f5b81..e5bc0041d0d80 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -3,6 +3,8 @@ #include "envoy/http/header_map.h" #include "envoy/stream_info/stream_info.h" +#include "common/crypto/utility.h" + #include "extensions/filters/common/lua/lua.h" namespace Envoy { @@ -201,6 +203,24 @@ class StreamInfoWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + PublicKeyWrapper(Envoy::Common::Crypto::PublicKeyPtr key) : public_key_(std::move(key)) {} + static ExportedFunctions exportedFunctions() { return {{"get", static_luaGet}}; } + +private: + /** + * Get a pointer to public key. + * @return pointer to public key. + */ + DECLARE_LUA_FUNCTION(PublicKeyWrapper, luaGet); + + Envoy::Common::Crypto::PublicKeyPtr public_key_; +}; + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 855cdaadb7e3e..7588973e3470e 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -126,6 +126,27 @@ ConnectionHandlerImpl::findActiveListenerByAddress(const Network::Address::Insta return listener_it->second.get(); } + /* + * istio: if the ip is local pod ip, select the predefined listener + * 1. where can I find the pod ip? + * env: INSTANCE_IP: (v1:status.podIP) + * + * 2. What the predefined listener should be? + */ + if (Network::Utility::isLocalIpAddress(address)) { + ENVOY_LOG_TO_LOGGER( + logger_, error, + "dest ip {} is local address but hit no listener, try using blackhole listener", + address.ip()->addressAsString()); + listener_it = std::find_if( + listeners_.begin(), listeners_.end(), + [](const std::pair& p) { + return p.second->config_.name() == "ISTIO_WILDCARD_PORT_LISTENER"; + }); + if (listener_it != listeners_.end()) { + return listener_it->second.get(); + } + } // Otherwise, we need to look for the wild card match, i.e., 0.0.0.0:[address_port]. // We do not return stopped listeners. // TODO(wattli): consolidate with previous search for more efficiency. diff --git a/test/common/crypto/utility_test.cc b/test/common/crypto/utility_test.cc index bea2f8f9e0d54..5de66a9c884df 100644 --- a/test/common/crypto/utility_test.cc +++ b/test/common/crypto/utility_test.cc @@ -50,6 +50,73 @@ TEST(UtilityTest, TestSha256HmacWithEmptyArguments) { EXPECT_EQ("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", Hex::encode(hmac)); } +TEST(UtilityTest, TestImportPublicKey) { + auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d" + "73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a6" + "2cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644" + "ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d" + "395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884" + "d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5" + "183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001"; + + auto pub_key = Utility::importPublicKey(Hex::decode(key)); + EXPECT_NE(nullptr, pub_key.get()); + + key = "badkey"; + + pub_key = Utility::importPublicKey(Hex::decode(key)); + EXPECT_EQ(nullptr, pub_key.get()); +} + +TEST(UtilityTest, TestVerifySignature) { + auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d" + "73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a6" + "2cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644" + "ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d" + "395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884" + "d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5" + "183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001"; + auto hash_func = "sha256"; + auto signature = + "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a2" + "9c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5" + "ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf5" + "70a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace" + "1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6" + "295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332"; + auto data = "hello"; + + auto pub_key = Utility::importPublicKey(Hex::decode(key)); + + std::vector text(data, data + strlen(data)); + + auto sig = Hex::decode(signature); + auto result = Utility::verifySignature(hash_func, pub_key.get(), sig, text); + + EXPECT_EQ(true, result.result_); + EXPECT_EQ("", result.error_message_); + + result = Utility::verifySignature("unknown", pub_key.get(), sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("unknown is not supported.", result.error_message_); + + result = Utility::verifySignature(hash_func, nullptr, sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to initialize digest verify.", result.error_message_); + + data = "baddata"; + text = std::vector(data, data + strlen(data)); + result = Utility::verifySignature(hash_func, pub_key.get(), sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); + + data = "hello"; + text = std::vector(data, data + strlen(data)); + result = Utility::verifySignature(hash_func, pub_key.get(), Hex::decode("000000"), text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); +} + } // namespace } // namespace Crypto } // namespace Common diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 51ccfbabc07c8..05cf3bc4e99d0 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -74,7 +74,7 @@ class ConnPoolImplForTest : public ConnPoolImpl { MOCK_METHOD0(createCodecClient_, CodecClient*()); MOCK_METHOD0(onClientDestroy, void()); - void expectClientCreate() { + void expectClientCreate(Protocol protocol = Protocol::Http11) { test_clients_.emplace_back(); TestCodecClient& test_client = test_clients_.back(); test_client.connection_ = new NiceMock(); @@ -99,6 +99,7 @@ class ConnPoolImplForTest : public ConnPoolImpl { .WillOnce(Return(test_client.connection_)); EXPECT_CALL(*this, createCodecClient_()).WillOnce(Return(test_client.codec_client_)); EXPECT_CALL(*test_client.connect_timer_, enableTimer(_)); + ON_CALL(*test_client.codec_, protocol()).WillByDefault(Return(protocol)); } void expectEnableUpstreamReady() { @@ -475,7 +476,9 @@ TEST_F(Http1ConnPoolImplTest, MaxConnections) { conn_pool_.expectAndRunUpstreamReady(); callbacks2.outer_encoder_->encodeHeaders(TestHeaderMapImpl{}, true); - response_headers.reset(new TestHeaderMapImpl{{":status", "200"}}); + // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. + response_headers = std::make_unique( + std::initializer_list>{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); // Cause the connection to go away. @@ -537,7 +540,9 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { conn_pool_.test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); callbacks2.outer_encoder_->encodeHeaders(TestHeaderMapImpl{}, true); - response_headers.reset(new TestHeaderMapImpl{{":status", "200"}}); + // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. + response_headers = std::make_unique( + std::initializer_list>{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); EXPECT_CALL(conn_pool_, onClientDestroy()); @@ -611,6 +616,39 @@ TEST_F(Http1ConnPoolImplTest, ProxyConnectionCloseHeader) { EXPECT_EQ(0U, cluster_->stats_.upstream_cx_destroy_with_active_rq_.value()); } +/** + * Test when upstream is HTTP/1.0 and does not send 'connection: keep-alive' + */ +TEST_F(Http1ConnPoolImplTest, Http10NoConnectionKeepAlive) { + InSequence s; + + // Request 1 should kick off a new connection. + NiceMock outer_decoder; + ConnPoolCallbacks callbacks; + conn_pool_.expectClientCreate(Protocol::Http10); + Http::ConnectionPool::Cancellable* handle = conn_pool_.newStream(outer_decoder, callbacks); + + EXPECT_NE(nullptr, handle); + + NiceMock request_encoder; + Http::StreamDecoder* inner_decoder; + EXPECT_CALL(*conn_pool_.test_clients_[0].codec_, newStream(_)) + .WillOnce(DoAll(SaveArgAddress(&inner_decoder), ReturnRef(request_encoder))); + EXPECT_CALL(callbacks.pool_ready_, ready()); + + conn_pool_.test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); + callbacks.outer_encoder_->encodeHeaders(TestHeaderMapImpl{}, true); + + // Response without 'connection: keep-alive' which should cause the connection to go away. + EXPECT_CALL(conn_pool_, onClientDestroy()); + Http::HeaderMapPtr response_headers( + new TestHeaderMapImpl{{":protocol", "HTTP/1.0"}, {":status", "200"}}); + inner_decoder->decodeHeaders(std::move(response_headers), true); + dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(0U, cluster_->stats_.upstream_cx_destroy_with_active_rq_.value()); +} + /** * Test when we reach max requests per connection. */ diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index cebe65610dff0..9dd265565e638 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -1577,6 +1577,131 @@ TEST_F(LuaHttpFilterTest, CheckConnection) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +TEST_F(LuaHttpFilterTest, ImportPublicKey) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a62cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001" + raw = key:fromhex() + key = request_handle:importPublicKey(raw, string.len(raw)):get() + + if key == nil then + request_handle:logTrace("failed to import public key") + else + request_handle:logTrace("succeeded to import public key") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("succeeded to import public key"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + +TEST_F(LuaHttpFilterTest, InvalidPublicKey) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "0000" + raw = key:fromhex() + key = request_handle:importPublicKey(raw, string.len(raw)):get() + + if key == nil then + request_handle:logTrace("failed to import public key") + else + request_handle:logTrace("succeeded to import public key") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("failed to import public key"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + +TEST_F(LuaHttpFilterTest, SignatureVerify) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a62cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001" + hashFunc = "sha256" + signature = "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a29c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf570a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332" + data = "hello" + + rawkey = key:fromhex() + pubkey = request_handle:importPublicKey(rawkey, string.len(rawkey)):get() + + if pubkey == nil then + request_handle:logTrace("failed to import public key") + return + end + + rawsig = signature:fromhex() + + ok, error = request_handle:verifySignature(hashFunc, pubkey, rawsig, string.len(rawsig), data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature("unknown", pubkey, rawsig, string.len(rawsig), data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature(hashFunc, pubkey, "0000", 4, data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature(hashFunc, pubkey, rawsig, string.len(rawsig), "xxxx", 4) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("signature is valid"))); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("unknown is not supported."))); + EXPECT_CALL(*filter_, + scriptLog(spdlog::level::trace, StrEq("Failed to verify digest. Error code: 0"))); + EXPECT_CALL(*filter_, + scriptLog(spdlog::level::trace, StrEq("Failed to verify digest. Error code: 0"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + } // namespace } // namespace Lua } // namespace HttpFilters diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 1f6e2e801a8df..259a0e9b4b030 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -53,6 +53,8 @@ class LuaIntegrationTest : public testing::TestWithParam0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) + end + + function envoy_on_request(request_handle) + local metadata = request_handle:metadata():get("keyset") + local keyder = metadata[request_handle:headers():get("keyid")] + + local rawkeyder = dec(keyder) + local pubkey = request_handle:importPublicKey(rawkeyder, string.len(rawkeyder)):get() + + if pubkey == nil then + request_handle:logErr("log test") + request_handle:headers():add("signature_verification", "rejected") + return + end + + local hash = request_handle:headers():get("hash") + local sig = request_handle:headers():get("signature") + local rawsig = sig:fromhex() + local data = request_handle:headers():get("message") + local ok, error = request_handle:verifySignature(hash, pubkey, rawsig, string.len(rawsig), data, string.len(data)) + + if ok then + request_handle:headers():add("signature_verification", "approved") + else + request_handle:logErr(error) + request_handle:headers():add("signature_verification", "rejected") + end + + request_handle:releasePublicKey(pubkey) + end +)EOF"; + + initializeFilter(FILTER_AND_CODE); + + auto signature = + "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a2" + "9c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5" + "ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf5" + "70a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace" + "1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6" + "295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332"; + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "https"}, + {":authority", "host"}, {"x-forwarded-for", "10.0.0.1"}, {"message", "hello"}, + {"keyid", "foo"}, {"signature", signature}, {"hash", "sha256"}}; + + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + waitForNextUpstreamRequest(); + + EXPECT_EQ("approved", upstream_request_->headers() + .get(Http::LowerCaseString("signature_verification")) + ->value() + .getStringView()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); + + cleanup(); +} + } // namespace } // namespace Envoy