// vim: ts=8 sw=2 sts=2 expandtab ft=cpp
#include <atomic>
+#include <chrono>
#include <ctime>
#include <iomanip>
#include <list>
ceph::timespan request_timeout = std::chrono::milliseconds(REQUEST_TIMEOUT);
size_t header_limit = 16384;
#ifdef WITH_RADOSGW_BEAST_OPENSSL
- boost::optional<ssl::context> ssl_context;
+#ifdef __cpp_lib_atomic_shared_ptr
+ std::atomic<std::shared_ptr<ssl::context>> ssl_context;
+#else
+ std::shared_ptr<ssl::context> ssl_context;
+#endif
+ boost::asio::steady_timer ssl_reload_timer;
int get_config_key_val(string name,
const string& type,
bufferlist *pbl);
- int ssl_set_private_key(const string& name, bool is_ssl_cert);
- int ssl_set_certificate_chain(const string& name);
- int init_ssl();
+ int ssl_set_private_key(ssl::context& ctx, const string& name, bool is_ssl_cert);
+ int ssl_set_certificate_chain(ssl::context& ctx, const string& name);
+ int ssl_init();
+ int ssl_reload();
+ int ssl_reload_timer_start();
#endif
SharedMutex pause_mutex;
std::unique_ptr<rgw::dmclock::Scheduler> scheduler;
dmc::SchedulerCtx& sched_ctx,
boost::asio::io_context& context)
: env(env), conf(conf), context(context),
+#ifdef WITH_RADOSGW_BEAST_OPENSSL
+ ssl_reload_timer(context),
+#endif
pause_mutex(context.get_executor()),
backoff(context)
{
}
#ifdef WITH_RADOSGW_BEAST_OPENSSL
- int r = init_ssl();
+ int r = ssl_init();
if (r < 0) {
return r;
}
+#ifdef __cpp_lib_atomic_shared_ptr
+ const auto ssl_ctx = ssl_context.load(std::memory_order_acquire);
+#else
+ const auto ssl_ctx = std::atomic_load_explicit(&ssl_context, std::memory_order_acquire);
+#endif
+ if (ssl_ctx != nullptr) {
+ r = ssl_reload_timer_start();
+ if (r < 0) {
+ return r;
+ }
+ }
#endif
// parse endpoints
return 0;
}
-int AsioFrontend::ssl_set_private_key(const string& name, bool is_ssl_certificate)
+int AsioFrontend::ssl_set_private_key(ssl::context& ssl_ctx, const string& name, bool is_ssl_certificate)
{
boost::system::error_code ec;
if (!boost::algorithm::starts_with(name, config_val_prefix)) {
- ssl_context->use_private_key_file(name, ssl::context::pem, ec);
+ ssl_ctx.use_private_key_file(name, ssl::context::pem, ec);
} else {
bufferlist bl;
int r = get_config_key_val(name.substr(config_val_prefix.size()),
if (r < 0) {
return r;
}
- ssl_context->use_private_key(boost::asio::buffer(bl.c_str(), bl.length()),
+ ssl_ctx.use_private_key(boost::asio::buffer(bl.c_str(), bl.length()),
ssl::context::pem, ec);
}
return 0;
}
-int AsioFrontend::ssl_set_certificate_chain(const string& name)
+int AsioFrontend::ssl_set_certificate_chain(ssl::context& ssl_ctx, const string& name)
{
boost::system::error_code ec;
if (!boost::algorithm::starts_with(name, config_val_prefix)) {
- ssl_context->use_certificate_chain_file(name, ec);
+ ssl_ctx.use_certificate_chain_file(name, ec);
} else {
bufferlist bl;
int r = get_config_key_val(name.substr(config_val_prefix.size()),
if (r < 0) {
return r;
}
- ssl_context->use_certificate_chain(boost::asio::buffer(bl.c_str(), bl.length()),
+ ssl_ctx.use_certificate_chain(boost::asio::buffer(bl.c_str(), bl.length()),
ec);
}
return 0;
}
-int AsioFrontend::init_ssl()
+int AsioFrontend::ssl_init()
{
boost::system::error_code ec;
auto& config = conf->get_config_map();
- // ssl configuration
- std::optional<string> cert = conf->get_val("ssl_certificate");
- if (cert) {
- // only initialize the ssl context if it's going to be used
- ssl_context = boost::in_place(ssl::context::tls);
+ auto ports = config.equal_range("ssl_port");
+ auto endpoints = config.equal_range("ssl_endpoint");
+
+ /*
+ * don't try to config certificate if frontend isn't configured for ssl
+ */
+ if (ports.first == ports.second &&
+ endpoints.first == endpoints.second) {
+ return 0;
}
- std::optional<string> key = conf->get_val("ssl_private_key");
- bool have_cert = false;
+ int r = ssl_reload();
+ if (r < 0) {
+ return r;
+ }
+
+ // parse ssl endpoints
+ for (auto i = ports.first; i != ports.second; ++i) {
+ auto port = parse_port(i->second.c_str(), ec);
+ if (ec) {
+ lderr(ctx()) << "failed to parse ssl_port=" << i->second << dendl;
+ return -ec.value();
+ }
+ listeners.emplace_back(context);
+ listeners.back().endpoint.port(port);
+ listeners.back().use_ssl = true;
+
+ listeners.emplace_back(context);
+ listeners.back().endpoint = tcp::endpoint(tcp::v6(), port);
+ listeners.back().use_ssl = true;
+ }
+ for (auto i = endpoints.first; i != endpoints.second; ++i) {
+ auto endpoint = parse_endpoint(i->second, 443, ec);
+ if (ec) {
+ lderr(ctx()) << "failed to parse ssl_endpoint=" << i->second << dendl;
+ return -ec.value();
+ }
+ listeners.emplace_back(context);
+ listeners.back().endpoint = endpoint;
+ listeners.back().use_ssl = true;
+ }
+
+ return 0;
+}
+
+int AsioFrontend::ssl_reload() {
+ const auto ssl_ctx = std::make_shared<ssl::context>(ssl::context(ssl::context::tls));
+
+ std::optional<string> cert = conf->get_val("ssl_certificate");
+ std::optional<string> key = conf->get_val("ssl_private_key");
if (key && !cert) {
lderr(ctx()) << "no ssl_certificate configured for ssl_private_key" << dendl;
return -EINVAL;
if (options) {
for (auto &option : ceph::split(*options, ":")) {
if (option == "default_workarounds") {
- ssl_context->set_options(ssl::context::default_workarounds);
+ ssl_ctx->set_options(ssl::context::default_workarounds);
} else if (option == "no_compression") {
- ssl_context->set_options(ssl::context::no_compression);
+ ssl_ctx->set_options(ssl::context::no_compression);
} else if (option == "no_sslv2") {
- ssl_context->set_options(ssl::context::no_sslv2);
+ ssl_ctx->set_options(ssl::context::no_sslv2);
} else if (option == "no_sslv3") {
- ssl_context->set_options(ssl::context::no_sslv3);
+ ssl_ctx->set_options(ssl::context::no_sslv3);
} else if (option == "no_tlsv1") {
- ssl_context->set_options(ssl::context::no_tlsv1);
+ ssl_ctx->set_options(ssl::context::no_tlsv1);
} else if (option == "no_tlsv1_1") {
- ssl_context->set_options(ssl::context::no_tlsv1_1);
+ ssl_ctx->set_options(ssl::context::no_tlsv1_1);
} else if (option == "no_tlsv1_2") {
- ssl_context->set_options(ssl::context::no_tlsv1_2);
+ ssl_ctx->set_options(ssl::context::no_tlsv1_2);
} else if (option == "single_dh_use") {
- ssl_context->set_options(ssl::context::single_dh_use);
+ ssl_ctx->set_options(ssl::context::single_dh_use);
} else {
lderr(ctx()) << "ignoring unknown ssl option '" << option << "'" << dendl;
}
return -EINVAL;
}
- int r = SSL_CTX_set_cipher_list(ssl_context->native_handle(),
- ciphers->c_str());
+ int r = SSL_CTX_set_cipher_list(ssl_ctx->native_handle(), ciphers->c_str());
if (r == 0) {
lderr(ctx()) << "no cipher could be selected from ssl_ciphers: "
<< *ciphers << dendl;
}
}
- auto ports = config.equal_range("ssl_port");
- auto endpoints = config.equal_range("ssl_endpoint");
-
- /*
- * don't try to config certificate if frontend isn't configured for ssl
- */
- if (ports.first == ports.second &&
- endpoints.first == endpoints.second) {
- return 0;
- }
-
bool key_is_cert = false;
-
+ bool have_cert = false;
if (cert) {
if (!key) {
key = cert;
}
ExpandMetaVar emv(env.driver->get_zone());
-
cert = emv.process_str(*cert);
key = emv.process_str(*key);
- int r = ssl_set_private_key(*key, key_is_cert);
+ int r = ssl_set_private_key(*ssl_ctx, *key, key_is_cert);
bool have_private_key = (r >= 0);
if (r < 0) {
if (!key_is_cert) {
- r = ssl_set_private_key(*cert, true);
+ r = ssl_set_private_key(*ssl_ctx, *cert, true);
have_private_key = (r >= 0);
}
}
if (have_private_key) {
- int r = ssl_set_certificate_chain(*cert);
+ int r = ssl_set_certificate_chain(*ssl_ctx, *cert);
have_cert = (r >= 0);
}
}
- // parse ssl endpoints
- for (auto i = ports.first; i != ports.second; ++i) {
- if (!have_cert) {
- lderr(ctx()) << "no ssl_certificate configured for ssl_port" << dendl;
- return -EINVAL;
- }
- auto port = parse_port(i->second.c_str(), ec);
- if (ec) {
- lderr(ctx()) << "failed to parse ssl_port=" << i->second << dendl;
- return -ec.value();
- }
- listeners.emplace_back(context);
- listeners.back().endpoint.port(port);
- listeners.back().use_ssl = true;
+ if (!have_cert) {
+ lderr(ctx()) << "no ssl_certificate configured" << dendl;
+ return -EINVAL;
+ }
- listeners.emplace_back(context);
- listeners.back().endpoint = tcp::endpoint(tcp::v6(), port);
- listeners.back().use_ssl = true;
+#ifdef __cpp_lib_atomic_shared_ptr
+ ssl_context.store(ssl_ctx, std::memory_order_release);
+#else
+ std::atomic_store_explicit(&ssl_context, ssl_ctx, std::memory_order_release);
+#endif
+
+ return 0;
+}
+
+int AsioFrontend::ssl_reload_timer_start() {
+ const auto interval_str = conf->get_val("ssl_reload");
+ if (!interval_str) {
+ return 0;
}
- for (auto i = endpoints.first; i != endpoints.second; ++i) {
- if (!have_cert) {
- lderr(ctx()) << "no ssl_certificate configured for ssl_endpoint" << dendl;
- return -EINVAL;
- }
- auto endpoint = parse_endpoint(i->second, 443, ec);
+ const auto interval = ceph::parse<uint64_t>(*interval_str);
+ if (!interval) {
+ lderr(ctx()) << "failed to parse ssl_reload=" << *interval_str << dendl;
+ return -EINVAL;
+ };
+
+ if (*interval == 0) {
+ return 0;
+ }
+
+ ssl_reload_timer.expires_after(std::chrono::seconds(*interval));
+ ssl_reload_timer.async_wait([this](const boost::system::error_code &ec) {
if (ec) {
- lderr(ctx()) << "failed to parse ssl_endpoint=" << i->second << dendl;
- return -ec.value();
+ return;
}
- listeners.emplace_back(context);
- listeners.back().endpoint = endpoint;
- listeners.back().use_ssl = true;
- }
+
+ ldout(ctx(), 4) << "ssl reload triggered" << dendl;
+ if (ssl_reload() < 0) {
+ lderr(ctx()) << "ssl reload failed, continuing with existing context"
+ << dendl;
+ } else {
+ ldout(ctx(), 4) << "ssl reload successful" << dendl;
+ }
+
+ ssl_reload_timer_start();
+ });
+
return 0;
}
#endif // WITH_RADOSGW_BEAST_OPENSSL
{
boost::system::error_code ec;
stream.set_option(tcp::no_delay(l.use_nodelay), ec);
-
+
// spawn a coroutine to handle the connection
#ifdef WITH_RADOSGW_BEAST_OPENSSL
if (l.use_ssl) {
+#ifdef __cpp_lib_atomic_shared_ptr
+ const auto ssl_ctx = ssl_context.load(std::memory_order_acquire);
+#else
+ const auto ssl_ctx = std::atomic_load_excplicit(&ssl_context, std::memory_order_acquire);
+#endif
boost::asio::spawn(make_strand(context), std::allocator_arg, make_stack_allocator(),
- [this, s=std::move(stream)] (boost::asio::yield_context yield) mutable {
+ [this, s=std::move(stream), ssl_ctx] (boost::asio::yield_context yield) mutable {
auto conn = boost::intrusive_ptr{new Connection(std::move(s))};
auto c = connections.add(*conn);
// wrap the tcp stream in an ssl stream
- boost::asio::ssl::stream<tcp::socket&> stream{conn->socket, *ssl_context};
+ boost::asio::ssl::stream<tcp::socket&> stream{conn->socket, *ssl_ctx};
auto timeout = timeout_timer{context.get_executor(), request_timeout, conn};
// do ssl handshake
boost::system::error_code ec;
// signal cancellation of accept()
listener.signal.emit(boost::asio::cancellation_type::terminal);
}
+ ssl_reload_timer.cancel();
const bool graceful_stop{ g_ceph_context->_conf->rgw_graceful_stop };
if (graceful_stop) {