summaryrefslogtreecommitdiffstats
path: root/vespalib/src
diff options
context:
space:
mode:
Diffstat (limited to 'vespalib/src')
-rw-r--r--vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp4
-rw-r--r--vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp4
-rw-r--r--vespalib/src/tests/net/tls/capabilities/CMakeLists.txt10
-rw-r--r--vespalib/src/tests/net/tls/capabilities/capabilities_test.cpp196
-rw-r--r--vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp27
-rw-r--r--vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp111
-rw-r--r--vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp62
-rw-r--r--vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp4
-rw-r--r--vespalib/src/vespa/vespalib/net/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/net/connection_auth_context.cpp21
-rw-r--r--vespalib/src/vespa/vespalib/net/connection_auth_context.h29
-rw-r--r--vespalib/src/vespa/vespalib/net/crypto_socket.cpp9
-rw-r--r--vespalib/src/vespa/vespalib/net/crypto_socket.h14
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt3
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/assumed_roles.cpp95
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/assumed_roles.h80
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/capability.cpp62
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/capability.h104
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/capability_set.cpp95
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/capability_set.h117
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/certificate_verification_callback.h2
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/crypto_codec.h6
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp7
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h12
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp11
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/peer_policies.h14
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp10
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/tls_crypto_socket.h2
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp34
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/verification_result.cpp14
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/verification_result.h25
-rw-r--r--vespalib/src/vespa/vespalib/process/process.cpp1
-rw-r--r--vespalib/src/vespa/vespalib/stllike/hash_fun.h4
-rw-r--r--vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp26
-rw-r--r--vespalib/src/vespa/vespalib/test/peer_policy_utils.h9
-rw-r--r--vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.h2
-rw-r--r--vespalib/src/vespa/vespalib/util/signalhandler.cpp3
40 files changed, 913 insertions, 322 deletions
diff --git a/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp b/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp
index 2117db276d8..d015cdb0f89 100644
--- a/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp
+++ b/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp
@@ -220,7 +220,7 @@ public:
++raw_reports;
}
std::vector<vespalib::string> make_keys() const override {
- return {fmt("raw:%zu", get_hash(_lines))};
+ return {fmt("raw:%" PRIu64, get_hash(_lines))};
}
void merge(const Report &) override { ++_count; }
size_t count() const override { return _count; }
@@ -277,7 +277,7 @@ public:
std::vector<vespalib::string> make_keys() const override {
std::vector<vespalib::string> result;
for (const auto &node: _nodes) {
- result.push_back(fmt("race:%zu", node.trace.hash()));
+ result.push_back(fmt("race:%" PRIu64, node.trace.hash()));
}
return result;
}
diff --git a/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp b/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
index 6988e41add1..de7d899e68a 100644
--- a/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
+++ b/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
@@ -27,7 +27,7 @@ struct Setup {
_allocGrowFactor(0.5),
_resizing(false)
{}
- Setup(const Setup& rhs);
+ Setup(const Setup& rhs) noexcept;
Setup &minArrays(uint32_t value) { _minArrays = value; return *this; }
Setup &used(size_t value) { _usedElems = value; return *this; }
Setup &needed(size_t value) { _neededElems = value; return *this; }
@@ -36,7 +36,7 @@ struct Setup {
Setup &resizing(bool value) { _resizing = value; return *this; }
};
-Setup::Setup(const Setup& rhs)
+Setup::Setup(const Setup& rhs) noexcept
: _minArrays(rhs._minArrays),
_usedElems(rhs._usedElems.load(std::memory_order_relaxed)),
_neededElems(rhs._neededElems),
diff --git a/vespalib/src/tests/net/tls/capabilities/CMakeLists.txt b/vespalib/src/tests/net/tls/capabilities/CMakeLists.txt
new file mode 100644
index 00000000000..4e366674d36
--- /dev/null
+++ b/vespalib/src/tests/net/tls/capabilities/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_net_tls_capabilities_test_app TEST
+ SOURCES
+ capabilities_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_net_tls_capabilities_test_app
+ COMMAND vespalib_net_tls_capabilities_test_app)
+
diff --git a/vespalib/src/tests/net/tls/capabilities/capabilities_test.cpp b/vespalib/src/tests/net/tls/capabilities/capabilities_test.cpp
new file mode 100644
index 00000000000..4a20200c631
--- /dev/null
+++ b/vespalib/src/tests/net/tls/capabilities/capabilities_test.cpp
@@ -0,0 +1,196 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/net/tls/capability_set.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+
+using namespace vespalib;
+using namespace vespalib::net::tls;
+using namespace std::string_view_literals;
+
+TEST("Capability instances are equality comparable") {
+ auto cap1 = Capability::content_document_api();
+ auto cap2 = Capability::content_document_api();
+ auto cap3 = Capability::content_storage_api();
+ EXPECT_EQUAL(cap1, cap2);
+ EXPECT_EQUAL(cap2, cap1);
+ EXPECT_NOT_EQUAL(cap1, cap3);
+}
+
+TEST("CapabilitySet instances are equality comparable") {
+ const auto cap1 = Capability::content_document_api();
+ const auto cap2 = Capability::content_search_api();
+
+ const auto all_caps = CapabilitySet::make_with_all_capabilities();
+ const auto set_12_a = CapabilitySet::of({cap1, cap2});
+ const auto set_12_b = CapabilitySet::of({cap1, cap2});
+ const auto set_1 = CapabilitySet::of({cap1});
+ const auto empty = CapabilitySet::make_empty();
+
+ EXPECT_EQUAL(all_caps, all_caps);
+ EXPECT_EQUAL(empty, empty);
+ EXPECT_EQUAL(set_12_a, set_12_b);
+ EXPECT_EQUAL(set_12_b, set_12_a);
+
+ EXPECT_NOT_EQUAL(all_caps, empty);
+ EXPECT_NOT_EQUAL(set_12_a, set_1);
+ EXPECT_NOT_EQUAL(set_12_a, all_caps);
+ EXPECT_NOT_EQUAL(set_1, empty);
+}
+
+TEST("Can get underlying name of all Capability instances") {
+ EXPECT_EQUAL(Capability::content_storage_api().name(), "vespa.content.storage_api"sv);
+ EXPECT_EQUAL(Capability::content_document_api().name(), "vespa.content.document_api"sv);
+ EXPECT_EQUAL(Capability::content_search_api().name(), "vespa.content.search_api"sv);
+ EXPECT_EQUAL(Capability::slobrok_api().name(), "vespa.slobrok.api"sv);
+ EXPECT_EQUAL(Capability::content_status_pages().name(), "vespa.content.status_pages"sv);
+ EXPECT_EQUAL(Capability::content_metrics_api().name(), "vespa.content.metrics_api"sv);
+ EXPECT_EQUAL(Capability::content_cluster_controller_internal_state_api().name(),
+ "vespa.content.cluster_controller.internal_state_api"sv);
+}
+
+TEST("Capability instances can be stringified") {
+ EXPECT_EQUAL(Capability::content_storage_api().to_string(), "Capability(vespa.content.storage_api)");
+}
+
+namespace {
+
+void check_capability_mapping(const std::string& name, Capability expected) {
+ auto cap = Capability::find_capability(name);
+ ASSERT_TRUE(cap.has_value());
+ EXPECT_EQUAL(*cap, expected);
+}
+
+void check_capability_set_mapping(const std::string& name, CapabilitySet expected) {
+ auto caps = CapabilitySet::find_capability_set(name);
+ ASSERT_TRUE(caps.has_value());
+ EXPECT_EQUAL(*caps, expected);
+}
+
+}
+
+TEST("All known capabilities can be looked up by name") {
+ check_capability_mapping("vespa.content.storage_api", Capability::content_storage_api());
+ check_capability_mapping("vespa.content.document_api", Capability::content_document_api());
+ check_capability_mapping("vespa.content.search_api", Capability::content_search_api());
+ check_capability_mapping("vespa.slobrok.api", Capability::slobrok_api());
+ check_capability_mapping("vespa.content.status_pages", Capability::content_status_pages());
+ check_capability_mapping("vespa.content.metrics_api", Capability::content_metrics_api());
+ check_capability_mapping("vespa.content.cluster_controller.internal_state_api",
+ Capability::content_cluster_controller_internal_state_api());
+}
+
+TEST("Unknown capability name returns nullopt") {
+ EXPECT_FALSE(Capability::find_capability("vespa.content.stale_cat_memes").has_value());
+}
+
+TEST("CapabilitySet instances can be stringified") {
+ EXPECT_EQUAL(CapabilitySet::content_node().to_string(),
+ "CapabilitySet({vespa.content.storage_api, vespa.content.document_api, vespa.slobrok.api})");
+}
+
+TEST("All known capability sets can be looked up by name") {
+ check_capability_set_mapping("vespa.content_node", CapabilitySet::content_node());
+ check_capability_set_mapping("vespa.container_node", CapabilitySet::container_node());
+ check_capability_set_mapping("vespa.telemetry", CapabilitySet::telemetry());
+ check_capability_set_mapping("vespa.cluster_controller_node", CapabilitySet::cluster_controller_node());
+ check_capability_set_mapping("vespa.config_server", CapabilitySet::config_server());
+}
+
+TEST("Unknown capability set name returns nullopt") {
+ EXPECT_FALSE(CapabilitySet::find_capability_set("vespa.unicorn_launcher").has_value());
+}
+
+TEST("Resolving a capability set adds all its underlying capabilities") {
+ CapabilitySet caps;
+ EXPECT_TRUE(caps.resolve_and_add("vespa.content_node"));
+ // Slightly suboptimal; this test will fail if the default set of capabilities for vespa.content_node changes.
+ EXPECT_EQUAL(caps.count(), 3u);
+ EXPECT_FALSE(caps.empty());
+ EXPECT_TRUE(caps.contains(Capability::content_storage_api()));
+ EXPECT_TRUE(caps.contains(Capability::content_document_api()));
+ EXPECT_TRUE(caps.contains(Capability::slobrok_api()));
+ EXPECT_FALSE(caps.contains(Capability::content_search_api()));
+}
+
+TEST("Resolving a single capability adds it to the underlying capabilities") {
+ CapabilitySet caps;
+ EXPECT_TRUE(caps.resolve_and_add("vespa.slobrok.api"));
+ EXPECT_EQUAL(caps.count(), 1u);
+ EXPECT_FALSE(caps.empty());
+ EXPECT_TRUE(caps.contains(Capability::slobrok_api()));
+ EXPECT_FALSE(caps.contains(Capability::content_storage_api()));
+}
+
+TEST("Resolving an unknown capability set returns false and does not add anything") {
+ CapabilitySet caps;
+ EXPECT_FALSE(caps.resolve_and_add("vespa.distributors_evil_twin_with_an_evil_goatee"));
+ EXPECT_EQUAL(caps.count(), 0u);
+ EXPECT_TRUE(caps.empty());
+}
+
+TEST("Resolving multiple capabilities/sets adds union of capabilities") {
+ CapabilitySet caps;
+ EXPECT_TRUE(caps.resolve_and_add("vespa.content_node")); // CapabilitySet
+ EXPECT_TRUE(caps.resolve_and_add("vespa.container_node")); // ditto
+ EXPECT_EQUAL(caps, CapabilitySet::of({Capability::content_storage_api(), Capability::content_document_api(),
+ Capability::slobrok_api(), Capability::content_search_api()}));
+ EXPECT_TRUE(caps.resolve_and_add("vespa.content.metrics_api")); // Capability (single)
+ EXPECT_EQUAL(caps, CapabilitySet::of({Capability::content_storage_api(), Capability::content_document_api(),
+ Capability::slobrok_api(), Capability::content_search_api(),
+ Capability::content_metrics_api()}));
+}
+
+TEST("Default-constructed CapabilitySet has no capabilities") {
+ CapabilitySet caps;
+ EXPECT_EQUAL(caps.count(), 0u);
+ EXPECT_TRUE(caps.empty());
+ EXPECT_FALSE(caps.contains(Capability::content_storage_api()));
+}
+
+TEST("CapabilitySet can be created with all capabilities") {
+ auto caps = CapabilitySet::make_with_all_capabilities();
+ EXPECT_EQUAL(caps.count(), CapabilitySet::max_count());
+ EXPECT_TRUE(caps.contains(Capability::content_storage_api()));
+ EXPECT_TRUE(caps.contains(Capability::content_metrics_api()));
+ // ... we just assume the rest are present as well.
+}
+
+TEST("CapabilitySet::contains_all() requires an intersection of capabilities") {
+ auto cap1 = Capability::content_document_api();
+ auto cap2 = Capability::content_search_api();
+ auto cap3 = Capability::content_storage_api();
+
+ const auto all_caps = CapabilitySet::make_with_all_capabilities();
+ auto set_123 = CapabilitySet::of({cap1, cap2, cap3});
+ auto set_13 = CapabilitySet::of({cap1, cap3});
+ auto set_2 = CapabilitySet::of({cap2});
+ auto set_23 = CapabilitySet::of({cap2, cap3});
+ auto empty = CapabilitySet::make_empty();
+
+ // Sets contain themselves
+ EXPECT_TRUE(all_caps.contains_all(all_caps));
+ EXPECT_TRUE(set_13.contains_all(set_13));
+ EXPECT_TRUE(set_2.contains_all(set_2));
+ EXPECT_TRUE(empty.contains_all(empty));
+
+ // Supersets contain subsets
+ EXPECT_TRUE(all_caps.contains_all(set_123));
+ EXPECT_TRUE(all_caps.contains_all(set_13));
+ EXPECT_TRUE(set_123.contains_all(set_13));
+ EXPECT_TRUE(set_2.contains_all(empty));
+
+ // Subsets do not contain supersets
+ EXPECT_FALSE(set_123.contains_all(all_caps));
+ EXPECT_FALSE(set_13.contains_all(set_123));
+ EXPECT_FALSE(empty.contains_all(set_2));
+
+ // Partially overlapping sets are not contained in each other
+ EXPECT_FALSE(set_13.contains_all(set_23));
+ EXPECT_FALSE(set_23.contains_all(set_13));
+
+ // Fully disjoint sets are not contained in each other
+ EXPECT_FALSE(set_2.contains_all(set_13));
+ EXPECT_FALSE(set_13.contains_all(set_2));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
index 1de10939bea..3d19c335c19 100644
--- a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
+++ b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
@@ -542,7 +542,7 @@ struct PrintingCertificateCallback : CertificateVerificationCallback {
for (auto& dns : peer_creds.dns_sans) {
fprintf(stderr, "Got a DNS SAN entry: %s\n", dns.c_str());
}
- return VerificationResult::make_authorized_for_all_roles();
+ return VerificationResult::make_authorized_with_all_capabilities();
}
};
@@ -551,7 +551,7 @@ struct MockCertificateCallback : CertificateVerificationCallback {
mutable PeerCredentials creds; // only used in single thread testing context
VerificationResult verify(const PeerCredentials& peer_creds) const override {
creds = peer_creds;
- return VerificationResult::make_authorized_for_all_roles();
+ return VerificationResult::make_authorized_with_all_capabilities();
}
};
@@ -712,6 +712,29 @@ TEST_F("Server allows client with certificate that DOES match peer policy", Cert
EXPECT_TRUE(f.handshake());
}
+TEST_F("Authz policy-derived peer capabilities are propagated to CryptoCodec", CertFixture) {
+ auto server_ck = f.create_ca_issued_peer_cert({}, {{"DNS:hello.world.example.com"}});
+ auto authorized = authorized_peers({policy_with({required_san_dns("stale.memes.example.com")},
+ CapabilitySet::of({Capability::content_search_api(),
+ Capability::content_status_pages()})),
+ policy_with({required_san_dns("fresh.memes.example.com")},
+ CapabilitySet::make_with_all_capabilities())});
+ f.reset_server_with_cert_opts(server_ck, std::move(authorized));
+ auto client_ck = f.create_ca_issued_peer_cert({}, {{"DNS:stale.memes.example.com"}});
+ f.reset_client_with_cert_opts(client_ck, AuthorizedPeers::allow_all_authenticated());
+
+ ASSERT_TRUE(f.handshake());
+
+ // Note: "inversion" of client <-> server is because the capabilities are that of the _peer_.
+ auto client_caps = f.server->granted_capabilities();
+ auto server_caps = f.client->granted_capabilities();
+ // Server (from client's PoV) implicitly has all capabilities since client doesn't specify any policies
+ EXPECT_EQUAL(server_caps, CapabilitySet::make_with_all_capabilities());
+ // Client (from server's PoV) only has capabilities for the rule matching its DNS SAN entry
+ EXPECT_EQUAL(client_caps, CapabilitySet::of({Capability::content_search_api(),
+ Capability::content_status_pages()}));
+}
+
void reset_peers_with_server_authz_mode(CertFixture& f, AuthorizationMode authz_mode) {
auto ck = f.create_ca_issued_peer_cert({"hello.world.example.com"}, {});
diff --git a/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp b/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
index fa2bc1a2eaf..c456d7e2a5c 100644
--- a/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
+++ b/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
@@ -127,9 +127,9 @@ bool verify(AuthorizedPeers authorized_peers, const PeerCredentials& peer_creds)
return verifier->verify(peer_creds).success();
}
-AssumedRoles verify_roles(AuthorizedPeers authorized_peers, const PeerCredentials& peer_creds) {
+CapabilitySet verify_capabilities(AuthorizedPeers authorized_peers, const PeerCredentials& peer_creds) {
auto verifier = create_verify_callback_from(std::move(authorized_peers));
- return verifier->verify(peer_creds).steal_assumed_roles();
+ return verifier->verify(peer_creds).granted_capabilities();
}
TEST("Default-constructed AuthorizedPeers does not allow all authenticated peers") {
@@ -142,14 +142,16 @@ TEST("Specially constructed set of policies allows all authenticated peers") {
EXPECT_TRUE(verify(allow_all, creds_with_dns_sans({{"anything.goes"}})));
}
-TEST("specially constructed set of policies returns wildcard role set") {
+TEST("specially constructed set of policies returns full capability set") {
auto allow_all = AuthorizedPeers::allow_all_authenticated();
- EXPECT_EQUAL(verify_roles(allow_all, creds_with_dns_sans({{"anything.goes"}})), AssumedRoles::make_wildcard_role());
+ EXPECT_EQUAL(verify_capabilities(allow_all, creds_with_dns_sans({{"anything.goes"}})),
+ CapabilitySet::make_with_all_capabilities());
}
-TEST("policy without explicit role set implicitly returns wildcard role set") {
+TEST("policy without explicit capability set implicitly returns full capability set") {
auto authorized = authorized_peers({policy_with({required_san_dns("yolo.swag")})});
- EXPECT_EQUAL(verify_roles(authorized, creds_with_dns_sans({{"yolo.swag"}})), AssumedRoles::make_wildcard_role());
+ EXPECT_EQUAL(verify_capabilities(authorized, creds_with_dns_sans({{"yolo.swag"}})),
+ CapabilitySet::make_with_all_capabilities());
}
TEST("Non-empty policies do not allow all authenticated peers") {
@@ -246,11 +248,11 @@ struct MultiPolicyMatchFixture {
};
MultiPolicyMatchFixture::MultiPolicyMatchFixture()
- : authorized(authorized_peers({policy_with({required_san_dns("hello.world")}, assumed_roles({"r1"})),
- policy_with({required_san_dns("foo.bar")}, assumed_roles({"r2"})),
- policy_with({required_san_dns("zoid.berg")}, assumed_roles({"r2", "r3"})),
- policy_with({required_san_dns("secret.sauce")}, AssumedRoles::make_wildcard_role()),
- policy_with({required_san_uri("zoid://be.rg/")}, assumed_roles({"r4"}))}))
+ : authorized(authorized_peers({policy_with({required_san_dns("hello.world")}, CapabilitySet::of({cap_1()})),
+ policy_with({required_san_dns("foo.bar")}, CapabilitySet::of({cap_2()})),
+ policy_with({required_san_dns("zoid.berg")}, CapabilitySet::of({cap_2(), cap_3()})),
+ policy_with({required_san_dns("secret.sauce")}, CapabilitySet::make_with_all_capabilities()),
+ policy_with({required_san_uri("zoid://be.rg/")}, CapabilitySet::of({cap_4()}))}))
{}
MultiPolicyMatchFixture::~MultiPolicyMatchFixture() = default;
@@ -262,32 +264,37 @@ TEST_F("peer verifies if it matches at least 1 policy of multiple", MultiPolicyM
EXPECT_TRUE(verify(f.authorized, creds_with_uri_sans({{"zoid://be.rg/"}})));
}
-TEST_F("role set is returned for single matched policy", MultiPolicyMatchFixture) {
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"hello.world"}})), assumed_roles({"r1"}));
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"foo.bar"}})), assumed_roles({"r2"}));
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"zoid.berg"}})), assumed_roles({"r2", "r3"}));
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"secret.sauce"}})), AssumedRoles::make_wildcard_role());
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_uri_sans({{"zoid://be.rg/"}})), assumed_roles({"r4"}));
+TEST_F("capability set is returned for single matched policy", MultiPolicyMatchFixture) {
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"hello.world"}})),
+ CapabilitySet::of({cap_1()}));
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"foo.bar"}})),
+ CapabilitySet::of({cap_2()}));
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"zoid.berg"}})),
+ CapabilitySet::of({cap_2(), cap_3()}));
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"secret.sauce"}})),
+ CapabilitySet::make_with_all_capabilities());
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_uri_sans({{"zoid://be.rg/"}})),
+ CapabilitySet::of({cap_4()}));
}
TEST_F("peer verifies if it matches multiple policies", MultiPolicyMatchFixture) {
EXPECT_TRUE(verify(f.authorized, creds_with_dns_sans({{"hello.world"}, {"zoid.berg"}})));
}
-TEST_F("union role set is returned if multiple policies match", MultiPolicyMatchFixture) {
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}, {"zoid.berg"}})),
- assumed_roles({"r1", "r2", "r3"}));
- // Wildcard role is tracked as a distinct role string
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}, {"secret.sauce"}})),
- assumed_roles({"r1", "r2", "*"}));
+TEST_F("union capability set is returned if multiple policies match", MultiPolicyMatchFixture) {
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}, {"zoid.berg"}})),
+ CapabilitySet::of({cap_1(), cap_2(), cap_3()}));
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}, {"secret.sauce"}})),
+ CapabilitySet::make_with_all_capabilities());
}
TEST_F("peer must match at least 1 of multiple policies", MultiPolicyMatchFixture) {
EXPECT_FALSE(verify(f.authorized, creds_with_dns_sans({{"does.not.exist"}})));
}
-TEST_F("empty role set is returned if no policies match", MultiPolicyMatchFixture) {
- EXPECT_EQUAL(verify_roles(f.authorized, creds_with_dns_sans({{"does.not.exist"}})), AssumedRoles::make_empty());
+TEST_F("empty capability set is returned if no policies match", MultiPolicyMatchFixture) {
+ EXPECT_EQUAL(verify_capabilities(f.authorized, creds_with_dns_sans({{"does.not.exist"}})),
+ CapabilitySet::make_empty());
}
TEST("CN requirement without glob pattern is matched as exact string") {
@@ -308,62 +315,32 @@ TEST("CN requirement can include glob wildcards") {
EXPECT_FALSE(verify(authorized, creds_with_cn("world")));
}
-TEST("AssumedRoles by default contains no roles") {
- AssumedRoles roles;
- EXPECT_TRUE(roles.empty());
- EXPECT_FALSE(roles.can_assume_role("foo"));
- auto empty = AssumedRoles::make_empty();
- EXPECT_EQUAL(roles, empty);
-}
-
-TEST("AssumedRoles can be constructed with an explicit set of roles") {
- auto roles = AssumedRoles::make_for_roles({"foo", "bar"});
- EXPECT_TRUE(roles.can_assume_role("foo"));
- EXPECT_TRUE(roles.can_assume_role("bar"));
- EXPECT_FALSE(roles.can_assume_role("baz"));
-}
-
-TEST("AssumedRoles wildcard role can assume any role") {
- auto roles = AssumedRoles::make_wildcard_role();
- EXPECT_TRUE(roles.can_assume_role("foo"));
- EXPECT_TRUE(roles.can_assume_role("bar"));
-}
-
-TEST("AssumedRolesBuilder builds union set of added roles") {
- AssumedRolesBuilder builder;
- builder.add_union(AssumedRoles::make_for_roles({"hello", "world"}));
- builder.add_union(AssumedRoles::make_for_roles({"hello", "moon"}));
- builder.add_union(AssumedRoles::make_for_roles({"goodbye", "moon"}));
- auto roles = builder.build_with_move();
- EXPECT_EQUAL(roles, AssumedRoles::make_for_roles({"hello", "goodbye", "moon", "world"}));
-}
-
TEST("VerificationResult is not authorized by default") {
VerificationResult result;
EXPECT_FALSE(result.success());
- EXPECT_TRUE(result.assumed_roles().empty());
+ EXPECT_TRUE(result.granted_capabilities().empty());
}
TEST("VerificationResult can be explicitly created as not authorized") {
auto result = VerificationResult::make_not_authorized();
EXPECT_FALSE(result.success());
- EXPECT_TRUE(result.assumed_roles().empty());
+ EXPECT_TRUE(result.granted_capabilities().empty());
}
-TEST("VerificationResult can be pre-authorized for all roles") {
- auto result = VerificationResult::make_authorized_for_all_roles();
+TEST("VerificationResult can be pre-authorized with all capabilities") {
+ auto result = VerificationResult::make_authorized_with_all_capabilities();
EXPECT_TRUE(result.success());
- EXPECT_FALSE(result.assumed_roles().empty());
- EXPECT_TRUE(result.assumed_roles().can_assume_role("foo"));
+ EXPECT_FALSE(result.granted_capabilities().empty());
+ EXPECT_EQUAL(result.granted_capabilities(), CapabilitySet::make_with_all_capabilities());
}
-TEST("VerificationResult can be pre-authorized for an explicit set of roles") {
- auto result = VerificationResult::make_authorized_for_roles(AssumedRoles::make_for_roles({"elden", "ring"}));
+TEST("VerificationResult can be pre-authorized for an explicit set of capabilities") {
+ auto result = VerificationResult::make_authorized_with_capabilities(CapabilitySet::of({cap_2(), cap_3()}));
EXPECT_TRUE(result.success());
- EXPECT_FALSE(result.assumed_roles().empty());
- EXPECT_TRUE(result.assumed_roles().can_assume_role("elden"));
- EXPECT_TRUE(result.assumed_roles().can_assume_role("ring"));
- EXPECT_FALSE(result.assumed_roles().can_assume_role("O you don't have the right"));
+ EXPECT_FALSE(result.granted_capabilities().empty());
+ EXPECT_TRUE(result.granted_capabilities().contains(cap_2()));
+ EXPECT_TRUE(result.granted_capabilities().contains(cap_3()));
+ EXPECT_FALSE(result.granted_capabilities().contains(cap_1()));
}
// TODO test CN _and_ SAN
diff --git a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
index be2c63b03f2..8d49bdbf73d 100644
--- a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
+++ b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
@@ -198,6 +198,68 @@ TEST("unknown fields are ignored at parse-time") {
EXPECT_TRUE(read_options_from_json_string(json).get() != nullptr); // And no exception thrown.
}
+TEST("policy without explicit capabilities implicitly get all capabilities") {
+ const char* json = R"({
+ "required-credentials":[
+ {"field": "SAN_DNS", "must-match": "hello.world"}
+ ]
+ })";
+ EXPECT_EQUAL(authorized_peers({policy_with({required_san_dns("hello.world")},
+ CapabilitySet::make_with_all_capabilities())}),
+ parse_policies(json).authorized_peers());
+}
+
+TEST("specifying a capability set adds all its underlying capabilities") {
+ const char* json = R"({
+ "required-credentials":[
+ {"field": "SAN_DNS", "must-match": "*.cool-content-clusters.example" }
+ ],
+ "capabilities": ["vespa.content_node"]
+ })";
+ EXPECT_EQUAL(authorized_peers({policy_with({required_san_dns("*.cool-content-clusters.example")},
+ CapabilitySet::content_node())}),
+ parse_policies(json).authorized_peers());
+}
+
+TEST("can specify single leaf capabilities") {
+ const char* json = R"({
+ "required-credentials":[
+ {"field": "SAN_DNS", "must-match": "*.cool-content-clusters.example" }
+ ],
+ "capabilities": ["vespa.content.metrics_api", "vespa.slobrok.api"]
+ })";
+ EXPECT_EQUAL(authorized_peers({policy_with({required_san_dns("*.cool-content-clusters.example")},
+ CapabilitySet::of({Capability::content_metrics_api(),
+ Capability::slobrok_api()}))}),
+ parse_policies(json).authorized_peers());
+}
+
+TEST("specifying multiple capability sets adds union of underlying capabilities") {
+ const char* json = R"({
+ "required-credentials":[
+ {"field": "SAN_DNS", "must-match": "*.cool-content-clusters.example" }
+ ],
+ "capabilities": ["vespa.content_node", "vespa.container_node"]
+ })";
+ CapabilitySet caps;
+ caps.add_all(CapabilitySet::content_node());
+ caps.add_all(CapabilitySet::container_node());
+ EXPECT_EQUAL(authorized_peers({policy_with({required_san_dns("*.cool-content-clusters.example")}, caps)}),
+ parse_policies(json).authorized_peers());
+}
+
+TEST("empty capabilities array is not allowed") {
+ const char* json = R"({
+ "required-credentials":[
+ {"field": "SAN_DNS", "must-match": "*.cool-content-clusters.example" }
+ ],
+ "capabilities": []
+ })";
+ EXPECT_EXCEPTION(parse_policies(json), vespalib::IllegalArgumentException,
+ "\"capabilities\" array must either be not present (implies "
+ "all capabilities) or contain at least one capability name");
+}
+
// TODO test parsing of multiple policies
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp b/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
index 57c765b8e44..74af25b54a8 100644
--- a/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
+++ b/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
@@ -48,11 +48,11 @@ struct IndirectContext {
static constexpr size_t values_size = 65536;
uint64_t _values[values_size];
- IndirectContext();
+ IndirectContext() noexcept;
uint64_t* calc_value_ptr(uint64_t idx) { return &_values[(idx & (values_size - 1))]; }
};
-IndirectContext::IndirectContext()
+IndirectContext::IndirectContext() noexcept
: _value_ptr(nullptr),
_pad(),
_values()
diff --git a/vespalib/src/vespa/vespalib/net/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/CMakeLists.txt
index e3eb32d3775..05c404ec2a7 100644
--- a/vespalib/src/vespa/vespalib/net/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/net/CMakeLists.txt
@@ -9,6 +9,7 @@ endif()
vespa_add_library(vespalib_vespalib_net OBJECT
SOURCES
async_resolver.cpp
+ connection_auth_context.cpp
crypto_engine.cpp
crypto_socket.cpp
selector.cpp
diff --git a/vespalib/src/vespa/vespalib/net/connection_auth_context.cpp b/vespalib/src/vespa/vespalib/net/connection_auth_context.cpp
new file mode 100644
index 00000000000..5dd41b3b4d5
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/connection_auth_context.cpp
@@ -0,0 +1,21 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "connection_auth_context.h"
+
+namespace vespalib::net {
+
+ConnectionAuthContext::ConnectionAuthContext(tls::PeerCredentials peer_credentials,
+ tls::CapabilitySet capabilities) noexcept
+ : _peer_credentials(std::move(peer_credentials)),
+ _capabilities(std::move(capabilities))
+{
+}
+
+ConnectionAuthContext::ConnectionAuthContext(const ConnectionAuthContext&) = default;
+ConnectionAuthContext& ConnectionAuthContext::operator=(const ConnectionAuthContext&) = default;
+ConnectionAuthContext::ConnectionAuthContext(ConnectionAuthContext&&) noexcept = default;
+ConnectionAuthContext& ConnectionAuthContext::operator=(ConnectionAuthContext&&) noexcept = default;
+
+ConnectionAuthContext::~ConnectionAuthContext() = default;
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/connection_auth_context.h b/vespalib/src/vespa/vespalib/net/connection_auth_context.h
new file mode 100644
index 00000000000..fc9815f8b8e
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/connection_auth_context.h
@@ -0,0 +1,29 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+// TODO consider moving out of tls sub-namespace
+#include <vespa/vespalib/net/tls/peer_credentials.h>
+#include <vespa/vespalib/net/tls/capability_set.h>
+
+namespace vespalib::net {
+
+class ConnectionAuthContext {
+ tls::PeerCredentials _peer_credentials;
+ tls::CapabilitySet _capabilities;
+public:
+ ConnectionAuthContext(tls::PeerCredentials peer_credentials,
+ tls::CapabilitySet capabilities) noexcept;
+
+ ConnectionAuthContext(const ConnectionAuthContext&);
+ ConnectionAuthContext& operator=(const ConnectionAuthContext&);
+ ConnectionAuthContext(ConnectionAuthContext&&) noexcept;
+ ConnectionAuthContext& operator=(ConnectionAuthContext&&) noexcept;
+
+ ~ConnectionAuthContext();
+
+ const tls::PeerCredentials& peer_credentials() const noexcept { return _peer_credentials; }
+ const tls::CapabilitySet& capabilities() const noexcept { return _capabilities; }
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/crypto_socket.cpp b/vespalib/src/vespa/vespalib/net/crypto_socket.cpp
index 8d3116339a3..0ae90be8539 100644
--- a/vespalib/src/vespa/vespalib/net/crypto_socket.cpp
+++ b/vespalib/src/vespa/vespalib/net/crypto_socket.cpp
@@ -1,9 +1,18 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "crypto_socket.h"
+#include <vespa/vespalib/net/connection_auth_context.h>
namespace vespalib {
CryptoSocket::~CryptoSocket() = default;
+std::unique_ptr<net::ConnectionAuthContext>
+CryptoSocket::make_auth_context()
+{
+ return std::make_unique<net::ConnectionAuthContext>(
+ net::tls::PeerCredentials(),
+ net::tls::CapabilitySet::make_with_all_capabilities());
+}
+
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/net/crypto_socket.h b/vespalib/src/vespa/vespalib/net/crypto_socket.h
index a6ed6553bbe..9ae2af08084 100644
--- a/vespalib/src/vespa/vespalib/net/crypto_socket.h
+++ b/vespalib/src/vespa/vespalib/net/crypto_socket.h
@@ -7,6 +7,8 @@
namespace vespalib {
+namespace net { class ConnectionAuthContext; }
+
/**
* Abstraction of a low-level async network socket which can produce
* io events and allows encrypting written data and decrypting read
@@ -143,6 +145,18 @@ struct CryptoSocket {
**/
virtual void drop_empty_buffers() = 0;
+ /**
+ * If the underlying transport channel supports authn/authz,
+ * returns a new ConnectionAuthContext object containing the verified
+ * credentials of the peer as well as the resulting peer capabilities
+ * inferred by our own policy matching.
+ *
+ * If the underlying transport channel does _not_ support authn/authz
+ * (such as a plaintext connection) a dummy context is returned which
+ * offers _all_ capabilities.
+ */
+ [[nodiscard]] virtual std::unique_ptr<net::ConnectionAuthContext> make_auth_context();
+
virtual ~CryptoSocket();
};
diff --git a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
index a94d088b6a8..5be2e0d4387 100644
--- a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
@@ -1,9 +1,10 @@
# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_library(vespalib_vespalib_net_tls OBJECT
SOURCES
- assumed_roles.cpp
authorization_mode.cpp
auto_reloading_tls_crypto_engine.cpp
+ capability.cpp
+ capability_set.cpp
crypto_codec.cpp
crypto_codec_adapter.cpp
maybe_tls_crypto_engine.cpp
diff --git a/vespalib/src/vespa/vespalib/net/tls/assumed_roles.cpp b/vespalib/src/vespa/vespalib/net/tls/assumed_roles.cpp
deleted file mode 100644
index 672458d0024..00000000000
--- a/vespalib/src/vespa/vespalib/net/tls/assumed_roles.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "assumed_roles.h"
-#include <vespa/vespalib/stllike/asciistream.h>
-#include <algorithm>
-#include <ostream>
-
-namespace vespalib::net::tls {
-
-const string AssumedRoles::WildcardRole("*");
-
-AssumedRoles::AssumedRoles() = default;
-
-AssumedRoles::AssumedRoles(RoleSet assumed_roles)
- : _assumed_roles(std::move(assumed_roles))
-{}
-
-AssumedRoles::AssumedRoles(const AssumedRoles&) = default;
-AssumedRoles& AssumedRoles::operator=(const AssumedRoles&) = default;
-AssumedRoles::AssumedRoles(AssumedRoles&&) noexcept = default;
-AssumedRoles& AssumedRoles::operator=(AssumedRoles&&) noexcept = default;
-AssumedRoles::~AssumedRoles() = default;
-
-bool AssumedRoles::can_assume_role(const string& role) const noexcept {
- return (_assumed_roles.contains(role) || _assumed_roles.contains(WildcardRole));
-}
-
-std::vector<string> AssumedRoles::ordered_roles() const {
- std::vector<string> roles;
- for (const auto& r : _assumed_roles) {
- roles.emplace_back(r);
- }
- std::sort(roles.begin(), roles.end());
- return roles;
-}
-
-bool AssumedRoles::operator==(const AssumedRoles& rhs) const noexcept {
- return (_assumed_roles == rhs._assumed_roles);
-}
-
-void AssumedRoles::print(asciistream& os) const {
- os << "AssumedRoles(roles: [";
- auto roles = ordered_roles();
- for (size_t i = 0; i < roles.size(); ++i) {
- if (i > 0) {
- os << ", ";
- }
- os << roles[i];
- }
- os << "])";
-}
-
-asciistream& operator<<(asciistream& os, const AssumedRoles& res) {
- res.print(os);
- return os;
-}
-
-std::ostream& operator<<(std::ostream& os, const AssumedRoles& res) {
- os << to_string(res);
- return os;
-}
-
-string to_string(const AssumedRoles& res) {
- asciistream os;
- os << res;
- return os.str();
-}
-
-AssumedRoles AssumedRoles::make_for_roles(RoleSet assumed_roles) {
- return AssumedRoles(std::move(assumed_roles));
-}
-
-AssumedRoles AssumedRoles::make_wildcard_role() {
- return AssumedRoles(RoleSet({WildcardRole}));
-}
-
-AssumedRoles AssumedRoles::make_empty() {
- return {};
-}
-
-AssumedRolesBuilder::AssumedRolesBuilder() = default;
-AssumedRolesBuilder::~AssumedRolesBuilder() = default;
-
-void AssumedRolesBuilder::add_union(const AssumedRoles& roles) {
- // TODO fix hash_set iterator range insert()
- for (const auto& role : roles.unordered_roles()) {
- _wip_roles.insert(role);
- }
-}
-
-AssumedRoles AssumedRolesBuilder::build_with_move() {
- return AssumedRoles::make_for_roles(std::move(_wip_roles));
-}
-
-}
-
diff --git a/vespalib/src/vespa/vespalib/net/tls/assumed_roles.h b/vespalib/src/vespa/vespalib/net/tls/assumed_roles.h
deleted file mode 100644
index 00d800916fd..00000000000
--- a/vespalib/src/vespa/vespalib/net/tls/assumed_roles.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-
-#include <vespa/vespalib/stllike/hash_set.h>
-#include <vespa/vespalib/stllike/string.h>
-#include <vector>
-#include <iosfwd>
-
-namespace vespalib { class asciistream; }
-
-namespace vespalib::net::tls {
-
-/**
- * Encapsulates a set of roles that requests over a particular authenticated
- * connection can assume, based on the authorization rules it matched during mTLS
- * handshaking.
- *
- * If at least one role is a wildcard ('*') role, the connection can assume _any_
- * possible role. This is the default when no role constraints are specified in
- * the TLS configuration file (legacy behavior). However, a default-constructed
- * AssumedRoles instance does not allow any roles to be assumed.
- */
-class AssumedRoles {
-public:
- using RoleSet = hash_set<string>;
-private:
- RoleSet _assumed_roles;
-
- static const string WildcardRole;
-
- explicit AssumedRoles(RoleSet assumed_roles);
-public:
- AssumedRoles();
- AssumedRoles(const AssumedRoles&);
- AssumedRoles& operator=(const AssumedRoles&);
- AssumedRoles(AssumedRoles&&) noexcept;
- AssumedRoles& operator=(AssumedRoles&&) noexcept;
- ~AssumedRoles();
-
- [[nodiscard]] bool empty() const noexcept {
- return _assumed_roles.empty();
- }
-
- /**
- * Returns true iff `role` is present in the role set OR the role set contains
- * the special wildcard role.
- */
- [[nodiscard]] bool can_assume_role(const string& role) const noexcept;
-
- [[nodiscard]] const RoleSet& unordered_roles() const noexcept {
- return _assumed_roles;
- }
-
- [[nodiscard]] std::vector<string> ordered_roles() const;
-
- bool operator==(const AssumedRoles& rhs) const noexcept;
-
- void print(asciistream& os) const;
-
- static AssumedRoles make_for_roles(RoleSet assumed_roles);
- static AssumedRoles make_wildcard_role(); // Allows assuming _all_ possible roles
- static AssumedRoles make_empty(); // Matches _no_ possible roles
-};
-
-asciistream& operator<<(asciistream&, const AssumedRoles&);
-std::ostream& operator<<(std::ostream&, const AssumedRoles&);
-string to_string(const AssumedRoles&);
-
-class AssumedRolesBuilder {
- AssumedRoles::RoleSet _wip_roles;
-public:
- AssumedRolesBuilder();
- ~AssumedRolesBuilder();
-
- void add_union(const AssumedRoles& roles);
- [[nodiscard]] bool empty() const noexcept { return _wip_roles.empty(); }
- [[nodiscard]] AssumedRoles build_with_move();
-};
-
-}
diff --git a/vespalib/src/vespa/vespalib/net/tls/capability.cpp b/vespalib/src/vespa/vespalib/net/tls/capability.cpp
new file mode 100644
index 00000000000..64de250e60d
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/capability.cpp
@@ -0,0 +1,62 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "capability.h"
+#include <vespa/vespalib/stllike/hash_map.hpp>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <array>
+
+namespace vespalib::net::tls {
+
+namespace {
+
+using namespace std::string_view_literals;
+
+// Important: must match 1-1 with CapabilityId values!
+constexpr std::array<std::string_view, Capability::max_value_count()> capability_names = {
+ "vespa.content.storage_api"sv,
+ "vespa.content.document_api"sv,
+ "vespa.content.search_api"sv,
+ "vespa.content.cluster_controller.internal_state_api"sv,
+ "vespa.slobrok.api"sv,
+ "vespa.content.status_pages"sv,
+ "vespa.content.metrics_api"sv,
+};
+
+} // anon ns
+
+std::string_view Capability::name() const noexcept {
+ return capability_names[id_as_idx()];
+}
+
+string Capability::to_string() const {
+ asciistream os;
+ // TODO asciistream should be made std::string_view-aware
+ os << "Capability(" << stringref(name().data(), name().length()) << ')';
+ return os.str();
+}
+
+std::optional<Capability> Capability::find_capability(const string& cap_name) noexcept {
+ static const hash_map<string, Capability> name_to_cap({
+ {"vespa.content.storage_api", content_storage_api()},
+ {"vespa.content.document_api", content_document_api()},
+ {"vespa.content.search_api", content_search_api()},
+ {"vespa.content.cluster_controller.internal_state_api", content_cluster_controller_internal_state_api()},
+ {"vespa.slobrok.api", slobrok_api()},
+ {"vespa.content.status_pages", content_status_pages()},
+ {"vespa.content.metrics_api", content_metrics_api()},
+ });
+ auto iter = name_to_cap.find(cap_name);
+ return (iter != name_to_cap.end()) ? std::optional<Capability>(iter->second) : std::nullopt;
+}
+
+std::ostream& operator<<(std::ostream& os, const Capability& cap) {
+ os << cap.to_string();
+ return os;
+}
+
+asciistream& operator<<(asciistream& os, const Capability& cap) {
+ os << cap.to_string();
+ return os;
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/capability.h b/vespalib/src/vespa/vespalib/net/tls/capability.h
new file mode 100644
index 00000000000..842e3f3a363
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/capability.h
@@ -0,0 +1,104 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/vespalib/stllike/string.h>
+#include <bitset>
+#include <iosfwd>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+namespace vespalib { class asciistream; }
+
+namespace vespalib::net::tls {
+
+/**
+ * A capability represents the ability to access a distinct service or API
+ * plane in Vespa (such as the Document API).
+ *
+ * Capability instances are intended to be very cheap to pass and store by value.
+ */
+class Capability {
+private:
+ // Each ID value corresponds to a unique single-bit position.
+ // These values shall never be exposed outside the running process, i.e. they
+ // must be possible to change arbitrarily internally across versions.
+ enum class Id : uint32_t {
+ ContentStorageApi = 0, // Must start at zero
+ ContentDocumentApi,
+ ContentSearchApi,
+ ContentClusterControllerInternalStateApi,
+ SlobrokApi,
+ ContentStatusPages,
+ ContentMetricsApi,
+ // When adding a capability ID to the end, max_value_count() MUST be updated
+ };
+public:
+ constexpr static size_t max_value_count() noexcept {
+ // This must refer to the highest possible CapabilityId enum value.
+ return static_cast<size_t>(Id::ContentMetricsApi) + 1;
+ }
+private:
+ Id _cap_id;
+
+ friend class CapabilitySet; // CapabilitySet needs to know the raw IDs for bit set bookkeeping
+
+ constexpr Id id() const noexcept { return _cap_id; }
+ constexpr uint32_t id_as_idx() const noexcept { return static_cast<uint32_t>(_cap_id); }
+
+ constexpr explicit Capability(Id cap_id) noexcept : _cap_id(cap_id) {}
+
+ constexpr static Capability of(Id id) noexcept {
+ return Capability(id);
+ }
+
+public:
+ Capability() = delete; // Only valid capabilities can be created.
+
+ constexpr bool operator==(const Capability& rhs) const noexcept {
+ return (_cap_id == rhs._cap_id);
+ }
+
+ constexpr bool operator!=(const Capability& rhs) const noexcept {
+ return !(*this == rhs);
+ }
+
+ std::string_view name() const noexcept;
+ string to_string() const;
+
+ static std::optional<Capability> find_capability(const string& cap_name) noexcept;
+
+ constexpr static Capability content_storage_api() noexcept {
+ return Capability(Id::ContentStorageApi);
+ }
+
+ constexpr static Capability content_document_api() noexcept {
+ return Capability(Id::ContentDocumentApi);
+ }
+
+ constexpr static Capability content_search_api() noexcept {
+ return Capability(Id::ContentSearchApi);
+ }
+
+ constexpr static Capability content_cluster_controller_internal_state_api() noexcept {
+ return Capability(Id::ContentClusterControllerInternalStateApi);
+ }
+
+ constexpr static Capability slobrok_api() noexcept {
+ return Capability(Id::SlobrokApi);
+ }
+
+ constexpr static Capability content_status_pages() noexcept {
+ return Capability(Id::ContentStatusPages);
+ }
+
+ constexpr static Capability content_metrics_api() noexcept {
+ return Capability(Id::ContentMetricsApi);
+ }
+
+};
+
+std::ostream& operator<<(std::ostream&, const Capability& cap);
+asciistream& operator<<(asciistream&, const Capability& cap);
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/capability_set.cpp b/vespalib/src/vespa/vespalib/net/tls/capability_set.cpp
new file mode 100644
index 00000000000..3663694e31a
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/capability_set.cpp
@@ -0,0 +1,95 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "capability_set.h"
+#include <vespa/vespalib/stllike/hash_map.hpp>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <cassert>
+
+namespace vespalib::net::tls {
+
+string CapabilitySet::to_string() const {
+ asciistream os;
+ os << "CapabilitySet({";
+ bool emit_comma = false;
+ for_each_capability([&emit_comma, &os](Capability cap) {
+ if (emit_comma) {
+ os << ", ";
+ } else {
+ emit_comma = true;
+ }
+ // TODO let asciistream and std::string_view play along
+ os << stringref(cap.name().data(), cap.name().size());
+ });
+ os << "})";
+ return os.str();
+}
+
+std::optional<CapabilitySet> CapabilitySet::find_capability_set(const string& cap_set_name) noexcept {
+ static const hash_map<string, CapabilitySet> name_to_cap_set({
+ {"vespa.content_node", content_node()},
+ {"vespa.container_node", container_node()},
+ {"vespa.telemetry", telemetry()},
+ {"vespa.cluster_controller_node", cluster_controller_node()},
+ {"vespa.config_server", config_server()}
+ });
+ auto iter = name_to_cap_set.find(cap_set_name);
+ return (iter != name_to_cap_set.end()) ? std::optional<CapabilitySet>(iter->second) : std::nullopt;
+}
+
+bool CapabilitySet::resolve_and_add(const string& set_or_cap_name) noexcept {
+ if (auto cap_set = find_capability_set(set_or_cap_name)) {
+ _capability_mask |= cap_set->_capability_mask;
+ return true;
+ } else if (auto cap = Capability::find_capability(set_or_cap_name)) {
+ _capability_mask |= cap_as_bit_set(*cap);
+ return true;
+ }
+ return false;
+}
+
+// Note: the capability set factory functions below are all just using constexpr and/or inline
+// functions, so the compiler will happily optimize them to just "return <constant bit pattern>".
+
+CapabilitySet CapabilitySet::content_node() noexcept {
+ return CapabilitySet::of({Capability::content_storage_api(),
+ Capability::content_document_api(),
+ Capability::slobrok_api()});
+}
+
+CapabilitySet CapabilitySet::container_node() noexcept {
+ return CapabilitySet::of({Capability::content_document_api(),
+ Capability::content_search_api(),
+ Capability::slobrok_api()});
+}
+
+CapabilitySet CapabilitySet::telemetry() noexcept {
+ return CapabilitySet::of({Capability::content_status_pages(),
+ Capability::content_metrics_api()});
+}
+
+CapabilitySet CapabilitySet::cluster_controller_node() noexcept {
+ return CapabilitySet::of({Capability::content_cluster_controller_internal_state_api(),
+ Capability::slobrok_api()});
+}
+
+CapabilitySet CapabilitySet::config_server() noexcept {
+ return CapabilitySet::of({/*TODO define required capabilities*/});
+}
+
+CapabilitySet CapabilitySet::make_with_all_capabilities() noexcept {
+ BitSet bit_set;
+ bit_set.flip(); // All cap bits set
+ return CapabilitySet(bit_set);
+}
+
+std::ostream& operator<<(std::ostream& os, const CapabilitySet& cap_set) {
+ os << cap_set.to_string();
+ return os;
+}
+
+asciistream& operator<<(asciistream& os, const CapabilitySet& cap_set) {
+ os << cap_set.to_string();
+ return os;
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/capability_set.h b/vespalib/src/vespa/vespalib/net/tls/capability_set.h
new file mode 100644
index 00000000000..f86b043ee7b
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/capability_set.h
@@ -0,0 +1,117 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "capability.h"
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/stllike/hash_set.h>
+#include <bitset>
+#include <initializer_list>
+#include <iosfwd>
+#include <optional>
+#include <vector>
+
+namespace vespalib { class asciistream; }
+
+namespace vespalib::net::tls {
+
+/**
+ * A CapabilitySet efficiently represents a finite set (possibly empty) of individual
+ * capabilities and allows for both single and set-based membership tests.
+ *
+ * Factory functions are provided for all predefined Vespa capability sets.
+ *
+ * CapabilitySet instances are intended to be very cheap to pass and store by value.
+ */
+class CapabilitySet {
+ using BitSet = std::bitset<Capability::max_value_count()>;
+ BitSet _capability_mask;
+
+ constexpr static uint32_t cap_as_bit_pos(const Capability& cap) noexcept {
+ return cap.id_as_idx();
+ }
+
+ constexpr static BitSet cap_as_bit_set(const Capability& cap) noexcept {
+ static_assert(Capability::max_value_count() <= 32); // Must fit into uint32_t bitmask
+ return {uint32_t(1) << cap_as_bit_pos(cap)};
+ }
+
+ explicit constexpr CapabilitySet(BitSet capabilities) noexcept
+ : _capability_mask(capabilities)
+ {}
+public:
+ constexpr CapabilitySet() noexcept = default;
+ constexpr ~CapabilitySet() = default;
+
+ string to_string() const;
+
+ bool operator==(const CapabilitySet& rhs) const noexcept {
+ return (_capability_mask == rhs._capability_mask);
+ }
+
+ [[nodiscard]] bool empty() const noexcept {
+ return _capability_mask.none();
+ }
+ size_t count() const noexcept {
+ return _capability_mask.count();
+ }
+ constexpr static size_t max_count() noexcept {
+ return Capability::max_value_count();
+ }
+
+ [[nodiscard]] constexpr bool contains(Capability cap) const noexcept {
+ return _capability_mask[cap_as_bit_pos(cap)];
+ }
+ [[nodiscard]] bool contains_all(CapabilitySet caps) const noexcept {
+ return ((_capability_mask & caps._capability_mask) == caps._capability_mask);
+ }
+
+ void add(const Capability& cap) noexcept {
+ _capability_mask |= cap_as_bit_set(cap);
+ }
+ void add_all(const CapabilitySet& cap_set) noexcept {
+ _capability_mask |= cap_set._capability_mask;
+ }
+
+ template <typename Func>
+ void for_each_capability(Func f) const noexcept(noexcept(f(Capability::content_storage_api()))) {
+ for (size_t i = 0; i < _capability_mask.size(); ++i) {
+ if (_capability_mask[i]) {
+ f(Capability::of(static_cast<Capability::Id>(i)));
+ }
+ }
+ }
+
+ /**
+ * Since we have two capability naming "tiers", resolving is done in two steps:
+ * 1. Check if the name matches a known capability _set_ name. If so, add
+ * all unique capabilities within the set to our own working set. Return true.
+ * 2. Check if the name matches a known single capability. If so, add that
+ * capability to our own working set. Return true.
+ * 3. Otherwise, return false.
+ */
+ [[nodiscard]] bool resolve_and_add(const string& set_or_cap_name) noexcept;
+
+ static std::optional<CapabilitySet> find_capability_set(const string& cap_set_name) noexcept;
+
+ static CapabilitySet of(std::initializer_list<Capability> caps) noexcept {
+ CapabilitySet set;
+ for (const auto& cap : caps) {
+ set._capability_mask |= cap_as_bit_set(cap);
+ }
+ return set;
+ }
+
+ static CapabilitySet content_node() noexcept;
+ static CapabilitySet container_node() noexcept;
+ static CapabilitySet telemetry() noexcept;
+ static CapabilitySet cluster_controller_node() noexcept;
+ static CapabilitySet config_server() noexcept;
+
+ static CapabilitySet make_with_all_capabilities() noexcept;
+ static CapabilitySet make_empty() noexcept { return CapabilitySet(); };
+};
+
+std::ostream& operator<<(std::ostream&, const CapabilitySet& cap_set);
+asciistream& operator<<(asciistream&, const CapabilitySet& cap_set);
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/certificate_verification_callback.h b/vespalib/src/vespa/vespalib/net/tls/certificate_verification_callback.h
index f4d8d39206b..c670d54273e 100644
--- a/vespalib/src/vespa/vespalib/net/tls/certificate_verification_callback.h
+++ b/vespalib/src/vespa/vespalib/net/tls/certificate_verification_callback.h
@@ -22,7 +22,7 @@ struct CertificateVerificationCallback {
// and it is signed by a trusted CA.
struct AcceptAllPreVerifiedCertificates : CertificateVerificationCallback {
VerificationResult verify([[maybe_unused]] const PeerCredentials& peer_creds) const override {
- return VerificationResult::make_authorized_for_all_roles(); // yolo
+ return VerificationResult::make_authorized_with_all_capabilities(); // yolo
}
};
diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h
index fc729d7dd6a..8b2f258199b 100644
--- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h
+++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
+#include "capability_set.h"
#include <vespa/vespalib/net/socket_address.h>
#include <memory>
@@ -54,7 +55,6 @@ struct DecodeResult {
struct TlsContext;
struct PeerCredentials;
-class AssumedRoles;
// TODO move to different namespace, not dependent on TLS?
@@ -185,9 +185,9 @@ public:
[[nodiscard]] virtual const PeerCredentials& peer_credentials() const noexcept = 0;
/**
- * Union set of all assumed roles in the peer policy rules that fully matched the peer's credentials.
+ * Union set of all granted capabilities in the peer policy rules that fully matched the peer's credentials.
*/
- [[nodiscard]] virtual const AssumedRoles& assumed_roles() const noexcept = 0;
+ [[nodiscard]] virtual CapabilitySet granted_capabilities() const noexcept = 0;
/*
* Creates an implementation defined CryptoCodec that provides at least TLSv1.2
diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
index 03170ae0f68..a50acc55bd0 100644
--- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "crypto_codec_adapter.h"
+#include <vespa/vespalib/net/connection_auth_context.h>
#include <assert.h>
namespace vespalib::net::tls {
@@ -208,4 +209,10 @@ CryptoCodecAdapter::drop_empty_buffers()
_output.drop_if_empty();
}
+std::unique_ptr<net::ConnectionAuthContext>
+CryptoCodecAdapter::make_auth_context()
+{
+ return std::make_unique<net::ConnectionAuthContext>(_codec->peer_credentials(), _codec->granted_capabilities());
+}
+
} // namespace vespalib::net::tls
diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
index 1d5b57dc9b2..4b3c66cbc3c 100644
--- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
+++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
@@ -46,6 +46,7 @@ public:
ssize_t flush() override;
ssize_t half_close() override;
void drop_empty_buffers() override;
+ std::unique_ptr<net::ConnectionAuthContext> make_auth_context() override;
};
} // namespace vespalib::net::tls
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h
index 5be2146b349..ca7237bfa9a 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h
@@ -4,8 +4,8 @@
#include <vespa/vespalib/crypto/openssl_typedefs.h>
#include <vespa/vespalib/net/socket_address.h>
#include <vespa/vespalib/net/socket_spec.h>
-#include <vespa/vespalib/net/tls/assumed_roles.h>
#include <vespa/vespalib/net/tls/crypto_codec.h>
+#include <vespa/vespalib/net/tls/capability_set.h>
#include <vespa/vespalib/net/tls/peer_credentials.h>
#include <vespa/vespalib/net/tls/transport_security_options.h>
#include <memory>
@@ -58,7 +58,7 @@ class OpenSslCryptoCodecImpl : public CryptoCodec {
std::optional<DeferredHandshakeParams> _deferred_handshake_params;
std::optional<HandshakeResult> _deferred_handshake_result;
PeerCredentials _peer_credentials;
- AssumedRoles _assumed_roles;
+ CapabilitySet _granted_capabilities;
public:
~OpenSslCryptoCodecImpl() override;
@@ -103,8 +103,8 @@ public:
return _peer_credentials;
}
- [[nodiscard]] const AssumedRoles& assumed_roles() const noexcept override {
- return _assumed_roles;
+ [[nodiscard]] CapabilitySet granted_capabilities() const noexcept override {
+ return _granted_capabilities;
}
const SocketAddress& peer_address() const noexcept { return _peer_address; }
@@ -120,8 +120,8 @@ public:
void set_peer_credentials(PeerCredentials peer_credentials) {
_peer_credentials = std::move(peer_credentials);
}
- void set_assumed_roles(AssumedRoles assumed_roles) {
- _assumed_roles = std::move(assumed_roles);
+ void set_granted_capabilities(CapabilitySet granted_capabilities) {
+ _granted_capabilities = granted_capabilities;
}
private:
OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContextImpl> ctx,
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
index 3810140854b..d7977f6cd2a 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
@@ -488,7 +488,7 @@ bool OpenSslTlsContextImpl::verify_trusted_certificate(::X509_STORE_CTX* store_c
// Store away credentials and role set for later use by requests that arrive over this connection.
// TODO encapsulate as const shared_ptr to immutable object to better facilitate sharing?
codec_impl.set_peer_credentials(std::move(creds));
- codec_impl.set_assumed_roles(authz_result.steal_assumed_roles());
+ codec_impl.set_granted_capabilities(authz_result.granted_capabilities());
} catch (std::exception& e) {
LOGBT(error, codec_impl.peer_address().ip_address(),
"Got exception during certificate verification callback for peer '%s': %s",
diff --git a/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp b/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
index a4e651f3f19..eaa6a8c2298 100644
--- a/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
@@ -123,13 +123,14 @@ PeerPolicy::PeerPolicy() = default;
PeerPolicy::PeerPolicy(std::vector<RequiredPeerCredential> required_peer_credentials)
: _required_peer_credentials(std::move(required_peer_credentials)),
- _assumed_roles(AssumedRoles::make_wildcard_role())
-{}
+ _granted_capabilities(CapabilitySet::make_with_all_capabilities())
+{
+}
PeerPolicy::PeerPolicy(std::vector<RequiredPeerCredential> required_peer_credentials,
- AssumedRoles assumed_roles)
+ CapabilitySet granted_capabilities)
: _required_peer_credentials(std::move(required_peer_credentials)),
- _assumed_roles(std::move(assumed_roles))
+ _granted_capabilities(granted_capabilities)
{}
PeerPolicy::~PeerPolicy() = default;
@@ -170,7 +171,7 @@ std::ostream& operator<<(std::ostream& os, const RequiredPeerCredential& cred) {
std::ostream& operator<<(std::ostream& os, const PeerPolicy& policy) {
os << "PeerPolicy(";
print_joined(os, policy.required_peer_credentials(), ", ");
- os << ")";
+ os << ", " << policy.granted_capabilities().to_string() << ")";
return os;
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/peer_policies.h b/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
index 6eab8c2c9b2..3314e5e4adf 100644
--- a/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
+++ b/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
-#include "assumed_roles.h"
+#include "capability_set.h"
#include <vespa/vespalib/stllike/string.h>
#include <memory>
#include <vector>
@@ -50,26 +50,26 @@ public:
class PeerPolicy {
// _All_ credentials must match for the policy itself to match.
std::vector<RequiredPeerCredential> _required_peer_credentials;
- AssumedRoles _assumed_roles;
+ CapabilitySet _granted_capabilities;
public:
PeerPolicy();
- // This policy is created with a wildcard role set, i.e. full access.
+ // This policy is created with a full capability set, i.e. unrestricted access.
explicit PeerPolicy(std::vector<RequiredPeerCredential> required_peer_credentials);
PeerPolicy(std::vector<RequiredPeerCredential> required_peer_credentials,
- AssumedRoles assumed_roles);
+ CapabilitySet granted_capabilities);
~PeerPolicy();
bool operator==(const PeerPolicy& rhs) const noexcept {
return ((_required_peer_credentials == rhs._required_peer_credentials) &&
- (_assumed_roles == rhs._assumed_roles));
+ (_granted_capabilities == rhs._granted_capabilities));
}
[[nodiscard]] const std::vector<RequiredPeerCredential>& required_peer_credentials() const noexcept {
return _required_peer_credentials;
}
- [[nodiscard]] const AssumedRoles& assumed_roles() const noexcept {
- return _assumed_roles;
+ [[nodiscard]] const CapabilitySet& granted_capabilities() const noexcept {
+ return _granted_capabilities;
}
};
diff --git a/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp b/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
index 4018e20225e..d9dbca6e808 100644
--- a/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
@@ -71,16 +71,16 @@ PolicyConfiguredCertificateVerifier::~PolicyConfiguredCertificateVerifier() = de
VerificationResult PolicyConfiguredCertificateVerifier::verify(const PeerCredentials& peer_creds) const {
if (_authorized_peers.allows_all_authenticated()) {
- return VerificationResult::make_authorized_for_all_roles();
+ return VerificationResult::make_authorized_with_all_capabilities();
}
- AssumedRolesBuilder roles_builder;
+ CapabilitySet caps;
for (const auto& policy : _authorized_peers.peer_policies()) {
if (matches_all_policy_requirements(peer_creds, policy)) {
- roles_builder.add_union(policy.assumed_roles());
+ caps.add_all(policy.granted_capabilities());
}
}
- if (!roles_builder.empty()) {
- return VerificationResult::make_authorized_for_roles(roles_builder.build_with_move());
+ if (!caps.empty()) {
+ return VerificationResult::make_authorized_with_capabilities(std::move(caps));
} else {
return VerificationResult::make_not_authorized();
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_socket.h b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_socket.h
index 0d036fdad4b..01d20155bd1 100644
--- a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_socket.h
+++ b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_socket.h
@@ -7,7 +7,7 @@
namespace vespalib {
struct TlsCryptoSocket : public CryptoSocket {
- ~TlsCryptoSocket();
+ ~TlsCryptoSocket() override;
virtual void inject_read_data(const char *buf, size_t len) = 0;
};
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
index 2e80135813d..94281d3ef41 100644
--- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
@@ -5,6 +5,8 @@
#include <vespa/vespalib/io/fileutil.h>
#include <vespa/vespalib/io/mapped_file_input.h>
#include <vespa/vespalib/data/memory_input.h>
+#include <vespa/vespalib/net/tls/capability_set.h>
+#include <vespa/vespalib/stllike/hash_set.h>
namespace vespalib::net::tls {
@@ -24,7 +26,8 @@ namespace vespalib::net::tls {
{ "field":"CN", "must-match": "*.config.blarg"},
{ "field":"SAN_DNS", "must-match": "*.fancy.config.blarg"}
],
- "name": "funky config servers"
+ "name": "funky config servers",
+ "capabilities": ["vespa.content.coolstuff"]
}
]
}
@@ -68,8 +71,7 @@ RequiredPeerCredential parse_peer_credential(const Inspector& req_entry) {
return RequiredPeerCredential(field, std::move(match));
}
-PeerPolicy parse_peer_policy(const Inspector& peer_entry) {
- auto& creds = peer_entry["required-credentials"];
+std::vector<RequiredPeerCredential> parse_peer_credentials(const Inspector& creds) {
if (creds.children() == 0) {
throw IllegalArgumentException("\"required-credentials\" array can't be empty (would allow all peers)");
}
@@ -77,7 +79,31 @@ PeerPolicy parse_peer_policy(const Inspector& peer_entry) {
for (size_t i = 0; i < creds.children(); ++i) {
required_creds.emplace_back(parse_peer_credential(creds[i]));
}
- return PeerPolicy(std::move(required_creds));
+ return required_creds;
+}
+
+CapabilitySet parse_capabilities(const Inspector& caps) {
+ CapabilitySet capabilities;
+ if (caps.valid() && (caps.children() == 0)) {
+ throw IllegalArgumentException("\"capabilities\" array must either be not present (implies "
+ "all capabilities) or contain at least one capability name");
+ } else if (caps.valid()) {
+ for (size_t i = 0; i < caps.children(); ++i) {
+ // TODO warn if resolve_and_add returns false; means capability is unknown!
+ (void)capabilities.resolve_and_add(caps[i].asString().make_string());
+ }
+ } else {
+ // If no capabilities are specified, all are implicitly granted.
+ // This avoids breaking every legacy mTLS app ever.
+ capabilities = CapabilitySet::make_with_all_capabilities();
+ }
+ return capabilities;
+}
+
+PeerPolicy parse_peer_policy(const Inspector& peer_entry) {
+ auto required_creds = parse_peer_credentials(peer_entry["required-credentials"]);
+ auto capabilities = parse_capabilities(peer_entry["capabilities"]);
+ return {std::move(required_creds), std::move(capabilities)};
}
AuthorizedPeers parse_authorized_peers(const Inspector& authorized_peers) {
diff --git a/vespalib/src/vespa/vespalib/net/tls/verification_result.cpp b/vespalib/src/vespa/vespalib/net/tls/verification_result.cpp
index e4833f59f47..f1e50d3115e 100644
--- a/vespalib/src/vespa/vespalib/net/tls/verification_result.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/verification_result.cpp
@@ -8,8 +8,8 @@ namespace vespalib::net::tls {
VerificationResult::VerificationResult() = default;
-VerificationResult::VerificationResult(AssumedRoles assumed_roles)
- : _assumed_roles(std::move(assumed_roles))
+VerificationResult::VerificationResult(CapabilitySet granted_capabilities)
+ : _granted_capabilities(std::move(granted_capabilities))
{}
VerificationResult::VerificationResult(const VerificationResult&) = default;
@@ -23,19 +23,19 @@ void VerificationResult::print(asciistream& os) const {
if (!success()) {
os << "NOT AUTHORIZED";
} else {
- os << _assumed_roles;
+ os << _granted_capabilities;
}
os << ')';
}
VerificationResult
-VerificationResult::make_authorized_for_roles(AssumedRoles assumed_roles) {
- return VerificationResult(std::move(assumed_roles));
+VerificationResult::make_authorized_with_capabilities(CapabilitySet granted_capabilities) {
+ return VerificationResult(std::move(granted_capabilities));
}
VerificationResult
-VerificationResult::make_authorized_for_all_roles() {
- return VerificationResult(AssumedRoles::make_wildcard_role());
+VerificationResult::make_authorized_with_all_capabilities() {
+ return VerificationResult(CapabilitySet::make_with_all_capabilities());
}
VerificationResult
diff --git a/vespalib/src/vespa/vespalib/net/tls/verification_result.h b/vespalib/src/vespa/vespalib/net/tls/verification_result.h
index 2de89269ba4..92b32ad92f7 100644
--- a/vespalib/src/vespa/vespalib/net/tls/verification_result.h
+++ b/vespalib/src/vespa/vespalib/net/tls/verification_result.h
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
-#include "assumed_roles.h"
+#include "capability_set.h"
#include <vespa/vespalib/stllike/string.h>
#include <iosfwd>
@@ -13,14 +13,14 @@ namespace vespalib::net::tls {
* The result of evaluating configured mTLS authorization rules against the
* credentials presented by a successfully authenticated peer certificate.
*
- * This result contains the union set of all roles specified by the matching
- * authorization rules. If no rules matched, the set will be empty. The role
+ * This result contains the union set of all capabilities granted by the matching
+ * authorization rules. If no rules matched, the set will be empty. The capability
* set will also be empty for a default-constructed instance.
*/
class VerificationResult {
- AssumedRoles _assumed_roles;
+ CapabilitySet _granted_capabilities;
- explicit VerificationResult(AssumedRoles assumed_roles);
+ explicit VerificationResult(CapabilitySet granted_capabilities);
public:
VerificationResult();
VerificationResult(const VerificationResult&);
@@ -29,22 +29,19 @@ public:
VerificationResult& operator=(VerificationResult&&) noexcept;
~VerificationResult();
- // Returns true iff at least one assumed role has been granted.
+ // Returns true iff at least one capability been granted.
[[nodiscard]] bool success() const noexcept {
- return !_assumed_roles.empty();
+ return !_granted_capabilities.empty();
}
- [[nodiscard]] const AssumedRoles& assumed_roles() const noexcept {
- return _assumed_roles;
- }
- [[nodiscard]] AssumedRoles steal_assumed_roles() noexcept {
- return std::move(_assumed_roles);
+ [[nodiscard]] const CapabilitySet& granted_capabilities() const noexcept {
+ return _granted_capabilities;
}
void print(asciistream& os) const;
- static VerificationResult make_authorized_for_roles(AssumedRoles assumed_roles);
- static VerificationResult make_authorized_for_all_roles();
+ static VerificationResult make_authorized_with_capabilities(CapabilitySet granted_capabilities);
+ static VerificationResult make_authorized_with_all_capabilities();
static VerificationResult make_not_authorized();
};
diff --git a/vespalib/src/vespa/vespalib/process/process.cpp b/vespalib/src/vespa/vespalib/process/process.cpp
index 7749ffdff65..5827546d0b4 100644
--- a/vespalib/src/vespa/vespalib/process/process.cpp
+++ b/vespalib/src/vespa/vespalib/process/process.cpp
@@ -10,6 +10,7 @@
#include <sys/types.h>
#include <sys/wait.h>
+#include <csignal>
#include <unistd.h>
#include <fcntl.h>
diff --git a/vespalib/src/vespa/vespalib/stllike/hash_fun.h b/vespalib/src/vespa/vespalib/stllike/hash_fun.h
index 4900fcd5a2b..daedddbba1b 100644
--- a/vespalib/src/vespa/vespalib/stllike/hash_fun.h
+++ b/vespalib/src/vespa/vespalib/stllike/hash_fun.h
@@ -69,10 +69,10 @@ size_t hashValue(const char *str) noexcept;
size_t hashValue(const void *str, size_t sz) noexcept;
struct hash_strings {
- size_t operator() (const vespalib::string & arg) const noexcept { return hashValue(arg.c_str()); }
+ size_t operator() (const vespalib::string & arg) const noexcept { return hashValue(arg.data(), arg.size()); }
size_t operator() (vespalib::stringref arg) const noexcept { return hashValue(arg.data(), arg.size()); }
size_t operator() (const char * arg) const noexcept { return hashValue(arg); }
- size_t operator() (const std::string& arg) const noexcept { return hashValue(arg.c_str()); }
+ size_t operator() (const std::string& arg) const noexcept { return hashValue(arg.data(), arg.size()); }
};
template<> struct hash<const char *> : hash_strings { };
diff --git a/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp b/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
index 82d7b9ea07b..c139b1391e0 100644
--- a/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
+++ b/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
@@ -16,25 +16,29 @@ RequiredPeerCredential required_san_uri(vespalib::stringref pattern) {
return {RequiredPeerCredential::Field::SAN_URI, pattern};
}
-AssumedRoles assumed_roles(const std::vector<string>& roles) {
- // TODO fix hash_set iterator range ctor to make this a one-liner
- AssumedRoles::RoleSet role_set;
- for (const auto& role : roles) {
- role_set.insert(role);
- }
- return AssumedRoles::make_for_roles(std::move(role_set));
-}
-
PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds) {
return PeerPolicy(std::move(creds));
}
-PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds, AssumedRoles roles) {
- return {std::move(creds), std::move(roles)};
+PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds, CapabilitySet capabilities) {
+ return {std::move(creds), std::move(capabilities)};
}
AuthorizedPeers authorized_peers(std::vector<PeerPolicy> peer_policies) {
return AuthorizedPeers(std::move(peer_policies));
}
+Capability cap_1() {
+ return Capability::content_search_api();
+}
+Capability cap_2() {
+ return Capability::content_storage_api();
+}
+Capability cap_3() {
+ return Capability::content_document_api();
+}
+Capability cap_4() {
+ return Capability::slobrok_api();
+}
+
}
diff --git a/vespalib/src/vespa/vespalib/test/peer_policy_utils.h b/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
index 72e9fde20de..5c6a97cc2c3 100644
--- a/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
+++ b/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
@@ -2,15 +2,20 @@
#pragma once
#include <vespa/vespalib/net/tls/peer_policies.h>
+#include <vespa/vespalib/net/tls/capability_set.h>
namespace vespalib::net::tls {
RequiredPeerCredential required_cn(vespalib::stringref pattern);
RequiredPeerCredential required_san_dns(vespalib::stringref pattern);
RequiredPeerCredential required_san_uri(vespalib::stringref pattern);
-AssumedRoles assumed_roles(const std::vector<string>& roles);
PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds);
-PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds, AssumedRoles roles);
+PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds, CapabilitySet capabilities);
AuthorizedPeers authorized_peers(std::vector<PeerPolicy> peer_policies);
+// Some shortcuts for valid capabilities:
+Capability cap_1();
+Capability cap_2();
+Capability cap_3();
+Capability cap_4();
}
diff --git a/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.cpp b/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.cpp
index 6db97ff0761..e7f43de8f92 100644
--- a/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.cpp
+++ b/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.cpp
@@ -6,7 +6,7 @@ namespace vespalib {
//-----------------------------------------------------------------------------
-AdaptiveSequencedExecutor::Strand::Strand()
+AdaptiveSequencedExecutor::Strand::Strand() noexcept
: state(State::IDLE),
queue()
{
diff --git a/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.h b/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.h
index fbebf8b4e4c..fee9b8a61f8 100644
--- a/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.h
+++ b/vespalib/src/vespa/vespalib/util/adaptive_sequenced_executor.h
@@ -80,7 +80,7 @@ private:
enum class State { IDLE, WAITING, ACTIVE };
State state;
ArrayQueue<TaggedTask> queue;
- Strand();
+ Strand() noexcept;
~Strand();
};
diff --git a/vespalib/src/vespa/vespalib/util/signalhandler.cpp b/vespalib/src/vespa/vespalib/util/signalhandler.cpp
index b00fe1718c9..68368269c59 100644
--- a/vespalib/src/vespa/vespalib/util/signalhandler.cpp
+++ b/vespalib/src/vespa/vespalib/util/signalhandler.cpp
@@ -2,6 +2,9 @@
#include "signalhandler.h"
#include "backtrace.h"
+#ifdef __APPLE__
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#endif
#include <boost/stacktrace/safe_dump_to.hpp> // Header-only dependency
#include <boost/stacktrace/frame.hpp>
#include <array>