diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index dc2844d24748..5ed7b8e51d18 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -40,7 +40,7 @@ message SanitizationConfig { } // Configuration for a Wasm VM. -// [#next-free-field: 7] +// [#next-free-field: 8] message VmConfig { // An ID which will be used along with a hash of the wasm code (or the name of the registered Null // VM plugin) to determine which VM will be used for the plugin. All plugins which use the same @@ -93,6 +93,22 @@ message VmConfig { // update and do a background fetch to fill the cache, otherwise fetch the code asynchronously and enter // warming state. bool nack_on_code_cache_miss = 6; + + // Specifies environment variables to be injected to this VM which will be available through + // WASI's ``environ_get`` and ``environ_get_sizes`` system calls. Note that these functions are mostly implicitly + // called in your language's standard library, so you do not need to call them directly and you can access to env + // vars just like when you do on native platforms. + // Warning: Envoy rejects the configuration if there's conflict of key space. + EnvironmentVariables environment_variables = 7; +} + +message EnvironmentVariables { + // The keys of *Envoy's* environment variables exposed to this VM. In other words, if a key exists in Envoy's environment + // variables, then that key-value pair will be injected. Note that if a key does not exist, it will be ignored. + repeated string host_env_keys = 1; + + // Explicitly given key-value pairs to be injected to this VM in the form of "KEY=VALUE". + map key_values = 2; } // Base Configuration for Wasm Plugins e.g. filters and services. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 0660d1cb8aa1..ca79aa1c3ce1 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -936,8 +936,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 = "d1a2a7db59a72edacc9a6286b64280b72767d2d0", - sha256 = "3e81235c963291bd01f9425ed6e34d6b44ca0adc9f281b0cafc5cba6ad3bcc6d", + version = "dd33aa6d825cd63deaa0f793c8caca8a9132a05f", + sha256 = "dac7998459616396684f17f35890fb03116450110f4d3bce3d9ae652141f2d3f", 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"], @@ -952,7 +952,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2021-02-19", + release_date = "2021-03-03", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/bazel/wasm/wasm.bzl b/bazel/wasm/wasm.bzl index 341851b9baa2..7a79a7cce5eb 100644 --- a/bazel/wasm/wasm.bzl +++ b/bazel/wasm/wasm.bzl @@ -6,6 +6,11 @@ def _wasm_rust_transition_impl(settings, attr): "//command_line_option:platforms": "@rules_rust//rust/platform:wasm", } +def _wasi_rust_transition_impl(settings, attr): + return { + "//command_line_option:platforms": "@rules_rust//rust/platform:wasi", + } + wasm_rust_transition = transition( implementation = _wasm_rust_transition_impl, inputs = [], @@ -14,6 +19,14 @@ wasm_rust_transition = transition( ], ) +wasi_rust_transition = transition( + implementation = _wasi_rust_transition_impl, + inputs = [], + outputs = [ + "//command_line_option:platforms", + ], +) + def _wasm_binary_impl(ctx): out = ctx.actions.declare_file(ctx.label.name) if ctx.attr.precompile: @@ -47,6 +60,11 @@ wasm_rust_binary_rule = rule( attrs = _wasm_attrs(wasm_rust_transition), ) +wasi_rust_binary_rule = rule( + implementation = _wasm_binary_impl, + attrs = _wasm_attrs(wasi_rust_transition), +) + def envoy_wasm_cc_binary(name, deps = [], tags = [], **kwargs): wasm_cc_binary( name = name, @@ -55,7 +73,7 @@ def envoy_wasm_cc_binary(name, deps = [], tags = [], **kwargs): **kwargs ) -def wasm_rust_binary(name, tags = [], **kwargs): +def wasm_rust_binary(name, tags = [], wasi = False, **kwargs): wasm_name = "_wasm_" + name.replace(".", "_") kwargs.setdefault("visibility", ["//visibility:public"]) @@ -68,7 +86,11 @@ def wasm_rust_binary(name, tags = [], **kwargs): **kwargs ) - wasm_rust_binary_rule( + bin_rule = wasm_rust_binary_rule + if wasi: + bin_rule = wasi_rust_binary_rule + + bin_rule( name = name, precompile = select({ "@envoy//bazel:linux_x86_64": True, diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index dc2844d24748..5ed7b8e51d18 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -40,7 +40,7 @@ message SanitizationConfig { } // Configuration for a Wasm VM. -// [#next-free-field: 7] +// [#next-free-field: 8] message VmConfig { // An ID which will be used along with a hash of the wasm code (or the name of the registered Null // VM plugin) to determine which VM will be used for the plugin. All plugins which use the same @@ -93,6 +93,22 @@ message VmConfig { // update and do a background fetch to fill the cache, otherwise fetch the code asynchronously and enter // warming state. bool nack_on_code_cache_miss = 6; + + // Specifies environment variables to be injected to this VM which will be available through + // WASI's ``environ_get`` and ``environ_get_sizes`` system calls. Note that these functions are mostly implicitly + // called in your language's standard library, so you do not need to call them directly and you can access to env + // vars just like when you do on native platforms. + // Warning: Envoy rejects the configuration if there's conflict of key space. + EnvironmentVariables environment_variables = 7; +} + +message EnvironmentVariables { + // The keys of *Envoy's* environment variables exposed to this VM. In other words, if a key exists in Envoy's environment + // variables, then that key-value pair will be injected. Note that if a key does not exist, it will be ignored. + repeated string host_env_keys = 1; + + // Explicitly given key-value pairs to be injected to this VM in the form of "KEY=VALUE". + map key_values = 2; } // Base Configuration for Wasm Plugins e.g. filters and services. diff --git a/source/extensions/common/wasm/plugin.cc b/source/extensions/common/wasm/plugin.cc index fa2ef38b3aca..b01ac0324f53 100644 --- a/source/extensions/common/wasm/plugin.cc +++ b/source/extensions/common/wasm/plugin.cc @@ -1,12 +1,8 @@ #include "extensions/common/wasm/plugin.h" -#include +#include "envoy/common/exception.h" -#include "envoy/extensions/wasm/v3/wasm.pb.validate.h" -#include "envoy/local_info/local_info.h" - -#include "common/protobuf/protobuf.h" -#include "common/protobuf/utility.h" +#include "extensions/common/wasm/well_known_names.h" #include "include/proxy-wasm/wasm.h" @@ -20,6 +16,45 @@ WasmConfig::WasmConfig(const envoy::extensions::wasm::v3::PluginConfig& config) // TODO(rapilado): Set the SanitizationConfig fields once sanitization is implemented. allowed_capabilities_[capability.first] = proxy_wasm::SanitizationConfig(); } + + if (config_.vm_config().has_environment_variables()) { + const auto& envs = config_.vm_config().environment_variables(); + + // We reject NullVm with key_values configuration + // since it directly accesses Envoy's env vars and we should not modify Envoy's env vars here. + // TODO(mathetake): Once proxy_get_map_values(type::EnvironmentVariables, ..) call is supported, + // then remove this restriction. + if (config.vm_config().runtime() == WasmRuntimeNames::get().Null && + !envs.key_values().empty()) { + throw EnvoyException("envoy.extensions.wasm.v3.VmConfig.EnvironmentVariables.key_values must " + "not be set for NullVm."); + } + + // Check key duplication. + absl::flat_hash_set keys; + for (const auto& env : envs.key_values()) { + keys.insert(env.first); + } + for (const auto& key : envs.host_env_keys()) { + if (!keys.insert(key).second) { + throw EnvoyException( + fmt::format("Key {} is duplicated in " + "envoy.extensions.wasm.v3.VmConfig.environment_variables for {}. " + "All the keys must be unique.", + key, config_.name())); + } + } + + // Construct merged key-value pairs. + for (const auto& env : envs.key_values()) { + envs_[env.first] = env.second; + } + for (const auto& key : envs.host_env_keys()) { + if (auto value = std::getenv(key.data())) { + envs_[key] = value; + } + } + } } } // namespace Wasm diff --git a/source/extensions/common/wasm/plugin.h b/source/extensions/common/wasm/plugin.h index aa1670dc5949..1e053e607ffd 100644 --- a/source/extensions/common/wasm/plugin.h +++ b/source/extensions/common/wasm/plugin.h @@ -15,15 +15,21 @@ namespace Extensions { namespace Common { namespace Wasm { +// clang-format off +using EnvironmentVariableMap = std::unordered_map; +// clang-format on + class WasmConfig { public: WasmConfig(const envoy::extensions::wasm::v3::PluginConfig& config); const envoy::extensions::wasm::v3::PluginConfig& config() { return config_; } proxy_wasm::AllowedCapabilitiesMap& allowedCapabilities() { return allowed_capabilities_; } + EnvironmentVariableMap& environmentVariables() { return envs_; } private: const envoy::extensions::wasm::v3::PluginConfig& config_; proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_{}; + EnvironmentVariableMap envs_; }; using WasmConfigPtr = std::unique_ptr; diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index b4e6233d8291..3895d0429cbc 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -91,7 +91,7 @@ Wasm::Wasm(WasmConfig& config, absl::string_view vm_key, const Stats::ScopeShare : WasmBase(createWasmVm(config.config().vm_config().runtime()), config.config().vm_config().vm_id(), MessageUtil::anyToBytes(config.config().vm_config().configuration()), vm_key, - config.allowedCapabilities()), + config.environmentVariables(), config.allowedCapabilities()), scope_(scope), cluster_manager_(cluster_manager), dispatcher_(dispatcher), time_source_(dispatcher.timeSource()), wasm_stats_(WasmStats{ALL_WASM_STATS( diff --git a/test/extensions/bootstrap/wasm/test_data/BUILD b/test/extensions/bootstrap/wasm/test_data/BUILD index a8f2ec270f1a..7256f0e7f0de 100644 --- a/test/extensions/bootstrap/wasm/test_data/BUILD +++ b/test/extensions/bootstrap/wasm/test_data/BUILD @@ -12,6 +12,7 @@ envoy_package() wasm_rust_binary( name = "logging_rust.wasm", srcs = ["logging_rust.rs"], + wasi = True, deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", "@proxy_wasm_rust_sdk//bazel/cargo:log", diff --git a/test/extensions/bootstrap/wasm/test_data/http_cpp.cc b/test/extensions/bootstrap/wasm/test_data/http_cpp.cc index 7b70f7b7d4e0..2f041221a8e4 100644 --- a/test/extensions/bootstrap/wasm/test_data/http_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/http_cpp.cc @@ -11,6 +11,14 @@ START_WASM_PLUGIN(WasmHttpCpp) WASM_EXPORT(void, proxy_abi_version_0_1_0, ()) {} WASM_EXPORT(uint32_t, proxy_on_configure, (uint32_t, uint32_t)) { + if (std::getenv("NON_EXIST")) { + logError("NON_EXIST should not exist in the environment variable key space"); + return 0; + } + if (std::string(std::getenv("KEY")) != "VALUE") { + logError("Environment variable KEY=VALUE must exist"); + return 0; + } proxy_set_tick_period_milliseconds(100); return 1; } @@ -31,6 +39,7 @@ WASM_EXPORT(void, proxy_on_tick, (uint32_t)) { } WASM_EXPORT(void, proxy_on_http_call_response, (uint32_t, uint32_t, uint32_t headers, uint32_t, uint32_t)) { + logTrace("KEY: " + std::string(std::getenv("KEY"))); if (headers != 0) { auto status = getHeaderMapValue(WasmHeaderMapType::HttpCallResponseHeaders, "status"); if ("200" == status->view()) { diff --git a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc index 9a5becfab1b3..f8923c1d6a04 100644 --- a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc @@ -9,6 +9,7 @@ extern "C" PROXY_WASM_KEEPALIVE void proxy_abi_version_0_1_0() {} extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_configure(uint32_t, uint32_t configuration_size) { + logTrace("ON_CONFIGURE: " + std::string(std::getenv("ON_CONFIGURE"))); fprintf(stdout, "printf stdout test"); fflush(stdout); fprintf(stderr, "printf stderr test"); @@ -29,6 +30,7 @@ extern "C" PROXY_WASM_KEEPALIVE void proxy_on_context_create(uint32_t, uint32_t) extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_vm_start(uint32_t, uint32_t) { return 1; } extern "C" PROXY_WASM_KEEPALIVE void proxy_on_tick(uint32_t) { + logTrace("ON_TICK: " + std::string(std::getenv("ON_TICK"))); const char* root_id = nullptr; size_t size; proxy_get_property("plugin_root_id", sizeof("plugin_root_id") - 1, &root_id, &size); diff --git a/test/extensions/bootstrap/wasm/test_data/logging_rust.rs b/test/extensions/bootstrap/wasm/test_data/logging_rust.rs index 49947fd975c3..ffc45b04b408 100644 --- a/test/extensions/bootstrap/wasm/test_data/logging_rust.rs +++ b/test/extensions/bootstrap/wasm/test_data/logging_rust.rs @@ -2,8 +2,16 @@ use log::{debug, error, info, trace, warn}; use proxy_wasm::traits::{Context, RootContext}; use proxy_wasm::types::LogLevel; +#[no_mangle] +extern "C" { + fn __wasilibc_initialize_environ(); +} + #[no_mangle] pub fn _start() { + unsafe { + __wasilibc_initialize_environ(); + } proxy_wasm::set_log_level(LogLevel::Trace); proxy_wasm::set_root_context(|_| -> Box { Box::new(TestRoot) }); } @@ -16,6 +24,7 @@ impl RootContext for TestRoot { } fn on_configure(&mut self, _: usize) -> bool { + trace!("ON_CONFIGURE: {}", std::env::var("ON_CONFIGURE").unwrap()); trace!("test trace logging"); debug!("test debug logging"); error!("test error logging"); @@ -26,6 +35,7 @@ impl RootContext for TestRoot { } fn on_tick(&mut self) { + trace!("ON_TICK: {}", std::env::var("ON_TICK").unwrap()); if let Some(value) = self.get_property(vec!["plugin_root_id"]) { info!("test tick logging{}", String::from_utf8(value).unwrap()); } else { diff --git a/test/extensions/bootstrap/wasm/wasm_integration_test.cc b/test/extensions/bootstrap/wasm/wasm_integration_test.cc index 6d022664a85b..10c07fca6011 100644 --- a/test/extensions/bootstrap/wasm/wasm_integration_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_integration_test.cc @@ -49,6 +49,10 @@ name: envoy.filters.http.wasm value: "" vm_config: vm_id: "my_vm_id" + environment_variables: + host_env_keys: ["NON_EXIST"] + key_values: + KEY: VALUE runtime: "envoy.wasm.runtime.{}" code: local: diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 8aeca8bc87c1..3142b736bb9e 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -56,6 +56,7 @@ class WasmTestBase { nullptr); auto config = plugin_->wasmConfig(); config.allowedCapabilities() = allowed_capabilities_; + config.environmentVariables() = envs_; wasm_ = std::make_shared(config, vm_key_, scope_, cluster_manager, *dispatcher_); EXPECT_NE(wasm_, nullptr); @@ -79,6 +80,7 @@ class WasmTestBase { std::string vm_configuration_; std::string vm_key_; proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_; + Extensions::Common::Wasm::EnvironmentVariableMap envs_{}; std::string plugin_configuration_; std::shared_ptr plugin_; std::shared_ptr wasm_; @@ -132,17 +134,16 @@ INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, testing::Values("cpp", "rust"))); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTestMatrix); -TEST_P(WasmTestMatrix, Logging) { +TEST_P(WasmTestMatrix, LoggingWithEnvVars) { plugin_configuration_ = "configure-test"; + envs_ = {{"ON_TICK", "TICK_VALUE"}, {"ON_CONFIGURE", "CONFIGURE_VALUE"}}; createWasm(); setWasmCode("logging"); - auto wasm_weak = std::weak_ptr(wasm_); auto wasm_handler = std::make_unique(std::move(wasm_)); EXPECT_TRUE(wasm_weak.lock()->initialize(code_, false)); auto context = static_cast(wasm_weak.lock()->start(plugin_)); - if (std::get<1>(GetParam()) == "cpp") { EXPECT_CALL(*context, log_(spdlog::level::info, Eq("printf stdout test"))); EXPECT_CALL(*context, log_(spdlog::level::err, Eq("printf stderr test"))); @@ -155,6 +156,8 @@ TEST_P(WasmTestMatrix, Logging) { .Times(testing::AtLeast(1)); EXPECT_CALL(*context, log_(spdlog::level::info, Eq("onDone logging"))); EXPECT_CALL(*context, log_(spdlog::level::info, Eq("onDelete logging"))); + EXPECT_CALL(*context, log_(spdlog::level::trace, Eq("ON_CONFIGURE: CONFIGURE_VALUE"))); + EXPECT_CALL(*context, log_(spdlog::level::trace, Eq("ON_TICK: TICK_VALUE"))); EXPECT_TRUE(wasm_weak.lock()->configure(context, plugin_)); wasm_handler.reset(); diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 925ea1db3824..b7129026dd3b 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -58,6 +58,15 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "plugin_test", + srcs = ["plugin_test.cc"], + deps = [ + "//source/extensions/common/wasm:wasm_lib", + "//test/test_common:environment_lib", + ], +) + envoy_cc_test_binary( name = "wasm_speed_test", srcs = ["wasm_speed_test.cc"], diff --git a/test/extensions/common/wasm/plugin_test.cc b/test/extensions/common/wasm/plugin_test.cc new file mode 100644 index 000000000000..47c72822de1a --- /dev/null +++ b/test/extensions/common/wasm/plugin_test.cc @@ -0,0 +1,92 @@ +#include + +#include "envoy/common/exception.h" + +#include "extensions/common/wasm/plugin.h" +#include "extensions/common/wasm/well_known_names.h" + +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { +namespace { + +TEST(TestWasmConfig, Basic) { + envoy::extensions::wasm::v3::PluginConfig plugin_config; + const std::string name = "my-plugin"; + plugin_config.set_name(name); + + const std::string function = "function"; + plugin_config.mutable_capability_restriction_config()->mutable_allowed_capabilities()->insert( + {function, envoy::extensions::wasm::v3::SanitizationConfig()}); + + auto proto_envs = plugin_config.mutable_vm_config()->mutable_environment_variables(); + const std::string host_env_key = "HOST_KEY"; + const std::string host_env_value = "HOST_VALUE"; + const std::string key = "KEY"; + const std::string value = "VALUE"; + TestEnvironment::setEnvVar(host_env_key, host_env_value, 0); + proto_envs->mutable_host_env_keys()->Add(host_env_key.c_str()); + (*proto_envs->mutable_key_values())[key] = value; + + auto wasm_config = WasmConfig(plugin_config); + EXPECT_EQ(name, wasm_config.config().name()); + auto allowed_capabilities = wasm_config.allowedCapabilities(); + EXPECT_NE(allowed_capabilities.find(function), allowed_capabilities.end()); + auto envs = wasm_config.environmentVariables(); + EXPECT_EQ(envs[host_env_key], host_env_value); + EXPECT_EQ(envs[key], value); +} + +TEST(TestWasmConfig, EnvKeyException) { + { + // Duplication in host_env_keys. + envoy::extensions::wasm::v3::PluginConfig plugin_config; + plugin_config.set_name("foo-wasm"); + auto proto_envs = plugin_config.mutable_vm_config()->mutable_environment_variables(); + auto key = "KEY"; + proto_envs->mutable_host_env_keys()->Add(key); + proto_envs->mutable_host_env_keys()->Add(key); + EXPECT_THROW_WITH_MESSAGE( + WasmConfig config(plugin_config), EnvoyException, + "Key KEY is duplicated in envoy.extensions.wasm.v3.VmConfig.environment_variables for " + "foo-wasm. All the keys must be unique."); + } + { + // Duplication between host_env_keys and key_values. + envoy::extensions::wasm::v3::PluginConfig plugin_config; + plugin_config.set_name("bar-wasm"); + auto proto_envs = plugin_config.mutable_vm_config()->mutable_environment_variables(); + auto key = "KEY"; + (*proto_envs->mutable_key_values())[key] = "VALUE"; + proto_envs->mutable_host_env_keys()->Add(key); + EXPECT_THROW_WITH_MESSAGE( + WasmConfig config(plugin_config), EnvoyException, + "Key KEY is duplicated in envoy.extensions.wasm.v3.VmConfig.environment_variables for " + "bar-wasm. All the keys must be unique."); + } +} + +TEST(TestWasmConfig, NullVMEnv) { + envoy::extensions::wasm::v3::PluginConfig plugin_config; + plugin_config.mutable_vm_config()->set_runtime(WasmRuntimeNames::get().Null); + (*plugin_config.mutable_vm_config() + ->mutable_environment_variables() + ->mutable_key_values())["key"] = "value"; + + EXPECT_THROW_WITH_MESSAGE( + WasmConfig config(plugin_config), EnvoyException, + "envoy.extensions.wasm.v3.VmConfig.EnvironmentVariables.key_values must " + "not be set for NullVm."); +} + +} // namespace +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 2ae0b5d4fedf..4d6cf2dec04e 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -660,7 +660,6 @@ TEST_P(WasmCommonTest, VmCache) { })); auto vm_config = plugin_config.mutable_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); @@ -754,7 +753,6 @@ TEST_P(WasmCommonTest, RemoteCode) { absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); auto vm_config = plugin_config.mutable_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); @@ -862,7 +860,6 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm"))); auto vm_config = plugin_config.mutable_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); @@ -1290,13 +1287,14 @@ class WasmCommonContextTest WasmCommonContextTest() = default; void setup(const std::string& code, std::string vm_configuration, std::string root_id = "") { - setupBase( - GetParam(), code, - [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { - return new TestContext(wasm, plugin); - }, - root_id, vm_configuration); + setRootId(root_id); + setVmConfiguration(vm_configuration); + setupBase(GetParam(), code, + [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + return new TestContext(wasm, plugin); + }); } + void setupContext() { context_ = std::make_unique(wasm_->wasm().get(), root_context_->id(), plugin_); context_->onCreate(); diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 91b76310e6b0..3e807289f76c 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -1,3 +1,5 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", @@ -30,6 +32,7 @@ wasm_rust_binary( wasm_rust_binary( name = "headers_rust.wasm", srcs = ["headers_rust.rs"], + wasi = True, deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", "@proxy_wasm_rust_sdk//bazel/cargo:log", diff --git a/test/extensions/filters/http/wasm/test_data/headers_rust.rs b/test/extensions/filters/http/wasm/test_data/headers_rust.rs index 71fea4fb9573..c2aee58f7b0b 100644 --- a/test/extensions/filters/http/wasm/test_data/headers_rust.rs +++ b/test/extensions/filters/http/wasm/test_data/headers_rust.rs @@ -1,9 +1,17 @@ -use log::{debug, error, info, warn}; +use log::{trace, debug, error, info, warn}; use proxy_wasm::traits::{Context, HttpContext}; use proxy_wasm::types::*; +#[no_mangle] +extern "C" { + fn __wasilibc_initialize_environ(); +} + #[no_mangle] pub fn _start() { + unsafe { + __wasilibc_initialize_environ(); + } proxy_wasm::set_log_level(LogLevel::Trace); proxy_wasm::set_http_context(|context_id, _| -> Box { Box::new(TestStream { context_id }) @@ -16,6 +24,18 @@ struct TestStream { impl HttpContext for TestStream { fn on_http_request_headers(&mut self, _: usize) -> Action { + let mut msg = String::new(); + if let Ok(value) = std::env::var("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV") { + msg.push_str("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV: "); + msg.push_str(&value); + } + if let Ok(value) = std::env::var("ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV") { + msg.push_str("\nENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV: "); + msg.push_str(&value); + } + if !msg.is_empty() { + trace!("{}", msg); + } debug!("onRequestHeaders {} headers", self.context_id); if let Some(path) = self.get_http_request_header(":path") { info!("header path {}", path); diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 6d3bb261b72e..705e468dce21 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -94,6 +94,16 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { root()->stream_context_id_ = id(); auto test = root()->test_; if (test == "headers") { + std::string msg = ""; + if (auto value = std::getenv("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV")) { + msg += "ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV: " + std::string(value); + } + if (auto value = std::getenv("ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV")) { + msg += "\nENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV: " + std::string(value); + } + if (!msg.empty()) { + logTrace(msg); + } logDebug(std::string("onRequestHeaders ") + std::to_string(id()) + std::string(" ") + test); auto path = getRequestHeader(":path"); logInfo(std::string("header path ") + std::string(path->view())); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 6a5184856bf1..ea0c924562c9 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -64,10 +64,8 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< }; } - void setup(const std::string& code, std::string root_id = "", std::string vm_configuration = "") { - setupBase(std::get<0>(GetParam()), code, createContextFn(), root_id, vm_configuration); - } - void setupTest(std::string root_id = "", std::string vm_configuration = "") { + void setupTest(std::string root_id = "", std::string vm_configuration = "", + envoy::extensions::wasm::v3::EnvironmentVariables envs = {}) { std::string code; if (std::get<0>(GetParam()) == "null") { code = "HttpWasmTestCpp"; @@ -82,8 +80,13 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< code = TestEnvironment::readFileToStringForTest(basic_path + "_rust.wasm"); } } - setupBase(std::get<0>(GetParam()), code, createContextFn(), root_id, vm_configuration); + + setRootId(root_id); + setEnvs(envs); + setVmConfiguration(vm_configuration); + setupBase(std::get<0>(GetParam()), code, createContextFn()); } + void setupFilter() { setupFilterBase(); } void setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks); @@ -101,15 +104,31 @@ INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, // Bad code in initial config. TEST_P(WasmHttpFilterTest, BadCode) { - setup("bad code"); + setupBase(std::get<0>(GetParam()), "bad code", createContextFn()); EXPECT_EQ(wasm_, nullptr); } // Script touching headers only, request that is headers only. -TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnly) { - setupTest("", "headers"); +TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnlyWithEnvVars) { + envoy::extensions::wasm::v3::EnvironmentVariables envs; + if (std::get<0>(GetParam()) != "null") { + // Setup env vars. + const std::string host_env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV"; + const std::string host_env_value = "foo"; + const std::string env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV"; + const std::string env_value = "bar"; + TestEnvironment::setEnvVar(host_env_key, host_env_value, 0); + envs.mutable_host_env_keys()->Add(host_env_key.c_str()); + (*envs.mutable_key_values())[env_key] = env_value; + } + setupTest("", "headers", envs); setupFilter(); EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + if (std::get<0>(GetParam()) != "null") { + EXPECT_CALL(filter(), + log_(spdlog::level::trace, Eq("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV: foo\n" + "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV: bar"))); + } EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 5df8937da99a..d186b1176e0b 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -53,13 +53,13 @@ class WasmNetworkFilterTest : public Extensions::Common::Wasm::WasmNetworkFilter } else { code_ = code; } - setupBase( - std::get<0>(GetParam()), code_, - [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { - return new TestRoot(wasm, plugin); - }, - "" /* root_id */, "" /* vm_configuration */, fail_open, "" /* plugin configuration*/, - allowed_capabilities); + setVmConfiguration(vm_configuration); + setFailOpen(fail_open); + setAllowedCapabilities(allowed_capabilities); + setupBase(std::get<0>(GetParam()), code_, + [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + return new TestRoot(wasm, plugin); + }); } void setupFilter() { setupFilterBase(); } diff --git a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc index adddcd737dc7..8e7311628c2d 100644 --- a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc +++ b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc @@ -37,12 +37,11 @@ class WasmCommonContextTest WasmCommonContextTest() = default; void setup(const std::string& code, std::string root_id = "") { - setupBase( - GetParam(), code, - [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { - return new TestContext(wasm, plugin); - }, - root_id); + setRootId(root_id); + setupBase(GetParam(), code, + [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + return new TestContext(wasm, plugin); + }); } void setupContext() { context_ = std::make_unique(wasm_->wasm().get(), root_context_->id(), plugin_); diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 236cead41b55..775197c2ddfe 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -58,32 +58,30 @@ template class WasmTestBase : public Base { // NOLINTNEXTLINE(readability-identifier-naming) void SetUp() override { clearCodeCacheForTesting(); } - 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 = "", - proxy_wasm::AllowedCapabilitiesMap allowed_capabilities = {}) { + void setupBase(const std::string& runtime, const std::string& code, CreateContextFn create_root) { Api::ApiPtr api = Api::createApiForTest(stats_store_); scope_ = Stats::ScopeSharedPtr(stats_store_.createScope("wasm.")); envoy::extensions::wasm::v3::PluginConfig plugin_config; - *plugin_config.mutable_root_id() = root_id; + *plugin_config.mutable_root_id() = root_id_; *plugin_config.mutable_name() = "plugin_name"; - plugin_config.set_fail_open(fail_open); - plugin_config.mutable_configuration()->set_value(plugin_configuration); + plugin_config.set_fail_open(fail_open_); + plugin_config.mutable_configuration()->set_value(plugin_configuration_); + *plugin_config.mutable_vm_config()->mutable_environment_variables() = envs_; auto vm_config = plugin_config.mutable_vm_config(); vm_config->set_vm_id("vm_id"); vm_config->set_runtime(absl::StrCat("envoy.wasm.runtime.", runtime)); ProtobufWkt::StringValue vm_configuration_string; - vm_configuration_string.set_value(vm_configuration); + 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); plugin_ = std::make_shared( plugin_config, envoy::config::core::v3::TrafficDirection::INBOUND, local_info_, &listener_metadata_); + plugin_->wasmConfig().allowedCapabilities() = allowed_capabilities_; // Passes ownership of root_context_. - plugin_->wasmConfig().allowedCapabilities() = allowed_capabilities; Extensions::Common::Wasm::createWasm( plugin_, scope_, cluster_manager_, init_manager_, dispatcher_, *api, lifecycle_notifier_, remote_data_provider_, [this](WasmHandleSharedPtr wasm) { wasm_ = wasm; }, create_root); @@ -120,6 +118,25 @@ template class WasmTestBase : public Base { envoy::config::core::v3::Metadata listener_metadata_; Context* root_context_ = nullptr; // Unowned. Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + + void setRootId(std::string root_id) { root_id_ = root_id; } + void setVmConfiguration(std::string vm_configuration) { vm_configuration_ = vm_configuration; } + void setPluginConfiguration(std::string plugin_configuration) { + plugin_configuration_ = plugin_configuration; + } + void setFailOpen(bool fail_open) { fail_open_ = fail_open; } + void setAllowedCapabilities(proxy_wasm::AllowedCapabilitiesMap allowed_capabilities) { + allowed_capabilities_ = allowed_capabilities; + } + void setEnvs(envoy::extensions::wasm::v3::EnvironmentVariables envs) { envs_ = envs; } + +private: + std::string root_id_ = ""; + std::string vm_configuration_ = ""; + bool fail_open_ = false; + std::string plugin_configuration_ = ""; + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_ = {}; + envoy::extensions::wasm::v3::EnvironmentVariables envs_ = {}; }; template class WasmHttpFilterTestBase : public WasmTestBase {