diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-08-22 13:55:17 +0000 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-08-22 14:11:59 +0000 |
commit | 55e0f93a74214caf22c4fd79d60e1b0b6836a99c (patch) | |
tree | 0a59f93c8c46f98007057ab9e49a789867e2018d /fnet/src | |
parent | b4117991e6d98dbc9a93625f6dc7170cdd1484dc (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/src')
-rw-r--r-- | fnet/src/tests/frt/rpc/CMakeLists.txt | 6 | ||||
-rw-r--r-- | fnet/src/tests/frt/rpc/invoke.cpp | 34 | ||||
-rw-r--r-- | fnet/src/tests/frt/rpc/my_crypto_engine.hpp | 9 | ||||
-rw-r--r-- | fnet/src/vespa/fnet/frt/require_capabilities.cpp | 14 |
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; } |