Skip to content

Commit 6f39b89

Browse files
Calle Wilundxemul
Calle Wilund
authored andcommitted
tls: Add optional builder + future-wait to cert reload callback + expose rebuild
Refs #2513 Adds a more advanced callback type, which takes both actual reloading builder as argument (into which new files are loaded), and allows proper future-wait in callback. Exposes certificates rebuilding (via builder) to allow "manual", quick, reload of certs. The point of these seemingly small changes is to allow client code to, for example, limit actual reloadable_certs (and by extension inotify watches) to shard 0 (or whatever), and simply use this as a trigger for manual reload of other shards. Note: we cannot do any magical "shard-0-only" file monitor in the objects themselves, not without making the certs/builders sharded or similarly stored (which contradict the general design of light objects, copyable between shards etc). But with this, a calling app in which certs _are_ held in sharded manners, we can fairly easily delegate non-shard-0 ops in a way that fits that topology. Note: a builder can be _called_ from any shard (as long as it is safe in its originating shard), but the objects returned are only valid on the current shard. Similarly, it is safe to share the reloading builder across shards _in the callback_, since rebuilding is blocked for the duration of the call. Closes #2573
1 parent 7385aed commit 6f39b89

File tree

3 files changed

+167
-13
lines changed

3 files changed

+167
-13
lines changed

include/seastar/net/tls.hh

+10-2
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,10 @@ namespace tls {
283283
};
284284

285285
class reloadable_credentials_base;
286+
class credentials_builder;
286287

287288
using reload_callback = std::function<void(const std::unordered_set<sstring>&, std::exception_ptr)>;
289+
using reload_callback_ex = std::function<future<>(const credentials_builder&, const std::unordered_set<sstring>&, std::exception_ptr)>;
288290

