From f468a96424ad7e3c2c1ab3c4fd27b333f63058f1 Mon Sep 17 00:00:00 2001 From: Ryan Apilado Date: Thu, 19 Nov 2020 02:17:32 +0000 Subject: [PATCH] implement proxy-wasm ABI restriction Signed-off-by: Ryan Apilado --- api/envoy/extensions/wasm/v3/wasm.proto | 11 +- .../envoy/extensions/wasm/v3/wasm.proto | 11 +- .../extensions/access_loggers/wasm/config.cc | 7 +- source/extensions/bootstrap/wasm/config.cc | 7 +- source/extensions/common/wasm/context.h | 1 + source/extensions/common/wasm/wasm.cc | 36 +-- source/extensions/common/wasm/wasm.h | 12 +- .../extensions/common/wasm/wasm_extension.cc | 12 +- .../extensions/common/wasm/wasm_extension.h | 7 +- .../filters/http/wasm/wasm_filter.cc | 7 +- .../filters/network/wasm/wasm_filter.cc | 7 +- source/extensions/stat_sinks/wasm/config.cc | 7 +- .../bootstrap/wasm/wasm_speed_test.cc | 5 +- test/extensions/bootstrap/wasm/wasm_test.cc | 5 +- test/extensions/common/wasm/BUILD | 2 + test/extensions/common/wasm/test_data/BUILD | 26 ++ .../wasm/test_data/test_restriction_cpp.cc | 24 ++ .../test_restriction_cpp_null_plugin.cc | 16 ++ .../extensions/common/wasm/wasm_speed_test.cc | 4 +- test/extensions/common/wasm/wasm_test.cc | 272 ++++++++++++++++-- test/extensions/filters/network/wasm/BUILD | 1 - .../filters/network/wasm/config_test.cc | 87 ++++++ .../filters/network/wasm/wasm_filter_test.cc | 78 ++++- test/test_common/wasm_base.h | 8 +- 24 files changed, 570 insertions(+), 83 deletions(-) create mode 100644 test/extensions/common/wasm/test_data/test_restriction_cpp.cc create mode 100644 test/extensions/common/wasm/test_data/test_restriction_cpp_null_plugin.cc diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index c6affb810611..ff8a77bb1c81 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -18,6 +18,12 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Wasm] // [#extension: envoy.bootstrap.wasm] +// Configuration for restricting proxy-wasm capabilities available to modules. +message CapabilityRestrictionConfig { + // The list of proxy-wasm capabilities which will be exposed to the module. + repeated string allowed_abi_functions = 1; +} + // Configuration for a Wasm VM. // [#next-free-field: 7] message VmConfig { @@ -74,7 +80,7 @@ message VmConfig { } // Base Configuration for Wasm Plugins e.g. filters and services. -// [#next-free-field: 6] +// [#next-free-field: 7] message PluginConfig { // A unique name for a filters/services in a VM for use in identifying the filter/service if // multiple filters/services are handled by the same *vm_id* and *root_id* and for @@ -105,6 +111,9 @@ message PluginConfig { // during xDS updates the xDS configuration will be rejected and when on_start or on_configuration return false on initial // startup the proxy will not start. bool fail_open = 5; + + // Configuration for restricting proxy-wasm capabilities available to modules. + CapabilityRestrictionConfig capability_restriction_config = 6; } // WasmService is configured as a built-in *envoy.wasm_service* :ref:`WasmService diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index c6affb810611..ff8a77bb1c81 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -18,6 +18,12 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Wasm] // [#extension: envoy.bootstrap.wasm] +// Configuration for restricting proxy-wasm capabilities available to modules. +message CapabilityRestrictionConfig { + // The list of proxy-wasm capabilities which will be exposed to the module. + repeated string allowed_abi_functions = 1; +} + // Configuration for a Wasm VM. // [#next-free-field: 7] message VmConfig { @@ -74,7 +80,7 @@ message VmConfig { } // Base Configuration for Wasm Plugins e.g. filters and services. -// [#next-free-field: 6] +// [#next-free-field: 7] message PluginConfig { // A unique name for a filters/services in a VM for use in identifying the filter/service if // multiple filters/services are handled by the same *vm_id* and *root_id* and for @@ -105,6 +111,9 @@ message PluginConfig { // during xDS updates the xDS configuration will be rejected and when on_start or on_configuration return false on initial // startup the proxy will not start. bool fail_open = 5; + + // Configuration for restricting proxy-wasm capabilities available to modules. + CapabilityRestrictionConfig capability_restriction_config = 6; } // WasmService is configured as a built-in *envoy.wasm_service* :ref:`WasmService diff --git a/source/extensions/access_loggers/wasm/config.cc b/source/extensions/access_loggers/wasm/config.cc index 8ca765442e9c..61f126365093 100644 --- a/source/extensions/access_loggers/wasm/config.cc +++ b/source/extensions/access_loggers/wasm/config.cc @@ -46,9 +46,10 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con }; if (!Common::Wasm::createWasm( - config.config().vm_config(), plugin, context.scope().createScope(""), - context.clusterManager(), context.initManager(), context.dispatcher(), context.api(), - context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) { + config.config().vm_config(), config.config().capability_restriction_config(), plugin, + context.scope().createScope(""), context.clusterManager(), context.initManager(), + context.dispatcher(), context.api(), context.lifecycleNotifier(), remote_data_provider_, + std::move(callback))) { throw Common::Wasm::WasmException( fmt::format("Unable to create Wasm access log {}", plugin->name_)); } diff --git a/source/extensions/bootstrap/wasm/config.cc b/source/extensions/bootstrap/wasm/config.cc index 0e8f4caa99ac..d70143764cca 100644 --- a/source/extensions/bootstrap/wasm/config.cc +++ b/source/extensions/bootstrap/wasm/config.cc @@ -52,9 +52,10 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con }; if (!Common::Wasm::createWasm( - config.config().vm_config(), plugin, context.scope().createScope(""), - context.clusterManager(), context.initManager(), context.dispatcher(), context.api(), - context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) { + config.config().vm_config(), config.config().capability_restriction_config(), plugin, + context.scope().createScope(""), context.clusterManager(), context.initManager(), + context.dispatcher(), context.api(), context.lifecycleNotifier(), remote_data_provider_, + std::move(callback))) { // NB: throw if we get a synchronous configuration failures as this is how such failures are // reported to xDS. throw Common::Wasm::WasmException( diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 657a0331addd..cf2f8177e7f4 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -42,6 +42,7 @@ using proxy_wasm::WasmResult; using proxy_wasm::WasmStreamType; using VmConfig = envoy::extensions::wasm::v3::VmConfig; +using CapabilityRestrictionConfig = envoy::extensions::wasm::v3::CapabilityRestrictionConfig; using GrpcService = envoy::config::core::v3::GrpcService; class Wasm; diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 1b0e8e513be2..8323bdd38b4e 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -100,10 +100,12 @@ void Wasm::initializeLifecycle(Server::ServerLifecycleNotifier& lifecycle_notifi } Wasm::Wasm(absl::string_view runtime, absl::string_view vm_id, absl::string_view vm_configuration, - absl::string_view vm_key, const Stats::ScopeSharedPtr& scope, - Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher) - : WasmBase(createWasmVm(runtime, scope), vm_id, vm_configuration, vm_key), scope_(scope), - cluster_manager_(cluster_manager), dispatcher_(dispatcher), + absl::string_view vm_key, std::unordered_set allowed_abi_functions, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher) + : WasmBase(createWasmVm(runtime, scope), vm_id, vm_configuration, vm_key, + allowed_abi_functions), + scope_(scope), cluster_manager_(cluster_manager), dispatcher_(dispatcher), time_source_(dispatcher.timeSource()), wasm_stats_(WasmStats{ ALL_WASM_STATS(POOL_COUNTER_PREFIX(*scope_, absl::StrCat("wasm.", runtime, ".")), @@ -314,8 +316,9 @@ WasmEvent toWasmEvent(const std::shared_ptr& wasm) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -static bool createWasmInternal(const VmConfig& vm_config, const PluginSharedPtr& plugin, - const Stats::ScopeSharedPtr& scope, +static bool createWasmInternal(const VmConfig& vm_config, + const CapabilityRestrictionConfig& cr_config, + const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, Event::Dispatcher& dispatcher, Api::Api& api, Server::ServerLifecycleNotifier& lifecycle_notifier, @@ -382,7 +385,7 @@ static bool createWasmInternal(const VmConfig& vm_config, const PluginSharedPtr& .value_or(code.empty() ? EMPTY_STRING : INLINE_STRING); } - auto complete_cb = [cb, vm_config, plugin, scope, &cluster_manager, &dispatcher, + auto complete_cb = [cb, vm_config, cr_config, plugin, scope, &cluster_manager, &dispatcher, &lifecycle_notifier, create_root_context_for_testing, wasm_extension](std::string code) -> bool { if (code.empty()) { @@ -393,10 +396,10 @@ static bool createWasmInternal(const VmConfig& vm_config, const PluginSharedPtr& proxy_wasm::makeVmKey(vm_config.vm_id(), anyToBytes(vm_config.configuration()), code); auto wasm_factory = wasm_extension->wasmFactory(); proxy_wasm::WasmHandleFactory proxy_wasm_factory = - [&vm_config, scope, &cluster_manager, &dispatcher, &lifecycle_notifier, + [&vm_config, &cr_config, scope, &cluster_manager, &dispatcher, &lifecycle_notifier, wasm_factory](absl::string_view vm_key) -> WasmHandleBaseSharedPtr { - return wasm_factory(vm_config, scope, cluster_manager, dispatcher, lifecycle_notifier, - vm_key); + return wasm_factory(vm_config, cr_config, scope, cluster_manager, dispatcher, + lifecycle_notifier, vm_key); }; auto wasm = proxy_wasm::createWasm( vm_key, code, plugin, proxy_wasm_factory, @@ -471,15 +474,16 @@ static bool createWasmInternal(const VmConfig& vm_config, const PluginSharedPtr& return true; } -bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, - const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, - Init::Manager& init_manager, Event::Dispatcher& dispatcher, Api::Api& api, +bool createWasm(const VmConfig& vm_config, const CapabilityRestrictionConfig& cr_config, + const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scope, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + Event::Dispatcher& dispatcher, Api::Api& api, Envoy::Server::ServerLifecycleNotifier& lifecycle_notifier, Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, CreateWasmCallback&& cb, CreateContextFn create_root_context_for_testing) { - return createWasmInternal(vm_config, plugin, scope, cluster_manager, init_manager, dispatcher, - api, lifecycle_notifier, remote_data_provider, std::move(cb), - create_root_context_for_testing); + return createWasmInternal(vm_config, cr_config, plugin, scope, cluster_manager, init_manager, + dispatcher, api, lifecycle_notifier, remote_data_provider, + std::move(cb), create_root_context_for_testing); } PluginHandleSharedPtr diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 8ade0d66e63d..e3f3787c133e 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -47,8 +47,9 @@ struct WasmStats { class Wasm : public WasmBase, Logger::Loggable { public: Wasm(absl::string_view runtime, absl::string_view vm_id, absl::string_view vm_configuration, - absl::string_view vm_key, const Stats::ScopeSharedPtr& scope, - Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher); + absl::string_view vm_key, std::unordered_set allowed_abi_functions, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher); Wasm(std::shared_ptr other, Event::Dispatcher& dispatcher); ~Wasm() override; @@ -160,9 +161,10 @@ using CreateWasmCallback = std::function; // all failures synchronously as it has no facility to report configuration update failures // asynchronously. Callers should throw an exception if they are part of a synchronous xDS update // because that is the mechanism for reporting configuration errors. -bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, - const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, - Init::Manager& init_manager, Event::Dispatcher& dispatcher, Api::Api& api, +bool createWasm(const VmConfig& vm_config, const CapabilityRestrictionConfig& cr_config, + const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scope, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + Event::Dispatcher& dispatcher, Api::Api& api, Envoy::Server::ServerLifecycleNotifier& lifecycle_notifier, Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, CreateWasmCallback&& callback, diff --git a/source/extensions/common/wasm/wasm_extension.cc b/source/extensions/common/wasm/wasm_extension.cc index 1917fa792a82..7c138a008e8f 100644 --- a/source/extensions/common/wasm/wasm_extension.cc +++ b/source/extensions/common/wasm/wasm_extension.cc @@ -55,13 +55,15 @@ PluginHandleExtensionFactory EnvoyWasm::pluginFactory() { } WasmHandleExtensionFactory EnvoyWasm::wasmFactory() { - return [](const VmConfig vm_config, const Stats::ScopeSharedPtr& scope, - Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher, - Server::ServerLifecycleNotifier& lifecycle_notifier, + return [](const VmConfig vm_config, const CapabilityRestrictionConfig cr_config, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher, Server::ServerLifecycleNotifier& lifecycle_notifier, absl::string_view vm_key) -> WasmHandleBaseSharedPtr { + std::unordered_set allowed_abi_functions(cr_config.allowed_abi_functions().begin(), + cr_config.allowed_abi_functions().end()); auto wasm = std::make_shared(vm_config.runtime(), vm_config.vm_id(), - anyToBytes(vm_config.configuration()), vm_key, scope, - cluster_manager, dispatcher); + anyToBytes(vm_config.configuration()), vm_key, + allowed_abi_functions, scope, cluster_manager, dispatcher); wasm->initializeLifecycle(lifecycle_notifier); return std::static_pointer_cast(std::make_shared(std::move(wasm))); }; diff --git a/source/extensions/common/wasm/wasm_extension.h b/source/extensions/common/wasm/wasm_extension.h index 22ae373162f2..fa5780a61199 100644 --- a/source/extensions/common/wasm/wasm_extension.h +++ b/source/extensions/common/wasm/wasm_extension.h @@ -34,9 +34,10 @@ using CreateContextFn = using PluginHandleExtensionFactory = std::function; using WasmHandleExtensionFactory = std::function; + const VmConfig& vm_config, const CapabilityRestrictionConfig& cr_config, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher, Server::ServerLifecycleNotifier& lifecycle_notifier, + absl::string_view vm_key)>; using WasmHandleExtensionCloneFactory = std::function; diff --git a/source/extensions/filters/http/wasm/wasm_filter.cc b/source/extensions/filters/http/wasm/wasm_filter.cc index 90713ba01989..c3d95f0237ff 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.cc +++ b/source/extensions/filters/http/wasm/wasm_filter.cc @@ -24,9 +24,10 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Was }; if (!Common::Wasm::createWasm( - config.config().vm_config(), plugin_, context.scope().createScope(""), - context.clusterManager(), context.initManager(), context.dispatcher(), context.api(), - context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) { + config.config().vm_config(), config.config().capability_restriction_config(), plugin_, + context.scope().createScope(""), context.clusterManager(), context.initManager(), + context.dispatcher(), context.api(), context.lifecycleNotifier(), remote_data_provider_, + std::move(callback))) { throw Common::Wasm::WasmException( fmt::format("Unable to create Wasm HTTP filter {}", plugin->name_)); } diff --git a/source/extensions/filters/network/wasm/wasm_filter.cc b/source/extensions/filters/network/wasm/wasm_filter.cc index ccceeb9dc478..24c60caca34c 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.cc +++ b/source/extensions/filters/network/wasm/wasm_filter.cc @@ -24,9 +24,10 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::network::wasm::v3:: }; if (!Common::Wasm::createWasm( - config.config().vm_config(), plugin_, context.scope().createScope(""), - context.clusterManager(), context.initManager(), context.dispatcher(), context.api(), - context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) { + config.config().vm_config(), config.config().capability_restriction_config(), plugin_, + context.scope().createScope(""), context.clusterManager(), context.initManager(), + context.dispatcher(), context.api(), context.lifecycleNotifier(), remote_data_provider_, + std::move(callback))) { throw Common::Wasm::WasmException( fmt::format("Unable to create Wasm network filter {}", plugin->name_)); } diff --git a/source/extensions/stat_sinks/wasm/config.cc b/source/extensions/stat_sinks/wasm/config.cc index da07bbdd5880..f60fcd702e6b 100644 --- a/source/extensions/stat_sinks/wasm/config.cc +++ b/source/extensions/stat_sinks/wasm/config.cc @@ -44,9 +44,10 @@ WasmSinkFactory::createStatsSink(const Protobuf::Message& proto_config, }; if (!Common::Wasm::createWasm( - config.config().vm_config(), plugin, context.scope().createScope(""), - context.clusterManager(), context.initManager(), context.dispatcher(), context.api(), - context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) { + config.config().vm_config(), config.config().capability_restriction_config(), plugin, + context.scope().createScope(""), context.clusterManager(), context.initManager(), + context.dispatcher(), context.api(), context.lifecycleNotifier(), remote_data_provider_, + std::move(callback))) { throw Common::Wasm::WasmException( fmt::format("Unable to create Wasm Stat Sink {}", plugin->name_)); } diff --git a/test/extensions/bootstrap/wasm/wasm_speed_test.cc b/test/extensions/bootstrap/wasm/wasm_speed_test.cc index 6d39d399fb89..54813102f1fe 100644 --- a/test/extensions/bootstrap/wasm/wasm_speed_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_speed_test.cc @@ -58,9 +58,10 @@ static void bmWasmSimpleCallSpeedTest(benchmark::State& state, std::string test, auto plugin = std::make_shared( name, root_id, vm_id, runtime, plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", runtime), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", runtime), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); std::string code; if (runtime == "null") { code = "WasmSpeedCpp"; diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 757b086770b6..9d162c8fbcfe 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -47,8 +47,8 @@ class WasmTestBase { name_, root_id_, vm_id_, runtime, plugin_configuration_, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info_, nullptr); wasm_ = std::make_shared( - absl::StrCat("envoy.wasm.runtime.", runtime), vm_id_, vm_configuration_, vm_key_, scope_, - cluster_manager, *dispatcher_); + absl::StrCat("envoy.wasm.runtime.", runtime), vm_id_, vm_configuration_, vm_key_, + allowed_abi_functions, scope_, cluster_manager, *dispatcher_); EXPECT_NE(wasm_, nullptr); wasm_->setCreateContextForTesting( nullptr, @@ -69,6 +69,7 @@ class WasmTestBase { std::string vm_id_; std::string vm_configuration_; std::string vm_key_; + std::unordered_set allowed_abi_functions; std::string plugin_configuration_; std::shared_ptr plugin_; std::shared_ptr wasm_; diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index b26be4cdb7e5..fbb2414b2ac1 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -36,6 +36,7 @@ envoy_cc_test( "//test/extensions/common/wasm/test_data:bad_signature_cpp.wasm", "//test/extensions/common/wasm/test_data:test_context_cpp.wasm", "//test/extensions/common/wasm/test_data:test_cpp.wasm", + "//test/extensions/common/wasm/test_data:test_restriction_cpp.wasm", ]), external_deps = ["abseil_optional"], deps = [ @@ -47,6 +48,7 @@ envoy_cc_test( "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/common/wasm:wasm_runtime", + "//test/extensions/common/wasm/test_data:test_restriction_cpp_plugin", "//test/extensions/common/wasm/test_data:test_context_cpp_plugin", "//test/extensions/common/wasm/test_data:test_cpp_plugin", "//test/mocks/server:server_mocks", diff --git a/test/extensions/common/wasm/test_data/BUILD b/test/extensions/common/wasm/test_data/BUILD index b40b3e49e41b..54c0eb8b33f3 100644 --- a/test/extensions/common/wasm/test_data/BUILD +++ b/test/extensions/common/wasm/test_data/BUILD @@ -50,6 +50,24 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "test_restriction_cpp_plugin", + srcs = [ + "test_restriction_cpp.cc", + "test_restriction_cpp_null_plugin.cc", + ], + copts = ["-DNULL_PLUGIN=1"], + deps = [ + "//external:abseil_node_hash_map", + "//source/common/common:assert_lib", + "//source/common/common:c_smart_ptr_lib", + "//source/extensions/common/wasm:wasm_hdr", + "//source/extensions/common/wasm:wasm_lib", + "//source/extensions/common/wasm:well_known_names", + "//source/extensions/common/wasm/ext:envoy_null_plugin", + ], +) + envoy_wasm_cc_binary( name = "test_cpp.wasm", srcs = ["test_cpp.cc"], @@ -66,6 +84,14 @@ envoy_wasm_cc_binary( ], ) +envoy_wasm_cc_binary( + name = "test_restriction_cpp.wasm", + srcs = ["test_restriction_cpp.cc"], + deps = [ + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", + ], +) + envoy_wasm_cc_binary( name = "bad_signature_cpp.wasm", srcs = ["bad_signature_cpp.cc"], diff --git a/test/extensions/common/wasm/test_data/test_restriction_cpp.cc b/test/extensions/common/wasm/test_data/test_restriction_cpp.cc new file mode 100644 index 000000000000..1550af015a9b --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_restriction_cpp.cc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" +#endif + +START_WASM_PLUGIN(CommonWasmRestrictionTestCpp) + +WASM_EXPORT(void, proxy_abi_version_0_2_1, (void)) {} + +WASM_EXPORT(uint32_t, proxy_on_vm_start, (uint32_t context_id, uint32_t configuration_size)) { + (void)(context_id); + (void)(configuration_size); + std::string level_message = "after on_vm_start, before proxy_log"; + proxy_log(LogLevel::info, level_message.c_str(), level_message.size()); + return 1; +} + +END_WASM_PLUGIN \ No newline at end of file diff --git a/test/extensions/common/wasm/test_data/test_restriction_cpp_null_plugin.cc b/test/extensions/common/wasm/test_data/test_restriction_cpp_null_plugin.cc new file mode 100644 index 000000000000..2841ae802f4e --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_restriction_cpp_null_plugin.cc @@ -0,0 +1,16 @@ +// NOLINT(namespace-envoy) +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace CommonWasmRestrictionTestCpp { +NullPluginRegistry* context_registry_; +} // namespace CommonWasmRestrictionTestCpp + +RegisterNullVmPluginFactory + register_common_wasm_test_restriction_cpp_plugin("CommonWasmTestRestrictionCpp", []() { + return std::make_unique(CommonWasmRestrictionTestCpp::context_registry_); + }); + +} // namespace null_plugin +} // namespace proxy_wasm diff --git a/test/extensions/common/wasm/wasm_speed_test.cc b/test/extensions/common/wasm/wasm_speed_test.cc index af1c31a2408f..0732dfd0be15 100644 --- a/test/extensions/common/wasm/wasm_speed_test.cc +++ b/test/extensions/common/wasm/wasm_speed_test.cc @@ -30,8 +30,10 @@ void bmWasmSpeedTest(benchmark::State& state) { Envoy::Upstream::MockClusterManager cluster_manager; Envoy::Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); auto scope = Envoy::Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - "envoy.wasm.runtime.null", "", "", "", scope, cluster_manager, *dispatcher); + "envoy.wasm.runtime.null", "", "", "", allowed_abi_functions, scope, cluster_manager, + *dispatcher); auto context = std::make_shared(wasm.get()); Envoy::Thread::ThreadFactory& thread_factory{Envoy::Thread::threadFactoryForTest()}; diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index cd56089daf5f..10a625297e17 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -104,9 +104,10 @@ TEST_P(WasmCommonTest, EnvoyWasm) { auto plugin = std::make_shared( "", "", "", GetParam(), "", false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); - auto wasm = std::make_shared( - std::make_unique(absl::StrCat("envoy.wasm.runtime.", GetParam()), "", - "vm_configuration", "", scope, cluster_manager, *dispatcher)); + std::unordered_set allowed_abi_functions; + auto wasm = std::make_shared(std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), "", "vm_configuration", "", + allowed_abi_functions, scope, cluster_manager, *dispatcher)); auto wasm_base = std::dynamic_pointer_cast(wasm); wasm->wasm()->setFailStateForTesting(proxy_wasm::FailState::UnableToCreateVM); EXPECT_EQ(toWasmEvent(wasm_base), EnvoyWasm::WasmEvent::UnableToCreateVM); @@ -182,9 +183,10 @@ TEST_P(WasmCommonTest, Logging) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_shared( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_NE(wasm->buildVersion(), ""); EXPECT_NE(std::unique_ptr(wasm->createContext(plugin)), nullptr); @@ -253,9 +255,10 @@ TEST_P(WasmCommonTest, BadSignature) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_FALSE(wasm->initialize(code, false)); EXPECT_TRUE(wasm->isFailed()); } @@ -282,9 +285,10 @@ TEST_P(WasmCommonTest, Segv) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_TRUE(wasm->initialize(code, false)); TestContext* root_context = nullptr; wasm->setCreateContextForTesting( @@ -325,9 +329,10 @@ TEST_P(WasmCommonTest, DivByZero) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); auto context = std::make_unique(wasm.get()); EXPECT_TRUE(wasm->initialize(code, false)); @@ -365,9 +370,10 @@ TEST_P(WasmCommonTest, IntrinsicGlobals) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -405,9 +411,10 @@ TEST_P(WasmCommonTest, Utilities) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -470,9 +477,10 @@ TEST_P(WasmCommonTest, Stats) { name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -506,9 +514,10 @@ TEST_P(WasmCommonTest, Foreign) { auto plugin = std::make_shared( name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -551,9 +560,10 @@ TEST_P(WasmCommonTest, OnForeign) { auto plugin = std::make_shared( name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -598,9 +608,10 @@ TEST_P(WasmCommonTest, WASI) { auto plugin = std::make_shared( name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + std::unordered_set allowed_abi_functions; auto wasm = std::make_unique( - absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, scope, - cluster_manager, *dispatcher); + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -651,6 +662,7 @@ TEST_P(WasmCommonTest, VmCache) { })); VmConfig vm_config; + CapabilityRestrictionConfig cr_config; vm_config.set_runtime(absl::StrCat("envoy.wasm.runtime.", GetParam())); ProtobufWkt::StringValue vm_configuration_string; vm_configuration_string.set_value(vm_configuration); @@ -666,7 +678,7 @@ TEST_P(WasmCommonTest, VmCache) { EXPECT_FALSE(code.empty()); vm_config.mutable_code()->mutable_local()->set_inline_bytes(code); WasmHandleSharedPtr wasm_handle; - createWasm(vm_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, + createWasm(vm_config, cr_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, lifecycle_notifier, remote_data_provider, [&wasm_handle](const WasmHandleSharedPtr& w) { wasm_handle = w; }); EXPECT_NE(wasm_handle, nullptr); @@ -674,7 +686,7 @@ TEST_P(WasmCommonTest, VmCache) { lifecycle_callback(post_cb); WasmHandleSharedPtr wasm_handle2; - createWasm(vm_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, + createWasm(vm_config, cr_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, lifecycle_notifier, remote_data_provider, [&wasm_handle2](const WasmHandleSharedPtr& w) { wasm_handle2 = w; }); EXPECT_NE(wasm_handle2, nullptr); @@ -742,6 +754,7 @@ TEST_P(WasmCommonTest, RemoteCode) { absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); VmConfig vm_config; + CapabilityRestrictionConfig cr_config; vm_config.set_runtime(absl::StrCat("envoy.wasm.runtime.", GetParam())); ProtobufWkt::BytesValue vm_configuration_bytes; vm_configuration_bytes.set_value(vm_configuration); @@ -776,7 +789,7 @@ TEST_P(WasmCommonTest, RemoteCode) { EXPECT_CALL(init_manager, add(_)).WillOnce(Invoke([&](const Init::Target& target) { init_target_handle = target.createHandle("test"); })); - createWasm(vm_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, + createWasm(vm_config, cr_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, lifecycle_notifier, remote_data_provider, [&wasm_handle](const WasmHandleSharedPtr& w) { wasm_handle = w; }); @@ -844,6 +857,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); VmConfig vm_config; + CapabilityRestrictionConfig cr_config; vm_config.set_runtime(absl::StrCat("envoy.wasm.runtime.", GetParam())); ProtobufWkt::StringValue vm_configuration_string; vm_configuration_string.set_value(vm_configuration); @@ -891,7 +905,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { EXPECT_CALL(init_manager, add(_)).WillOnce(Invoke([&](const Init::Target& target) { init_target_handle = target.createHandle("test"); })); - createWasm(vm_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, + createWasm(vm_config, cr_config, plugin, scope, cluster_manager, init_manager, *dispatcher, *api, lifecycle_notifier, remote_data_provider, [&wasm_handle](const WasmHandleSharedPtr& w) { wasm_handle = w; }); @@ -933,6 +947,210 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { dispatcher->clearDeferredDeleteList(); } +// test that wasm imports/exports do not work when ABI restriction is enforced +TEST_P(WasmCommonTest, RestrictABIFunctions) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "restrict_all"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_restriction_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, GetParam(), plugin_configuration, false, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + + // restriction enforced if allowed_abi_functions is non-empty + std::unordered_set allowed_abi_functions{"foo"}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); + + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_log")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // on_vm_start will trigger proxy_log, but expect no call because both are restricted + // trigger on_configure, but expect it to be blocked because it is restricted + wasm->setCreateContextForTesting( + nullptr, [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + auto root_context = new TestContext(wasm, plugin); + EXPECT_CALL(*root_context, + log_(spdlog::level::info, Eq("after on_vm_start, before proxy_log"))) + .Times(0); + return root_context; + }); + wasm->start(plugin); +} + +// test with proxy_on_vm_start allowed, but proxy_log restricted +TEST_P(WasmCommonTest, AllowModuleImplementedABIFunctions) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "allow_on_vm_start"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_restriction_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, GetParam(), plugin_configuration, false, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions{"proxy_on_vm_start"}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); + + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_log")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // on_vm_start will trigger proxy_log, but expect no call because both are restricted + // trigger on_configure, but expect it to be blocked because it is restricted + wasm->setCreateContextForTesting( + nullptr, [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + auto root_context = new TestContext(wasm, plugin); + EXPECT_CALL(*root_context, + log_(spdlog::level::info, Eq("after on_vm_start, before proxy_log"))) + .Times(0); + return root_context; + }); + wasm->start(plugin); +} + +// test with both proxy_on_vm_start and proxy_log allowed +TEST_P(WasmCommonTest, AllowABIFunctions) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "allow_log"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_restriction_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, GetParam(), plugin_configuration, false, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions{"proxy_on_vm_start", "proxy_log"}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); + + // Restrict capabilites, but allow proxy_log + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_on_vm_start")); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_log")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // Module will call proxy_log, expect success since allowed + wasm->setCreateContextForTesting( + nullptr, [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + auto root_context = new TestContext(wasm, plugin); + EXPECT_CALL(*root_context, + log_(spdlog::level::info, Eq("after on_vm_start, before proxy_log"))) + .Times(1); + return root_context; + }); + wasm->start(plugin); +} + +// test that a copy-constructed thread-local Wasm still enforces the same policy +TEST_P(WasmCommonTest, ThreadLocalCopyRetainsEnforcement) { + if (GetParam() != "v8") { + return; + } + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + NiceMock local_info; + auto name = ""; + auto root_id = ""; + auto vm_id = ""; + auto vm_configuration = "restrict_all"; + auto plugin_configuration = ""; + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_restriction_cpp.wasm")); + EXPECT_FALSE(code.empty()); + auto plugin = std::make_shared( + name, root_id, vm_id, GetParam(), plugin_configuration, false, + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + auto vm_key = proxy_wasm::makeVmKey(vm_id, vm_configuration, code); + std::unordered_set allowed_abi_functions{"proxy_on_vm_start"}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_abi_functions, scope, cluster_manager, *dispatcher); + + // Restrict capabilities + EXPECT_FALSE(wasm->abiFunctionAllowed("on_vm_start")); + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_log")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + auto wasm_handle = std::make_shared(std::move(wasm)); + auto thread_local_wasm = std::make_shared(wasm_handle, *dispatcher); + + EXPECT_NE(thread_local_wasm, nullptr); + context = std::make_unique(thread_local_wasm.get()); + EXPECT_TRUE(thread_local_wasm->initialize(code, false)); + + EXPECT_FALSE(thread_local_wasm->abiFunctionAllowed("on_vm_start")); + EXPECT_FALSE(thread_local_wasm->abiFunctionAllowed("proxy_log")); + + // Module will call proxy_log, expect no call since all capabilities restricted + thread_local_wasm->setCreateContextForTesting( + nullptr, [](Wasm* thread_local_wasm, const std::shared_ptr& plugin) -> ContextBase* { + auto root_context = new TestContext(thread_local_wasm, plugin); + EXPECT_CALL(*root_context, + log_(spdlog::level::info, Eq("after on_vm_start, before proxy_log"))) + .Times(0); + return root_context; + }); + thread_local_wasm->start(plugin); +} + class WasmCommonContextTest : public Common::Wasm::WasmTestBase> { public: diff --git a/test/extensions/filters/network/wasm/BUILD b/test/extensions/filters/network/wasm/BUILD index bfbd34124d5f..2503005d660e 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -41,7 +41,6 @@ envoy_extension_cc_test( name = "wasm_filter_test", srcs = ["wasm_filter_test.cc"], data = envoy_select_wasm([ - "//test/extensions/filters/network/wasm/test_data:logging_rust.wasm", "//test/extensions/filters/network/wasm/test_data:test_cpp.wasm", ]), extension_name = "envoy.filters.network.wasm", diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 6d93a167f674..cdf7639953bf 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -176,6 +176,93 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { EXPECT_EQ(filter_config.createFilter(), nullptr); } +TEST_P(WasmNetworkFilterConfigTest, FilterConfigABIUnrestrictedByDefault) { + if (GetParam() == "null") { + return; + } + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( + config: + vm_config: + runtime: "envoy.wasm.runtime.)EOF", + GetParam(), R"EOF(" + code: + local: + filename: "{{ test_rundir }}/test/extensions/filters/network/wasm/test_data/test_cpp.wasm" + capability_restriction_config: + allowed_abi_functions: + )EOF")); + + envoy::extensions::filters::network::wasm::v3::Wasm proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); + auto wasm = filter_config.wasmForTest(); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_log")); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_on_vm_start")); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_http_call")); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_on_log")); + EXPECT_FALSE(filter_config.createFilter() == nullptr); +} + +TEST_P(WasmNetworkFilterConfigTest, FilterConfigABIRestriction) { + if (GetParam() == "null") { + return; + } + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( + config: + vm_config: + runtime: "envoy.wasm.runtime.)EOF", + GetParam(), R"EOF(" + code: + local: + filename: "{{ test_rundir }}/test/extensions/filters/network/wasm/test_data/test_cpp.wasm" + capability_restriction_config: + allowed_abi_functions: + - proxy_log + - proxy_on_new_connection + )EOF")); + + envoy::extensions::filters::network::wasm::v3::Wasm proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); + auto wasm = filter_config.wasmForTest(); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_log")); + EXPECT_TRUE(wasm->abiFunctionAllowed("proxy_on_new_connection")); + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_http_call")); + EXPECT_FALSE(wasm->abiFunctionAllowed("proxy_on_log")); + EXPECT_FALSE(filter_config.createFilter() == nullptr); +} + +TEST_P(WasmNetworkFilterConfigTest, FilterConfigAllowOnVmStart) { + if (GetParam() == "null") { + return; + } + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( + config: + vm_config: + runtime: "envoy.wasm.runtime.)EOF", + GetParam(), R"EOF(" + code: + local: + filename: "{{ test_rundir }}/test/extensions/filters/network/wasm/test_data/test_cpp.wasm" + capability_restriction_config: + allowed_abi_functions: + - proxy_on_vm_start + - proxy_get_property + - proxy_on_context_create + )EOF")); + + envoy::extensions::filters::network::wasm::v3::Wasm proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + WasmFilterConfig factory; + Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context_); + EXPECT_CALL(init_watcher_, ready()); + context_.initManager().initialize(init_watcher_); + EXPECT_EQ(context_.initManager().state(), Init::Manager::State::Initialized); + Network::MockConnection connection; + EXPECT_CALL(connection, addFilter(_)); + cb(connection); +} + } // namespace Wasm } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 0b602428083c..1d7390c1ba5e 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -45,7 +45,8 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< WasmNetworkFilterTest() = default; ~WasmNetworkFilterTest() override = default; - void setupConfig(const std::string& code, std::string vm_configuration, bool fail_open = false) { + void setupConfig(const std::string& code, std::string vm_configuration, bool fail_open = false, + std::unordered_set allowed_abi_functions = {}) { if (code.empty()) { setupWasmCode(vm_configuration); } else { @@ -56,7 +57,8 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { return new TestRoot(wasm, plugin); }, - "" /* root_id */, "" /* vm_configuration */, fail_open); + "" /* root_id */, "" /* vm_configuration */, fail_open, "" /* plugin configuration*/, + allowed_abi_functions); } void setupFilter() { setupFilterBase(); } @@ -194,6 +196,78 @@ TEST_P(WasmNetworkFilterTest, SegvFailOpen) { EXPECT_EQ(Network::FilterStatus::Continue, filter().onData(fake_downstream_data, false)); } +TEST_P(WasmNetworkFilterTest, RestrictOnNewConnection) { + if (std::get<0>(GetParam()) != "v8" || std::get<1>(GetParam()) != "cpp") { + return; + } + std::unordered_set allowed_abi_functions = { + "proxy_on_context_create", "proxy_get_property", "proxy_log", "proxy_on_new_connection"}; + setupConfig("", "logging", false, allowed_abi_functions); + setupFilter(); + + // Expect this call, because proxy_on_new_connection is allowed + EXPECT_CALL(filter(), log_(spdlog::level::trace, Eq(absl::string_view("onNewConnection 2")))); + EXPECT_EQ(Network::FilterStatus::Continue, filter().onNewConnection()); + + // Do not expect this call, because proxy_on_downstream_connection_close is not allowed + EXPECT_CALL(filter(), + log_(spdlog::level::trace, Eq(absl::string_view("onDownstreamConnectionClose 2 1")))) + .Times(0); + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + // Noop. + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + filter().testClose(); +} + +TEST_P(WasmNetworkFilterTest, RestrictOnDownstreamConnectionClose) { + if (std::get<0>(GetParam()) != "v8" || std::get<1>(GetParam()) != "cpp") { + return; + } + std::unordered_set allowed_abi_functions = {"proxy_on_context_create", + "proxy_get_property", "proxy_log", + "proxy_on_downstream_connection_close"}; + setupConfig("", "logging", false, allowed_abi_functions); + setupFilter(); + + // Do not expect this call, because proxy_on_new_connection is not allowed + EXPECT_CALL(filter(), log_(spdlog::level::trace, Eq(absl::string_view("onNewConnection 2")))) + .Times(0); + EXPECT_EQ(Network::FilterStatus::Continue, filter().onNewConnection()); + + // Expect this call, because proxy_on_downstream_connection_close allowed + EXPECT_CALL(filter(), + log_(spdlog::level::trace, Eq(absl::string_view("onDownstreamConnectionClose 2 1")))); + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + // Noop. + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + filter().testClose(); +} + +TEST_P(WasmNetworkFilterTest, RestrictLog) { + if (std::get<0>(GetParam()) != "v8" || std::get<1>(GetParam()) != "cpp") { + return; + } + std::unordered_set allowed_abi_functions = { + "proxy_on_context_create", "proxy_get_property", "proxy_on_new_connection", + "proxy_on_downstream_connection_close"}; + setupConfig("", "logging", false, allowed_abi_functions); + setupFilter(); + + // Do not expect this call, because proxy_log is not allowed + EXPECT_CALL(filter(), log_(spdlog::level::trace, Eq(absl::string_view("onNewConnection 2")))) + .Times(0); + EXPECT_EQ(Network::FilterStatus::Continue, filter().onNewConnection()); + + // Do not expect this call, because proxy_log is not allowed + EXPECT_CALL(filter(), + log_(spdlog::level::trace, Eq(absl::string_view("onDownstreamConnectionClose 2 1")))) + .Times(0); + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + // Noop. + read_filter_callbacks_.connection_.close(Network::ConnectionCloseType::FlushWrite); + filter().testClose(); +} + } // namespace Wasm } // namespace NetworkFilters } // namespace Extensions diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 2cfc796084eb..07c4140d3c73 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -59,7 +59,8 @@ template class WasmTestBase : public Base { void setupBase(const std::string& runtime, const std::string& code, CreateContextFn create_root, std::string root_id = "", std::string vm_configuration = "", - bool fail_open = false, std::string plugin_configuration = "") { + bool fail_open = false, std::string plugin_configuration = "", + std::unordered_set allowed_abi_functions = {}) { envoy::extensions::wasm::v3::VmConfig vm_config; vm_config.set_vm_id("vm_id"); vm_config.set_runtime(absl::StrCat("envoy.wasm.runtime.", runtime)); @@ -67,6 +68,9 @@ template class WasmTestBase : public Base { vm_configuration_string.set_value(vm_configuration); vm_config.mutable_configuration()->PackFrom(vm_configuration_string); vm_config.mutable_code()->mutable_local()->set_inline_bytes(code); + envoy::extensions::wasm::v3::CapabilityRestrictionConfig cr_config; + *cr_config.mutable_allowed_abi_functions() = {allowed_abi_functions.begin(), + allowed_abi_functions.end()}; Api::ApiPtr api = Api::createApiForTest(stats_store_); scope_ = Stats::ScopeSharedPtr(stats_store_.createScope("wasm.")); auto name = "plugin_name"; @@ -76,7 +80,7 @@ template class WasmTestBase : public Base { envoy::config::core::v3::TrafficDirection::INBOUND, local_info_, &listener_metadata_); // Passes ownership of root_context_. Extensions::Common::Wasm::createWasm( - vm_config, plugin_, scope_, cluster_manager_, init_manager_, dispatcher_, *api, + vm_config, cr_config, plugin_, scope_, cluster_manager_, init_manager_, dispatcher_, *api, lifecycle_notifier_, remote_data_provider_, [this](WasmHandleSharedPtr wasm) { wasm_ = wasm; }, create_root); if (wasm_) {