summaryrefslogtreecommitdiffstats
path: root/fnet
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-08-22 13:55:17 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-08-22 14:11:59 +0000
commit55e0f93a74214caf22c4fd79d60e1b0b6836a99c (patch)
tree0a59f93c8c46f98007057ab9e49a789867e2018d /fnet
parentb4117991e6d98dbc9a93625f6dc7170cdd1484dc (diff)
Support capability enforcement environment variable in C++
Mirrors Java enforce/log-only/disable semantics, defaulting to enforce. Also fixes an issue where connection auth context and capabilities would not be set if a server socket was running in mixed-mode. This is not a problem in practice since mixed-mode is inherently completely insecure since it must accept plain-text clients, which implicitly have all capabilities granted.
Diffstat (limited to 'fnet')
-rw-r--r--fnet/src/tests/frt/rpc/CMakeLists.txt6
-rw-r--r--fnet/src/tests/frt/rpc/invoke.cpp34
-rw-r--r--fnet/src/tests/frt/rpc/my_crypto_engine.hpp9
-rw-r--r--fnet/src/vespa/fnet/frt/require_capabilities.cpp14
4 files changed, 57 insertions, 6 deletions
diff --git a/fnet/src/tests/frt/rpc/CMakeLists.txt b/fnet/src/tests/frt/rpc/CMakeLists.txt
index 64a1d150fce..35150cad7b6 100644
--- a/fnet/src/tests/frt/rpc/CMakeLists.txt
+++ b/fnet/src/tests/frt/rpc/CMakeLists.txt
@@ -10,6 +10,12 @@ vespa_add_test(NAME fnet_invoke_test_app_xor COMMAND fnet_invoke_test_app ENVIRO
vespa_add_test(NAME fnet_invoke_test_app_tls COMMAND fnet_invoke_test_app ENVIRONMENT "CRYPTOENGINE=tls")
vespa_add_test(NAME fnet_invoke_test_app_tls_maybe_yes COMMAND fnet_invoke_test_app ENVIRONMENT "CRYPTOENGINE=tls_maybe_yes")
vespa_add_test(NAME fnet_invoke_test_app_tls_maybe_no COMMAND fnet_invoke_test_app ENVIRONMENT "CRYPTOENGINE=tls_maybe_no")
+vespa_add_test(NAME fnet_invoke_test_app_tls_cap_enforced COMMAND fnet_invoke_test_app
+ ENVIRONMENT "CRYPTOENGINE=tls" "VESPA_TLS_CAPABILITIES_ENFORCEMENT_MODE=enforce")
+vespa_add_test(NAME fnet_invoke_test_app_tls_cap_log_only COMMAND fnet_invoke_test_app
+ ENVIRONMENT "CRYPTOENGINE=tls" "VESPA_TLS_CAPABILITIES_ENFORCEMENT_MODE=log_only")
+vespa_add_test(NAME fnet_invoke_test_app_tls_cap_disable COMMAND fnet_invoke_test_app
+ ENVIRONMENT "CRYPTOENGINE=tls" "VESPA_TLS_CAPABILITIES_ENFORCEMENT_MODE=disable")
vespa_add_executable(fnet_detach_return_invoke_test_app TEST
SOURCES
detach_return_invoke.cpp
diff --git a/fnet/src/tests/frt/rpc/invoke.cpp b/fnet/src/tests/frt/rpc/invoke.cpp
index e1912985379..2668d86cae6 100644
--- a/fnet/src/tests/frt/rpc/invoke.cpp
+++ b/fnet/src/tests/frt/rpc/invoke.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/net/socket_spec.h>
+#include <vespa/vespalib/net/tls/capability_env_config.h>
#include <vespa/vespalib/util/benchmark_timer.h>
#include <vespa/vespalib/util/latch.h>
#include <vespa/fnet/frt/supervisor.h>
@@ -8,12 +9,14 @@
#include <vespa/fnet/frt/rpcrequest.h>
#include <vespa/fnet/frt/invoker.h>
#include <vespa/fnet/frt/request_access_filter.h>
+#include <vespa/fnet/frt/require_capabilities.h>
#include <mutex>
#include <condition_variable>
#include <string_view>
using vespalib::SocketSpec;
using vespalib::BenchmarkTimer;
+using namespace vespalib::net::tls;
constexpr double timeout = 60.0;
constexpr double short_timeout = 0.1;
@@ -221,6 +224,13 @@ public:
rb.DefineMethod("accessRestricted", "s", "",
FRT_METHOD(TestRPC::RPC_AccessRestricted), this);
rb.RequestAccessFilter(std::make_unique<MyAccessFilter>());
+ // The authz rules used for this test only grant the telemetry capability set
+ rb.DefineMethod("capabilityRestricted", "", "",
+ FRT_METHOD(TestRPC::RPC_AccessRestricted), this);
+ rb.RequestAccessFilter(std::make_unique<FRT_RequireCapabilities>(CapabilitySet::content_node()));
+ rb.DefineMethod("capabilityAllowed", "", "",
+ FRT_METHOD(TestRPC::RPC_AccessRestricted), this);
+ rb.RequestAccessFilter(std::make_unique<FRT_RequireCapabilities>(CapabilitySet::telemetry()));
}
void RPC_Test(FRT_RPCRequest *req)
@@ -470,6 +480,30 @@ TEST_F("request allowed by access filter invokes server method as usual", Fixtur
EXPECT_TRUE(f1.server_instance().restricted_method_was_invoked());
}
+TEST_F("capability checking filter is enforced under mTLS unless overridden by env var", Fixture()) {
+ MyReq req("capabilityRestricted"); // Requires content node cap set; disallowed
+ f1.target().InvokeSync(req.borrow(), timeout);
+ auto cap_mode = capability_enforcement_mode_from_env();
+ fprintf(stderr, "capability enforcement mode: %s\n", to_string(cap_mode));
+ if (crypto->use_tls_when_client() && (cap_mode == CapabilityEnforcementMode::Enforce)) {
+ // Default authz rule does not give required capabilities; must fail.
+ EXPECT_EQUAL(req.get().GetErrorCode(), FRTE_RPC_PERMISSION_DENIED);
+ EXPECT_FALSE(f1.server_instance().restricted_method_was_invoked());
+ } else {
+ // Either no mTLS configured (implicit full capability set) or capabilities not enforced.
+ ASSERT_FALSE(req.get().IsError());
+ EXPECT_TRUE(f1.server_instance().restricted_method_was_invoked());
+ }
+}
+
+TEST_F("access is allowed by capability filter when peer is granted the required capability", Fixture()) {
+ MyReq req("capabilityAllowed"); // Requires telemetry cap set; allowed
+ f1.target().InvokeSync(req.borrow(), timeout);
+ // Should always be allowed, regardless of mTLS mode or capability enforcement
+ 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/frt/rpc/my_crypto_engine.hpp b/fnet/src/tests/frt/rpc/my_crypto_engine.hpp
index 83934c430b3..219b4dafd05 100644
--- a/fnet/src/tests/frt/rpc/my_crypto_engine.hpp
+++ b/fnet/src/tests/frt/rpc/my_crypto_engine.hpp
@@ -17,14 +17,17 @@ vespalib::CryptoEngine::SP my_crypto_engine() {
return std::make_shared<vespalib::XorCryptoEngine>();
} else if (engine == "tls") {
fprintf(stderr, "crypto engine: tls\n");
- return std::make_shared<vespalib::TlsCryptoEngine>(vespalib::test::make_tls_options_for_testing());
+ return std::make_shared<vespalib::TlsCryptoEngine>(
+ vespalib::test::make_telemetry_only_capability_tls_options_for_testing());
} else if (engine == "tls_maybe_yes") {
fprintf(stderr, "crypto engine: tls client, mixed server\n");
- auto tls = std::make_shared<vespalib::TlsCryptoEngine>(vespalib::test::make_tls_options_for_testing());
+ auto tls = std::make_shared<vespalib::TlsCryptoEngine>(
+ vespalib::test::make_telemetry_only_capability_tls_options_for_testing());
return std::make_shared<vespalib::MaybeTlsCryptoEngine>(std::move(tls), true);
} else if (engine == "tls_maybe_no") {
fprintf(stderr, "crypto engine: null client, mixed server\n");
- auto tls = std::make_shared<vespalib::TlsCryptoEngine>(vespalib::test::make_tls_options_for_testing());
+ auto tls = std::make_shared<vespalib::TlsCryptoEngine>(
+ vespalib::test::make_telemetry_only_capability_tls_options_for_testing());
return std::make_shared<vespalib::MaybeTlsCryptoEngine>(std::move(tls), false);
}
TEST_FATAL(("invalid crypto engine: " + engine).c_str());
diff --git a/fnet/src/vespa/fnet/frt/require_capabilities.cpp b/fnet/src/vespa/fnet/frt/require_capabilities.cpp
index fc64621717f..df6857625cd 100644
--- a/fnet/src/vespa/fnet/frt/require_capabilities.cpp
+++ b/fnet/src/vespa/fnet/frt/require_capabilities.cpp
@@ -4,6 +4,7 @@
#include "rpcrequest.h"
#include <vespa/fnet/connection.h>
#include <vespa/vespalib/net/connection_auth_context.h>
+#include <vespa/vespalib/net/tls/capability_env_config.h>
#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".fnet.frt.require_capabilities");
@@ -15,15 +16,22 @@ FRT_RequireCapabilities::allow(FRT_RPCRequest& req) const noexcept
{
const auto& auth_ctx = req.GetConnection()->auth_context();
const bool is_authorized = auth_ctx.capabilities().contains_all(_required_capabilities);
- if (!is_authorized) {
+ if (is_authorized) {
+ return true;
+ } else {
+ const auto mode = capability_enforcement_mode_from_env();
+ if (mode == CapabilityEnforcementMode::Disable) {
+ return true;
+ }
auto peer_spec = req.GetConnection()->GetPeerSpec();
std::string method_name(req.GetMethodName(), req.GetMethodNameLen());
- LOGBT(warning, peer_spec, "Permission denied for RPC method '%s'. "
+ LOGBT(warning, peer_spec, "%sPermission denied for RPC method '%s'. "
"Peer at %s with %s. Call requires %s, but peer has %s",
+ ((mode == CapabilityEnforcementMode::LogOnly) ? "(Dry-run only, not enforced): " : ""),
method_name.c_str(), peer_spec.c_str(),
to_string(auth_ctx.peer_credentials()).c_str(),
_required_capabilities.to_string().c_str(),
auth_ctx.capabilities().to_string().c_str());
+ return (mode == CapabilityEnforcementMode::Enforce) ? false : true;
}
- return is_authorized;
}