Skip to content

Commit

Permalink
wasm: handle invalid grpc code (envoyproxy#33856)
Browse files Browse the repository at this point in the history
Commit Message: wasm: Manage wasm invalid grpc code

Additional Description: In the scenario when the wasm generate a local reply with unset (invalid) grpc code. instead of propagate the -1, now the context will check and propagate the nullopt to let envoy populate it correctly.

Risk Level: Low
Testing: yes
Docs Changes: no
Release Notes: no
Platform Specific Features: n/a
  • Loading branch information
juanmolle authored May 2, 2024
1 parent 6091f47 commit 1ae957c
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
10 changes: 9 additions & 1 deletion source/extensions/common/wasm/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1691,8 +1691,16 @@ WasmResult Context::sendLocalResponse(uint32_t response_code, std::string_view b
if (local_reply_sent_) {
return;
}
// C++, Rust and other SDKs use -1 (InvalidCode) as the default value if gRPC code is not set,
// which should be mapped to nullopt in Envoy to prevent it from sending a grpc-status trailer
// at all.
absl::optional<Grpc::Status::GrpcStatus> grpc_status_code = absl::nullopt;
if (grpc_status >= Grpc::Status::WellKnownGrpcStatus::Ok &&
grpc_status <= Grpc::Status::WellKnownGrpcStatus::MaximumKnown) {
grpc_status_code = Grpc::Status::WellKnownGrpcStatus(grpc_status);
}
decoder_callbacks_->sendLocalReply(static_cast<Envoy::Http::Code>(response_code), body_text,
modify_headers, grpc_status, details);
modify_headers, grpc_status_code, details);
local_reply_sent_ = true;
});
}
Expand Down
19 changes: 19 additions & 0 deletions test/extensions/common/wasm/test_data/test_context_cpp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,30 @@ FilterDataStatus PanicReplyContext::onRequestBody(size_t, bool) {
return FilterDataStatus::Continue;
}

class InvalidGrpcStatusReplyContext : public Context {
public:
explicit InvalidGrpcStatusReplyContext(uint32_t id, RootContext* root) : Context(id, root) {}
FilterDataStatus onRequestBody(size_t body_buffer_length, bool end_of_stream) override;

private:
EnvoyRootContext* root() { return static_cast<EnvoyRootContext*>(Context::root()); }
};

FilterDataStatus InvalidGrpcStatusReplyContext::onRequestBody(size_t size, bool) {
sendLocalResponse(200, "ok", "body", {}, size == 0 ? GrpcStatus::InvalidCode : GrpcStatus::PermissionDenied);
return FilterDataStatus::Continue;
}

static RegisterContextFactory register_DupReplyContext(CONTEXT_FACTORY(DupReplyContext),
ROOT_FACTORY(EnvoyRootContext),
"send local reply twice");
static RegisterContextFactory register_PanicReplyContext(CONTEXT_FACTORY(PanicReplyContext),
ROOT_FACTORY(EnvoyRootContext),
"panic after sending local reply");

static RegisterContextFactory register_InvalidGrpcStatusReplyContext(CONTEXT_FACTORY(InvalidGrpcStatusReplyContext),
ROOT_FACTORY(EnvoyRootContext),
"send local reply grpc");


END_WASM_PLUGIN
50 changes: 50 additions & 0 deletions test/extensions/common/wasm/wasm_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,56 @@ TEST_P(WasmCommonContextTest, LocalReplyWhenPanic) {
EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, context().onRequestBody(0, false));
}

// test that in case -1 is send from wasm it propagate nullopt
TEST_P(WasmCommonContextTest, ProcessInvalidGRPCStatusCodeAsEmptyInLocalReply) {
std::string code;
if (std::get<0>(GetParam()) != "null") {
code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat(
"{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm")));
} else {
// The name of the Null VM plugin.
code = "CommonWasmTestContextCpp";
}
EXPECT_FALSE(code.empty());

setup(code, "context", "send local reply grpc");
setupContext();
EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _))
.WillOnce([this](Http::ResponseHeaderMap&, bool) { context().onResponseHeaders(0, false); });
EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::OK, testing::Eq("body"), _,
testing::Eq(absl::nullopt), testing::Eq("ok")));

// Create in-VM context.
context().onCreate();
EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, context().onRequestBody(0, false));
}

// test that in case valid grpc status is send from wasm it propagate as it is
TEST_P(WasmCommonContextTest, ProcessValidGRPCStatusCodeAsEmptyInLocalReply) {
std::string code;
if (std::get<0>(GetParam()) != "null") {
code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat(
"{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm")));
} else {
// The name of the Null VM plugin.
code = "CommonWasmTestContextCpp";
}
EXPECT_FALSE(code.empty());

setup(code, "context", "send local reply grpc");
setupContext();
EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _))
.WillOnce([this](Http::ResponseHeaderMap&, bool) { context().onResponseHeaders(0, false); });
EXPECT_CALL(decoder_callbacks_,
sendLocalReply(Envoy::Http::Code::OK, testing::Eq("body"), _,
testing::Eq(Grpc::Status::WellKnownGrpcStatus::PermissionDenied),
testing::Eq("ok")));

// Create in-VM context.
context().onCreate();
EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, context().onRequestBody(1, false));
}

} // namespace Wasm
} // namespace Common
} // namespace Extensions
Expand Down

0 comments on commit 1ae957c

Please sign in to comment.