From cc44b799f0d78a5e26f12ecb8b868301095570c4 Mon Sep 17 00:00:00 2001 From: Tor Brede Vekterli Date: Wed, 22 Jun 2022 15:44:57 +0000 Subject: Support mTLS connection-level capabilities and RPC access filtering in C++ Adds the following: * Named capabilities and capability sets that represent (respectively) a single Vespa access API (such as Document API, search API etc) or a concrete subset of individual capabilities that make up a particular Vespa service (such as a content node). * A new `capabilities` array field to the mTLS authorization policies that allows for constraining what requests sent over a particular connection are allowed to actually do. Capabilities are referenced by name and may include any combination of capability sets and individual capabilities. If multiple capabilities/sets are configured, the resulting set of capabilities is the union set of all of them. * An FRT RPC-level access filter that can be set up as part of RPC method definitions. If set, filters are invoked prior to RPC methods. * A new `PERMISSION_DENIED` error code to FRT RPC that is invoked if an access filter denies a request. This also GCs the unused `AssumedRoles` concept which is now deprecated in favor of capabilities. Note: this is **not yet** a public or stable API, and capability names/semantics may change at any time. --- fnet/src/tests/frt/rpc/invoke.cpp | 55 ++++++++++++++++++++++++++++++++++++--- fnet/src/tests/info/info.cpp | 2 +- 2 files changed, 53 insertions(+), 4 deletions(-) (limited to 'fnet/src/tests') diff --git a/fnet/src/tests/frt/rpc/invoke.cpp b/fnet/src/tests/frt/rpc/invoke.cpp index 1fbd356b239..e1912985379 100644 --- a/fnet/src/tests/frt/rpc/invoke.cpp +++ b/fnet/src/tests/frt/rpc/invoke.cpp @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include using vespalib::SocketSpec; using vespalib::BenchmarkTimer; @@ -175,11 +177,25 @@ public: //------------------------------------------------------------- +struct MyAccessFilter : FRT_RequestAccessFilter { + ~MyAccessFilter() override = default; + + constexpr static std::string_view WRONG_KEY = "...mellon!"; + constexpr static std::string_view CORRECT_KEY = "let me in, I have cake"; + + bool allow(FRT_RPCRequest& req) const noexcept override { + const auto& req_param = req.GetParams()->GetValue(0)._string; + const auto magic_key = std::string_view(req_param._str, req_param._len); + return (magic_key == CORRECT_KEY); + } +}; + class TestRPC : public FRT_Invokable { private: - uint32_t _intValue; - RequestLatch _detached_req; + uint32_t _intValue; + RequestLatch _detached_req; + std::atomic _restricted_method_was_invoked; TestRPC(const TestRPC &); TestRPC &operator=(const TestRPC &); @@ -187,7 +203,8 @@ private: public: TestRPC(FRT_Supervisor *supervisor) : _intValue(0), - _detached_req() + _detached_req(), + _restricted_method_was_invoked(false) { FRT_ReflectionBuilder rb(supervisor); @@ -201,6 +218,9 @@ public: FRT_METHOD(TestRPC::RPC_GetValue), this); rb.DefineMethod("test", "iibb", "i", FRT_METHOD(TestRPC::RPC_Test), this); + rb.DefineMethod("accessRestricted", "s", "", + FRT_METHOD(TestRPC::RPC_AccessRestricted), this); + rb.RequestAccessFilter(std::make_unique()); } void RPC_Test(FRT_RPCRequest *req) @@ -244,6 +264,16 @@ public: req->GetReturn()->AddInt32(_intValue); } + void RPC_AccessRestricted([[maybe_unused]] FRT_RPCRequest *req) + { + // We'll only get here if the access filter lets us in + _restricted_method_was_invoked.store(true); + } + + bool restricted_method_was_invoked() const noexcept { + return _restricted_method_was_invoked.load(); + } + RequestLatch &detached_req() { return _detached_req; } }; @@ -264,6 +294,7 @@ public: FRT_Target *make_bad_target() { return _client.supervisor().GetTarget("bogus address"); } RequestLatch &detached_req() { return _testRPC.detached_req(); } EchoTest &echo() { return _echoTest; } + const TestRPC& server_instance() const noexcept { return _testRPC; } Fixture() : _client(crypto), @@ -421,6 +452,24 @@ TEST_F("require that parameters can be echoed as return values", Fixture()) { EXPECT_TRUE(req.get().GetParams()->Equals(req.get().GetReturn())); } +TEST_F("request denied by access filter returns PERMISSION_DENIED and does not invoke server method", Fixture()) { + MyReq req("accessRestricted"); + auto key = MyAccessFilter::WRONG_KEY; + req.get().GetParams()->AddString(key.data(), key.size()); + f1.target().InvokeSync(req.borrow(), timeout); + EXPECT_EQUAL(req.get().GetErrorCode(), FRTE_RPC_PERMISSION_DENIED); + EXPECT_FALSE(f1.server_instance().restricted_method_was_invoked()); +} + +TEST_F("request allowed by access filter invokes server method as usual", Fixture()) { + MyReq req("accessRestricted"); + auto key = MyAccessFilter::CORRECT_KEY; + req.get().GetParams()->AddString(key.data(), key.size()); + f1.target().InvokeSync(req.borrow(), timeout); + ASSERT_FALSE(req.get().IsError()); + EXPECT_TRUE(f1.server_instance().restricted_method_was_invoked()); +} + TEST_MAIN() { crypto = my_crypto_engine(); TEST_RUN_ALL(); diff --git a/fnet/src/tests/info/info.cpp b/fnet/src/tests/info/info.cpp index 0d4e0f90a09..4271546e647 100644 --- a/fnet/src/tests/info/info.cpp +++ b/fnet/src/tests/info/info.cpp @@ -80,7 +80,7 @@ TEST("size of important objects") EXPECT_EQUAL(MUTEX_SIZE + sizeof(std::string) + 112u, sizeof(FNET_IOComponent)); EXPECT_EQUAL(32u, sizeof(FNET_Channel)); EXPECT_EQUAL(40u, sizeof(FNET_PacketQueue_NoLock)); - EXPECT_EQUAL(MUTEX_SIZE + sizeof(std::string) + 408u, sizeof(FNET_Connection)); + EXPECT_EQUAL(MUTEX_SIZE + sizeof(std::string) + 416u, sizeof(FNET_Connection)); EXPECT_EQUAL(48u, sizeof(std::condition_variable)); EXPECT_EQUAL(56u, sizeof(FNET_DataBuffer)); EXPECT_EQUAL(8u, sizeof(FNET_Context)); -- cgit v1.2.3