289291
/**
290292
* Intentionally "primitive", and more importantly, copyable
@@ -320,10 +322,16 @@ namespace tls {
320322
shared_ptr<certificate_credentials> build_certificate_credentials() const;
321323
shared_ptr<server_credentials> build_server_credentials() const;
322324

325+
void rebuild(certificate_credentials&) const;
326+
void rebuild(server_credentials&) const;
327+
323328
// same as above, but any files used for certs/keys etc will be watched
324329
// for modification and reloaded if changed
325-
future<shared_ptr<certificate_credentials>> build_reloadable_certificate_credentials(reload_callback = {}, std::optional<std::chrono::milliseconds> tolerance = {}) const;
326-
future<shared_ptr<server_credentials>> build_reloadable_server_credentials(reload_callback = {}, std::optional<std::chrono::milliseconds> tolerance = {}) const;
330+
future<shared_ptr<certificate_credentials>> build_reloadable_certificate_credentials(reload_callback_ex = {}, std::optional<std::chrono::milliseconds> tolerance = {}) const;
331+
future<shared_ptr<server_credentials>> build_reloadable_server_credentials(reload_callback_ex = {}, std::optional<std::chrono::milliseconds> tolerance = {}) const;
332+
333+
future<shared_ptr<certificate_credentials>> build_reloadable_certificate_credentials(reload_callback, std::optional<std::chrono::milliseconds> tolerance = {}) const;
334+
future<shared_ptr<server_credentials>> build_reloadable_server_credentials(reload_callback, std::optional<std::chrono::milliseconds> tolerance = {}) const;
327335
private:
328336
friend class reloadable_credentials_base;
329337

src/net/tls.cc

+33-11
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ class tls::reloadable_credentials_base {
796796
public:
797797
using time_point = std::chrono::system_clock::time_point;
798798

799-
reloading_builder(credentials_builder b, reload_callback cb, reloadable_credentials_base* creds, delay_type delay)
799+
reloading_builder(credentials_builder b, reload_callback_ex cb, reloadable_credentials_base* creds, delay_type delay)
800800
: credentials_builder(std::move(b))
801801
, _cb(std::move(cb))
802802
, _creds(creds)
@@ -955,7 +955,7 @@ class tls::reloadable_credentials_base {
955955
}
956956
void do_callback(std::exception_ptr ep = {}) {
957957
if (_cb && !_files.empty()) {
958-
_cb(boost::copy_range<std::unordered_set<sstring>>(_files | boost::adaptors::map_keys), std::move(ep));
958+
_cb(*this, boost::copy_range<std::unordered_set<sstring>>(_files | boost::adaptors::map_keys), std::move(ep)).get();
959959
}
960960
}
961961
// called from seastar::thread
@@ -988,7 +988,7 @@ class tls::reloadable_credentials_base {
988988
});
989989
}
990990

991-
reload_callback _cb;
991+
reload_callback_ex _cb;
992992
reloadable_credentials_base* _creds;
993993
fsnotifier _fsn;
994994
std::unordered_map<fsnotifier::watch_token, std::pair<fsnotifier::watch, sstring>> _watches;
@@ -997,7 +997,7 @@ class tls::reloadable_credentials_base {
997997
timer<> _timer;
998998
delay_type _delay;
999999
};
1000-
reloadable_credentials_base(credentials_builder builder, reload_callback cb, delay_type delay = default_tolerance)
1000+
reloadable_credentials_base(credentials_builder builder, reload_callback_ex cb, delay_type delay = default_tolerance)
10011001
: _builder(seastar::make_shared<reloading_builder>(std::move(builder), std::move(cb), this, delay))
10021002
{
10031003
_builder->start();
@@ -1016,7 +1016,7 @@ class tls::reloadable_credentials_base {
10161016
template<typename Base>
10171017
class tls::reloadable_credentials : public Base, public tls::reloadable_credentials_base {
10181018
public:
1019-
reloadable_credentials(credentials_builder builder, reload_callback cb, Base b, delay_type delay = default_tolerance)
1019+
reloadable_credentials(credentials_builder builder, reload_callback_ex cb, Base b, delay_type delay = default_tolerance)
10201020
: Base(std::move(b))
10211021
, tls::reloadable_credentials_base(std::move(builder), std::move(cb), delay)
10221022
{}
@@ -1025,30 +1025,52 @@ class tls::reloadable_credentials : public Base, public tls::reloadable_credenti
10251025

10261026
template<>
10271027
void tls::reloadable_credentials<tls::certificate_credentials>::rebuild(const credentials_builder& builder) {
1028-
auto tmp = builder.build_certificate_credentials();
1029-
this->_impl = std::move(tmp->_impl);
1028+
builder.rebuild(*this);
10301029
}
10311030

10321031
template<>
10331032
void tls::reloadable_credentials<tls::server_credentials>::rebuild(const credentials_builder& builder) {
1034-
auto tmp = builder.build_server_credentials();
1035-
this->_impl = std::move(tmp->_impl);
1033+
builder.rebuild(*this);
10361034
}
10371035

1038-
future<shared_ptr<tls::certificate_credentials>> tls::credentials_builder::build_reloadable_certificate_credentials(reload_callback cb, std::optional<std::chrono::milliseconds> tolerance) const {
1036+
void tls::credentials_builder::rebuild(certificate_credentials& creds) const {
1037+
auto tmp = build_certificate_credentials();
1038+
creds._impl = std::move(tmp->_impl);
1039+
}
1040+
1041+
void tls::credentials_builder::rebuild(server_credentials& creds) const {
1042+
auto tmp = build_server_credentials();
1043+
creds._impl = std::move(tmp->_impl);
1044+
}
1045+
1046+
future<shared_ptr<tls::certificate_credentials>> tls::credentials_builder::build_reloadable_certificate_credentials(reload_callback_ex cb, std::optional<std::chrono::milliseconds> tolerance) const {
10391047
auto creds = seastar::make_shared<reloadable_credentials<tls::certificate_credentials>>(*this, std::move(cb), std::move(*build_certificate_credentials()), tolerance.value_or(reloadable_credentials_base::default_tolerance));
10401048
return creds->init().then([creds] {
10411049
return make_ready_future<shared_ptr<tls::certificate_credentials>>(creds);
10421050
});
10431051
}
10441052

1045-
future<shared_ptr<tls::server_credentials>> tls::credentials_builder::build_reloadable_server_credentials(reload_callback cb, std::optional<std::chrono::milliseconds> tolerance) const {
1053+
future<shared_ptr<tls::server_credentials>> tls::credentials_builder::build_reloadable_server_credentials(reload_callback_ex cb, std::optional<std::chrono::milliseconds> tolerance) const {
10461054
auto creds = seastar::make_shared<reloadable_credentials<tls::server_credentials>>(*this, std::move(cb), std::move(*build_server_credentials()), tolerance.value_or(reloadable_credentials_base::default_tolerance));
10471055
return creds->init().then([creds] {
10481056
return make_ready_future<shared_ptr<tls::server_credentials>>(creds);
10491057
});
10501058
}
10511059

1060+
future<shared_ptr<tls::certificate_credentials>> tls::credentials_builder::build_reloadable_certificate_credentials(reload_callback cb, std::optional<std::chrono::milliseconds> tolerance) const {
1061+
return build_reloadable_certificate_credentials([cb = std::move(cb)](const credentials_builder&, const std::unordered_set<sstring>& files, std::exception_ptr p) {
1062+
cb(files, p);
1063+
return make_ready_future<>();
1064+
}, tolerance);
1065+
}
1066+
1067+
future<shared_ptr<tls::server_credentials>> tls::credentials_builder::build_reloadable_server_credentials(reload_callback cb, std::optional<std::chrono::milliseconds> tolerance) const {
1068+
return build_reloadable_server_credentials([cb = std::move(cb)](const credentials_builder&, const std::unordered_set<sstring>& files, std::exception_ptr p) {
1069+
cb(files, p);
1070+
return make_ready_future<>();
1071+
}, tolerance);
1072+
}
1073+
10521074
namespace tls {
10531075

10541076
/**

tests/unit/tls_test.cc

+124
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <seastar/net/inet_address.hh>
4141
#include <seastar/testing/test_case.hh>
4242
#include <seastar/testing/thread_test_case.hh>
43+
#include <seastar/util/defer.hh>
4344

4445
#include <boost/dll.hpp>
4546

@@ -1595,3 +1596,126 @@ SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets) {
15951596
}
15961597

15971598
}
1599+
1600+
SEASTAR_THREAD_TEST_CASE(test_reload_certificates_with_only_shard0_notify) {
1601+
tmpdir tmp;
1602+
1603+
namespace fs = std::filesystem;
1604+
1605+
// copy the wrong certs. We don't trust these
1606+
// blocking calls, but this is a test and seastar does not have a copy
1607+
// util and I am lazy...
1608+
fs::copy_file(certfile("other.crt"), tmp.path() / "test.crt");
1609+
fs::copy_file(certfile("other.key"), tmp.path() / "test.key");
1610+
1611+
auto cert = (tmp.path() / "test.crt").native();
1612+
auto key = (tmp.path() / "test.key").native();
1613+
promise<> p;
1614+
1615+
tls::credentials_builder b;
1616+
b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
1617+
b.set_dh_level();
1618+
1619+
auto certs = b.build_server_credentials();
1620+
1621+
auto shard_1_certs = smp::submit_to(1, [&]() -> future<shared_ptr<tls::server_credentials>> {
1622+
co_return co_await b.build_reloadable_server_credentials([&, changed = std::unordered_set<sstring>{}](const tls::credentials_builder& builder, const std::unordered_set<sstring>& files, std::exception_ptr ep) mutable -> future<> {
1623+
if (ep) {
1624+
co_return;
1625+
}
1626+
changed.insert(files.begin(), files.end());
1627+
if (changed.count(cert) && changed.count(key)) {
1628+
// shard one certs are not reloadable. We issue a reload of them from shard 0
1629+
// - to save inotify instances.
1630+
co_await smp::submit_to(0, [&] {
1631+
builder.rebuild(*certs);
1632+
p.set_value();
1633+
});
1634+
}
1635+
});
1636+
}).get();
1637+
1638+
auto def = defer([&]() noexcept {
1639+
try {
1640+
smp::submit_to(1, [&] {
1641+
shard_1_certs = nullptr;
1642+
}).get();
1643+
} catch (...) {}
1644+
});
1645+
1646+
::listen_options opts;
1647+
opts.reuse_address = true;
1648+
auto addr = ::make_ipv4_address( {0x7f000001, 4712});
1649+
auto server = tls::listen(certs, addr, opts);
1650+
1651+
tls::credentials_builder b2;
1652+
b2.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
1653+
1654+
{
1655+
auto sa = server.accept();
1656+
auto c = tls::connect(b2.build_certificate_credentials(), addr).get();
1657+
auto s = sa.get();
1658+
auto in = s.connection.input();
1659+
1660+
output_stream<char> out(c.output().detach(), 4096);
1661+
1662+
try {
1663+
out.write("apa").get();
1664+
auto f = out.flush();
1665+
auto f2 = in.read();
1666+
1667+
try {
1668+
f.get();
1669+
BOOST_FAIL("should not reach");
1670+
} catch (tls::verification_error&) {
1671+
// ok
1672+
}
1673+
try {
1674+
out.close().get();
1675+
} catch (...) {
1676+
}
1677+
1678+
try {
1679+
f2.get();
1680+
BOOST_FAIL("should not reach");
1681+
} catch (...) {
1682+
// ok
1683+
}
1684+
try {
1685+
in.close().get();
1686+
} catch (...) {
1687+
}
1688+
} catch (tls::verification_error&) {
1689+
// ok
1690+
}
1691+
}
1692+
1693+
// copy the right (trusted) certs over the old ones.
1694+
fs::copy_file(certfile("test.crt"), tmp.path() / "test0.crt");
1695+
fs::copy_file(certfile("test.key"), tmp.path() / "test0.key");
1696+
1697+
rename_file((tmp.path() / "test0.crt").native(), (tmp.path() / "test.crt").native()).get();
1698+
rename_file((tmp.path() / "test0.key").native(), (tmp.path() / "test.key").native()).get();
1699+
1700+
p.get_future().get();
1701+
1702+
// now it should work
1703+
{
1704+
auto sa = server.accept();
1705+
auto c = tls::connect(b2.build_certificate_credentials(), addr).get();
1706+
auto s = sa.get();
1707+
auto in = s.connection.input();
1708+
1709+
output_stream<char> out(c.output().detach(), 4096);
1710+
1711+
out.write("apa").get();
1712+
auto f = out.flush();
1713+
auto buf = in.read().get();
1714+
f.get();
1715+
out.close().get();
1716+
in.read().get(); // ignore - just want eof
1717+
in.close().get();
1718+
1719+
BOOST_CHECK_EQUAL(sstring(buf.begin(), buf.end()), "apa");
1720+
}
1721+
}

0 commit comments

Comments
 (0)