aboutsummaryrefslogtreecommitdiffstats
path: root/fnet/src/tests
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-06-22 15:44:57 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-06-29 11:20:24 +0000
commitcc44b799f0d78a5e26f12ecb8b868301095570c4 (patch)
tree374f50996663fbdfa85d529202c0e7cccb99648d /fnet/src/tests
parentcbe98d69506bf60f7fcf7681eb99a79589300882 (diff)
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.
Diffstat (limited to 'fnet/src/tests')
-rw-r--r--fnet/src/tests/frt/rpc/invoke.cpp55
-rw-r--r--fnet/src/tests/info/info.cpp2
2 files changed, 53 insertions, 4 deletions
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 <vespa/fnet/frt/target.h>
#include <vespa/fnet/frt/rpcrequest.h>
#include <vespa/fnet/frt/invoker.h>
+#include <vespa/fnet/frt/request_access_filter.h>
#include <mutex>
#include <condition_variable>
+#include <string_view>
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<bool> _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<MyAccessFilter>());
}
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));