diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index c6affb810611..83a83aa5670b 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -18,6 +18,28 @@ 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 Proxy-Wasm capabilities which will be allowed. Capabilities are mapped by + // name. The *SanitizationConfig* which each capability maps to is currently unimplemented and ignored, + // and so should be left empty. + // + // The capability names are given in the + // `Proxy-Wasm ABI `_. + // Additionally, the following WASI capabilities from + // `this list `_ + // are implemented and can be allowed: + // *fd_write*, *fd_read*, *fd_seek*, *fd_close*, *fd_fdstat_get*, *environ_get*, *environ_sizes_get*, + // *args_get*, *args_sizes_get*, *proc_exit*, *clock_time_get*, *random_get*. + map allowed_capabilities = 1; +} + +// Configuration for sanitization of inputs to an allowed capability. +// +// NOTE: This is currently unimplemented. +message SanitizationConfig { +} + // Configuration for a Wasm VM. // [#next-free-field: 7] message VmConfig { @@ -74,7 +96,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 +127,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/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3b0d623e3761..0b751dd5c73c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -889,8 +889,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "6dab125d7a668c7158848b6f48c67fd827c952e6", - sha256 = "b5c73ed053a7079bd8bf53b14c4811e87ae521d9fcf4769ec5b248202a27600d", + version = "5a53cf4b231599e1d2a1f2f4598fdfbb727ff948", + sha256 = "600dbc651a2837e6f1db964eb7e1078e5e338049a34c9ab47415dfa7f3de5478", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -905,7 +905,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2020-12-16", + release_date = "2021-01-12", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index c6affb810611..83a83aa5670b 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -18,6 +18,28 @@ 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 Proxy-Wasm capabilities which will be allowed. Capabilities are mapped by + // name. The *SanitizationConfig* which each capability maps to is currently unimplemented and ignored, + // and so should be left empty. + // + // The capability names are given in the + // `Proxy-Wasm ABI `_. + // Additionally, the following WASI capabilities from + // `this list `_ + // are implemented and can be allowed: + // *fd_write*, *fd_read*, *fd_seek*, *fd_close*, *fd_fdstat_get*, *environ_get*, *environ_sizes_get*, + // *args_get*, *args_sizes_get*, *proc_exit*, *clock_time_get*, *random_get*. + map allowed_capabilities = 1; +} + +// Configuration for sanitization of inputs to an allowed capability. +// +// NOTE: This is currently unimplemented. +message SanitizationConfig { +} + // Configuration for a Wasm VM. // [#next-free-field: 7] message VmConfig { @@ -74,7 +96,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 +127,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 545a761391d2..08f871e19263 100644 --- a/source/extensions/bootstrap/wasm/config.cc +++ b/source/extensions/bootstrap/wasm/config.cc @@ -50,9 +50,10 @@ void WasmServiceExtension::createWasm(Server::Configuration::ServerFactoryContex }; 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 f222523e0275..133324f7de38 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -42,6 +42,8 @@ using proxy_wasm::WasmResult; using proxy_wasm::WasmStreamType; using VmConfig = envoy::extensions::wasm::v3::VmConfig; +using CapabilityRestrictionConfig = envoy::extensions::wasm::v3::CapabilityRestrictionConfig; +using SanitizationConfig = envoy::extensions::wasm::v3::SanitizationConfig; 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 cc4efd963fdf..1690b70c64d7 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -100,10 +100,11 @@ 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), vm_id, vm_configuration, vm_key), scope_(scope), - cluster_manager_(cluster_manager), dispatcher_(dispatcher), + absl::string_view vm_key, proxy_wasm::AllowedCapabilitiesMap allowed_capabilities, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher) + : WasmBase(createWasmVm(runtime), vm_id, vm_configuration, vm_key, allowed_capabilities), + 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, ".")), @@ -312,8 +313,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& capability_restriction_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, @@ -380,8 +382,8 @@ 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, - &lifecycle_notifier, create_root_context_for_testing, + auto complete_cb = [cb, vm_config, capability_restriction_config, plugin, scope, &cluster_manager, + &dispatcher, &lifecycle_notifier, create_root_context_for_testing, wasm_extension](std::string code) -> bool { if (code.empty()) { cb(nullptr); @@ -391,10 +393,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, - wasm_factory](absl::string_view vm_key) -> WasmHandleBaseSharedPtr { - return wasm_factory(vm_config, scope, cluster_manager, dispatcher, lifecycle_notifier, - vm_key); + [&vm_config, &capability_restriction_config, scope, &cluster_manager, &dispatcher, + &lifecycle_notifier, wasm_factory](absl::string_view vm_key) -> WasmHandleBaseSharedPtr { + return wasm_factory(vm_config, capability_restriction_config, scope, cluster_manager, + dispatcher, lifecycle_notifier, vm_key); }; auto wasm = proxy_wasm::createWasm( vm_key, code, plugin, proxy_wasm_factory, @@ -469,15 +471,17 @@ 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& capability_restriction_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, capability_restriction_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..29e9e2768ae9 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, proxy_wasm::AllowedCapabilitiesMap allowed_capabilities, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher); Wasm(std::shared_ptr other, Event::Dispatcher& dispatcher); ~Wasm() override; @@ -160,9 +161,11 @@ 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& capability_restriction_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 6c99a8124dc7..c3c443e4d006 100644 --- a/source/extensions/common/wasm/wasm_extension.cc +++ b/source/extensions/common/wasm/wasm_extension.cc @@ -48,13 +48,19 @@ 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 capability_restriction_config, + const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, + Event::Dispatcher& dispatcher, Server::ServerLifecycleNotifier& lifecycle_notifier, absl::string_view vm_key) -> WasmHandleBaseSharedPtr { + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; + for (auto& capability : capability_restriction_config.allowed_capabilities()) { + // TODO(rapilado): Set the SanitizationConfig fields once sanitization is implemented. + allowed_capabilities[capability.first] = proxy_wasm::SanitizationConfig(); + } 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_capabilities, 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 8fb6b9b07f77..e839e34cad83 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& capability_restriction_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 9dbfd82911eb..7727cfc1e036 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, 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 2eaf5083095a..8d404f6fdd1a 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_capabilities, 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_; + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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..925ea1db3824 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 = [ @@ -49,6 +50,7 @@ envoy_cc_test( "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/common/wasm/test_data:test_context_cpp_plugin", "//test/extensions/common/wasm/test_data:test_cpp_plugin", + "//test/extensions/common/wasm/test_data:test_restriction_cpp_plugin", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/extensions/common/wasm/test_data/BUILD b/test/extensions/common/wasm/test_data/BUILD index 5b84e0d23993..9160fe94f739 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"], @@ -63,6 +81,11 @@ envoy_wasm_cc_binary( ], ) +envoy_wasm_cc_binary( + name = "test_restriction_cpp.wasm", + srcs = ["test_restriction_cpp.cc"], +) + 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..29037b9a9eb5 --- /dev/null +++ b/test/extensions/common/wasm/test_data/test_restriction_cpp.cc @@ -0,0 +1,33 @@ +#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 log_message = "after proxy_on_vm_start: written by proxy_log"; + proxy_log(LogLevel::info, log_message.c_str(), log_message.size()); + fprintf(stdout, "WASI write to stdout\n"); + return 1; +} + +WASM_EXPORT(void, proxy_on_context_create, (uint32_t context_id, uint32_t parent_context_id)) { + (void)(context_id); + (void)(parent_context_id); + std::string log_message = "after proxy_on_context_create: written by proxy_log"; + proxy_log(LogLevel::info, log_message.c_str(), log_message.size()); + return; +} + +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..692f473b84cf 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.")); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; auto wasm = std::make_unique( - "envoy.wasm.runtime.null", "", "", "", scope, cluster_manager, *dispatcher); + "envoy.wasm.runtime.null", "", "", "", allowed_capabilities, 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 5233f535394b..38403cd6a940 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)); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; + auto wasm = std::make_shared(std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), "", "vm_configuration", "", + allowed_capabilities, 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); @@ -183,9 +184,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_NE(wasm->buildVersion(), ""); EXPECT_NE(std::unique_ptr(wasm->createContext(plugin)), nullptr); @@ -254,9 +256,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_FALSE(wasm->initialize(code, false)); EXPECT_TRUE(wasm->isFailed()); } @@ -283,9 +286,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_TRUE(wasm->initialize(code, false)); TestContext* root_context = nullptr; wasm->setCreateContextForTesting( @@ -326,9 +330,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); auto context = std::make_unique(wasm.get()); EXPECT_TRUE(wasm->initialize(code, false)); @@ -366,9 +371,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -406,9 +412,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -474,9 +481,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); EXPECT_TRUE(wasm->initialize(code, false)); wasm->setCreateContextForTesting( @@ -510,9 +518,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -555,9 +564,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -602,9 +612,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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities; 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_capabilities, scope, cluster_manager, *dispatcher); EXPECT_NE(wasm, nullptr); std::string code; if (GetParam() != "null") { @@ -655,6 +666,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); @@ -670,7 +682,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); @@ -678,7 +690,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); @@ -746,6 +758,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); @@ -781,7 +794,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; }); @@ -849,6 +862,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); @@ -897,7 +911,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; }); @@ -939,6 +953,330 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { dispatcher->clearDeferredDeleteList(); } +// test that wasm imports/exports do not work when ABI restriction is enforced +TEST_P(WasmCommonTest, RestrictCapabilities) { + if (GetParam() == "null") { + 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_capabilities is non-empty + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"foo", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + EXPECT_FALSE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->capabilityAllowed("proxy_log")); + EXPECT_FALSE(wasm->capabilityAllowed("fd_write")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // expect no call because proxy_on_vm_start 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 proxy_on_vm_start: written by proxy_log"))) + .Times(0); + EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("WASI write to stdout"))).Times(0); + return root_context; + }); + wasm->start(plugin); +} + +// test with proxy_on_vm_start allowed, but proxy_log restricted +TEST_P(WasmCommonTest, AllowOnVmStart) { + if (GetParam() == "null") { + 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"proxy_on_vm_start", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->capabilityAllowed("proxy_log")); + EXPECT_FALSE(wasm->capabilityAllowed("fd_write")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // proxy_on_vm_start will trigger proxy_log and fd_write, but expect no call because both are + // 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 proxy_on_vm_start: written by proxy_log"))) + .Times(0); + EXPECT_CALL(*root_context, log_(spdlog::level::info, + Eq("after proxy_on_context_create: written by proxy_log"))) + .Times(0); + EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("WASI write to stdout"))).Times(0); + return root_context; + }); + wasm->start(plugin); +} + +// test with both proxy_on_vm_start and proxy_log allowed, but WASI restricted +TEST_P(WasmCommonTest, AllowLog) { + if (GetParam() == "null") { + 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"proxy_on_vm_start", proxy_wasm::SanitizationConfig()}, + {"proxy_log", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + // Restrict capabilities, but allow proxy_log + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_log")); + EXPECT_FALSE(wasm->capabilityAllowed("fd_write")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // Expect proxy_log since allowed, but no call to WASI since 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 proxy_on_vm_start: written by proxy_log"))); + EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("WASI write to stdout"))).Times(0); + return root_context; + }); + wasm->start(plugin); +} + +// test with both proxy_on_vm_start and fd_write allowed, but proxy_log restricted +TEST_P(WasmCommonTest, AllowWASI) { + if (GetParam() == "null") { + 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_wasi"; + 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"proxy_on_vm_start", proxy_wasm::SanitizationConfig()}, + {"fd_write", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + // Restrict capabilities, but allow fd_write + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->capabilityAllowed("proxy_log")); + EXPECT_TRUE(wasm->capabilityAllowed("fd_write")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + 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 proxy_on_vm_start: written by proxy_log"))) + .Times(0); + EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("WASI write to stdout"))); + return root_context; + }); + wasm->start(plugin); +} + +// test a different callback besides proxy_on_vm_start +TEST_P(WasmCommonTest, AllowOnContextCreate) { + if (GetParam() == "null") { + 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_context_create"; + 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"proxy_on_vm_start", proxy_wasm::SanitizationConfig()}, + {"proxy_on_context_create", proxy_wasm::SanitizationConfig()}, + {"proxy_log", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + // Restrict capabilities, but allow proxy_log + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_context_create")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_log")); + + EXPECT_NE(wasm, nullptr); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, false)); + + // Expect two calls to proxy_log, from proxy_on_vm_start and from proxy_on_context_create + 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 proxy_on_vm_start: written by proxy_log"))); + EXPECT_CALL(*root_context, log_(spdlog::level::info, + Eq("after proxy_on_context_create: written by proxy_log"))); + 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() == "null") { + 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 = "thread_local_copy"; + 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); + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities{ + {"proxy_on_vm_start", proxy_wasm::SanitizationConfig()}, + {"fd_write", proxy_wasm::SanitizationConfig()}}; + auto wasm = std::make_unique( + absl::StrCat("envoy.wasm.runtime.", GetParam()), vm_id, vm_configuration, vm_key, + allowed_capabilities, scope, cluster_manager, *dispatcher); + + // Restrict capabilities + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_FALSE(wasm->capabilityAllowed("proxy_log")); + EXPECT_TRUE(wasm->capabilityAllowed("fd_write")); + + 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_TRUE(thread_local_wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_FALSE(thread_local_wasm->capabilityAllowed("proxy_log")); + EXPECT_TRUE(thread_local_wasm->capabilityAllowed("fd_write")); + + // 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 proxy_on_vm_start: written by proxy_log"))) + .Times(0); + EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("WASI write to stdout"))); + 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..991c12a96ce0 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -39,6 +39,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "wasm_filter_test", + size = "large", srcs = ["wasm_filter_test.cc"], data = envoy_select_wasm([ "//test/extensions/filters/network/wasm/test_data:logging_rust.wasm", diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 6d93a167f674..403f5b8f86d1 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, FilterConfigCapabilitiesUnrestrictedByDefault) { + 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_capabilities: + )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->capabilityAllowed("proxy_log")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_vm_start")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_http_call")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_log")); + EXPECT_FALSE(filter_config.createFilter() == nullptr); +} + +TEST_P(WasmNetworkFilterConfigTest, FilterConfigCapabilityRestriction) { + 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_capabilities: + 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->capabilityAllowed("proxy_log")); + EXPECT_TRUE(wasm->capabilityAllowed("proxy_on_new_connection")); + EXPECT_FALSE(wasm->capabilityAllowed("proxy_http_call")); + EXPECT_FALSE(wasm->capabilityAllowed("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_capabilities: + 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 ac479d0adf13..86a933097cc6 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -22,6 +22,7 @@ using Envoy::Extensions::Common::Wasm::Context; using Envoy::Extensions::Common::Wasm::Plugin; using Envoy::Extensions::Common::Wasm::PluginSharedPtr; using Envoy::Extensions::Common::Wasm::Wasm; +using proxy_wasm::AllowedCapabilitiesMap; using proxy_wasm::ContextBase; class TestFilter : public Context { @@ -45,7 +46,8 @@ class WasmNetworkFilterTest : public Extensions::Common::Wasm::WasmNetworkFilter 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, + AllowedCapabilitiesMap allowed_capabilities = {}) { if (code.empty()) { setupWasmCode(vm_configuration); } else { @@ -56,7 +58,8 @@ class WasmNetworkFilterTest : public Extensions::Common::Wasm::WasmNetworkFilter [](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_capabilities); } void setupFilter() { setupFilterBase(); } @@ -190,6 +193,85 @@ TEST_P(WasmNetworkFilterTest, SegvFailOpen) { EXPECT_EQ(Network::FilterStatus::Continue, filter().onData(fake_downstream_data, false)); } +TEST_P(WasmNetworkFilterTest, RestrictOnNewConnection) { + if (std::get<0>(GetParam()) == "null") { + return; + } + AllowedCapabilitiesMap allowed_capabilities = { + {"proxy_on_context_create", proxy_wasm::SanitizationConfig()}, + {"proxy_get_property", proxy_wasm::SanitizationConfig()}, + {"proxy_log", proxy_wasm::SanitizationConfig()}, + {"proxy_on_new_connection", proxy_wasm::SanitizationConfig()}}; + setupConfig("", "logging", false, allowed_capabilities); + 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()) == "null") { + return; + } + AllowedCapabilitiesMap allowed_capabilities = { + {"proxy_on_context_create", proxy_wasm::SanitizationConfig()}, + {"proxy_get_property", proxy_wasm::SanitizationConfig()}, + {"proxy_log", proxy_wasm::SanitizationConfig()}, + {"proxy_on_downstream_connection_close", proxy_wasm::SanitizationConfig()}}; + setupConfig("", "logging", false, allowed_capabilities); + 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()) == "null") { + return; + } + AllowedCapabilitiesMap allowed_capabilities = { + {"proxy_on_context_create", proxy_wasm::SanitizationConfig()}, + {"proxy_get_property", proxy_wasm::SanitizationConfig()}, + {"proxy_on_new_connection", proxy_wasm::SanitizationConfig()}, + {"proxy_on_downstream_connection_close", proxy_wasm::SanitizationConfig()}}; + setupConfig("", "logging", false, allowed_capabilities); + 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 af298b72b61c..1655e59ca296 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -58,7 +58,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 = "", + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities = {}) { envoy::extensions::wasm::v3::VmConfig vm_config; vm_config.set_vm_id("vm_id"); vm_config.set_runtime(absl::StrCat("envoy.wasm.runtime.", runtime)); @@ -66,6 +67,13 @@ 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; + Protobuf::Map allowed_capabilities_; + for (auto& capability : allowed_capabilities) { + // TODO(rapilado): Set the SanitizationConfig fields once sanitization is implemented. + allowed_capabilities_[capability.first] = SanitizationConfig(); + } + *cr_config.mutable_allowed_capabilities() = allowed_capabilities_; Api::ApiPtr api = Api::createApiForTest(stats_store_); scope_ = Stats::ScopeSharedPtr(stats_store_.createScope("wasm.")); auto name = "plugin_name"; @@ -75,7 +83,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_) { diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 48f4c1d953de..8a5bea2d024d 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -37,6 +37,7 @@ Preconnecting STATNAME SkyWalking TIDs +WASI ceil CCM CHACHA @@ -548,6 +549,7 @@ deflater deletable deleter delim +denylist deque deprecations dereference @@ -594,6 +596,7 @@ enqueued enqueues enum enums +environ epoll errno etag