aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/tests/net
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-04-25 14:55:52 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-04-26 11:48:13 +0000
commite69532d73c6b5962e508097c559aecec514cb5fe (patch)
tree59b9b5e6c5bb56315caa3b0a94cb437140c602ab /vespalib/src/tests/net
parentf58b136d4fc80e8752dab4bfae70e4c029ccf63d (diff)
Initial foundations for C++ mTLS peer authz role constraints
Exposes the following information via the OpenSSL-backed CryptoCodec: * Credentials retrieved from authenticated peer certificate. * Union set of assumed roles from all peer authorization rules that matched the peer certificate. Note that this does not add parsing of any mTLS config file role fields, nor any FNET/FRT wiring required for RPC requests to be associated with a particular peer authz context. Syntax and semantics etc still pending.
Diffstat (limited to 'vespalib/src/tests/net')
-rw-r--r--vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp52
-rw-r--r--vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp46
2 files changed, 83 insertions, 15 deletions
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 45e9c92343e..e20cd30c597 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
@@ -535,40 +535,40 @@ struct CertFixture : Fixture {
CertFixture::~CertFixture() = default;
struct PrintingCertificateCallback : CertificateVerificationCallback {
- bool verify(const PeerCredentials& peer_creds) const override {
+ AuthorizationResult verify(const PeerCredentials& peer_creds) const override {
if (!peer_creds.common_name.empty()) {
fprintf(stderr, "Got a CN: %s\n", peer_creds.common_name.c_str());
}
for (auto& dns : peer_creds.dns_sans) {
fprintf(stderr, "Got a DNS SAN entry: %s\n", dns.c_str());
}
- return true;
+ return AuthorizationResult::make_authorized_for_all_roles();
}
};
// Single-use mock verifier
struct MockCertificateCallback : CertificateVerificationCallback {
mutable PeerCredentials creds; // only used in single thread testing context
- bool verify(const PeerCredentials& peer_creds) const override {
+ AuthorizationResult verify(const PeerCredentials& peer_creds) const override {
creds = peer_creds;
- return true;
+ return AuthorizationResult::make_authorized_for_all_roles();
}
};
struct AlwaysFailVerifyCallback : CertificateVerificationCallback {
- bool verify([[maybe_unused]] const PeerCredentials& peer_creds) const override {
+ AuthorizationResult verify([[maybe_unused]] const PeerCredentials& peer_creds) const override {
fprintf(stderr, "Rejecting certificate, none shall pass!\n");
- return false;
+ return AuthorizationResult::make_not_authorized();
}
};
struct ExceptionThrowingCallback : CertificateVerificationCallback {
- bool verify([[maybe_unused]] const PeerCredentials& peer_creds) const override {
+ AuthorizationResult verify([[maybe_unused]] const PeerCredentials& peer_creds) const override {
throw std::runtime_error("oh no what is going on");
}
};
-TEST_F("Certificate verification callback returning false breaks handshake", CertFixture) {
+TEST_F("Certificate verification callback returning unauthorized breaks handshake", CertFixture) {
auto ck = f.create_ca_issued_peer_cert({"hello.world.example.com"}, {});
f.reset_client_with_cert_opts(ck, std::make_shared<PrintingCertificateCallback>());
@@ -602,8 +602,40 @@ TEST_F("Certificate verification callback observes CN, DNS SANs and URI SANs", C
ASSERT_EQUAL(2u, creds.dns_sans.size());
EXPECT_EQUAL("crash.wile.example.com", creds.dns_sans[0]);
EXPECT_EQUAL("burn.wile.example.com", creds.dns_sans[1]);
- ASSERT_EQUAL(1u, server_cb->creds.uri_sans.size());
- EXPECT_EQUAL("foo://bar.baz/zoid", server_cb->creds.uri_sans[0]);
+ ASSERT_EQUAL(1u, creds.uri_sans.size());
+ EXPECT_EQUAL("foo://bar.baz/zoid", creds.uri_sans[0]);
+}
+
+TEST_F("Peer credentials are propagated to CryptoCodec", CertFixture) {
+ auto cli_cert = f.create_ca_issued_peer_cert(
+ {{"rockets.wile.example.com"}},
+ {{"DNS:crash.wile.example.com"}, {"DNS:burn.wile.example.com"},
+ {"URI:foo://bar.baz/zoid"}});
+ auto serv_cert = f.create_ca_issued_peer_cert(
+ {{"birdseed.roadrunner.example.com"}},
+ {{"DNS:fake.tunnel.example.com"}});
+ f.reset_client_with_cert_opts(cli_cert, std::make_shared<PrintingCertificateCallback>());
+ auto server_cb = std::make_shared<MockCertificateCallback>();
+ f.reset_server_with_cert_opts(serv_cert, server_cb);
+ ASSERT_TRUE(f.handshake());
+
+ auto& client_creds = f.server->peer_credentials();
+ auto& server_creds = f.client->peer_credentials();
+
+ fprintf(stderr, "Client credentials (observed by server): %s\n", to_string(client_creds).c_str());
+ fprintf(stderr, "Server credentials (observed by client): %s\n", to_string(server_creds).c_str());
+
+ EXPECT_EQUAL("rockets.wile.example.com", client_creds.common_name);
+ ASSERT_EQUAL(2u, client_creds.dns_sans.size());
+ EXPECT_EQUAL("crash.wile.example.com", client_creds.dns_sans[0]);
+ EXPECT_EQUAL("burn.wile.example.com", client_creds.dns_sans[1]);
+ ASSERT_EQUAL(1u, client_creds.uri_sans.size());
+ EXPECT_EQUAL("foo://bar.baz/zoid", client_creds.uri_sans[0]);
+
+ EXPECT_EQUAL("birdseed.roadrunner.example.com", server_creds.common_name);
+ ASSERT_EQUAL(1u, server_creds.dns_sans.size());
+ EXPECT_EQUAL("fake.tunnel.example.com", server_creds.dns_sans[0]);
+ ASSERT_EQUAL(0u, server_creds.uri_sans.size());
}
TEST_F("Last occurring CN is given to verification callback if multiple CNs are present", CertFixture) {
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 812d06868fd..b1414475d83 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
@@ -124,7 +124,12 @@ PeerCredentials creds_with_cn(vespalib::stringref cn) {
bool verify(AuthorizedPeers authorized_peers, const PeerCredentials& peer_creds) {
auto verifier = create_verify_callback_from(std::move(authorized_peers));
- return verifier->verify(peer_creds);
+ return verifier->verify(peer_creds).success();
+}
+
+AssumedRoles verify_roles(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();
}
TEST("Default-constructed AuthorizedPeers does not allow all authenticated peers") {
@@ -137,6 +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") {
+ auto allow_all = AuthorizedPeers::allow_all_authenticated();
+ EXPECT_EQUAL(verify_roles(allow_all, creds_with_dns_sans({{"anything.goes"}})), AssumedRoles::make_wildcard_role());
+}
+
+TEST("policy without explicit role set implicitly returns wildcard role 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());
+}
+
TEST("Non-empty policies do not allow all authenticated peers") {
auto allow_not_all = authorized_peers({policy_with({required_san_dns("hello.world")})});
EXPECT_FALSE(allow_not_all.allows_all_authenticated());
@@ -231,10 +246,11 @@ struct MultiPolicyMatchFixture {
};
MultiPolicyMatchFixture::MultiPolicyMatchFixture()
- : authorized(authorized_peers({policy_with({required_san_dns("hello.world")}),
- policy_with({required_san_dns("foo.bar")}),
- policy_with({required_san_dns("zoid.berg")}),
- policy_with({required_san_uri("zoid://be.rg/")})}))
+ : 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"}))}))
{}
MultiPolicyMatchFixture::~MultiPolicyMatchFixture() = default;
@@ -246,14 +262,34 @@ 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("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("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("CN requirement without glob pattern is matched as exact string") {
auto authorized = authorized_peers({policy_with({required_cn("hello.world")})});
EXPECT_TRUE(verify(authorized, creds_with_cn("hello.world")));