From 0e80bc48cd72d294132164500cf5867a3af63c3f Mon Sep 17 00:00:00 2001 From: Marcel Lauhoff Date: Wed, 10 Jun 2026 10:08:08 +0200 Subject: [PATCH 01/28] release: record SSE-KMS Secrets Cache backport metadata --- .../019eb08c-159f-7a78-b689-5d37a7d343d0.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 release-management/backports/019eb08c-159f-7a78-b689-5d37a7d343d0.md diff --git a/release-management/backports/019eb08c-159f-7a78-b689-5d37a7d343d0.md b/release-management/backports/019eb08c-159f-7a78-b689-5d37a7d343d0.md new file mode 100644 index 000000000000..03299d70ba38 --- /dev/null +++ b/release-management/backports/019eb08c-159f-7a78-b689-5d37a7d343d0.md @@ -0,0 +1,49 @@ +--- +id: 019eb08c-159f-7a78-b689-5d37a7d343d0 +title: KMS Cache +component: rgw +category: feature +provenance: + type: upstream_pr + upstream_prs: + - https://github.com/ceph/ceph/pull/61256 + upstream_pr_state: merged-main + upstream_commits: + - 56253c4ae02ecffcf878f5046839991f3e7b0d0e + upstream_trackers: + - tracker.ceph.com/issues/68524 +justification: + type: customer + refs: + - cobaltcore-dev/cloud-storage#463 + - https://github.com/cobaltcore-dev/cloud-storage/issues/125 +risk: + blast: data-loss + conflict: substantive + coverage: partial +--- + +## Justification (public) + +Adds a secure in-memory cache for SSE-KMS encryption keys retrieved from Barbican (or other KMS backends), eliminating redundant round-trips on repeated access to the same encrypted objects. Uses a new SIEVE-based eviction cache (WebCache) and stores decrypted secrets in the Linux kernel keyring. +Please refer to the Ceph documentation on how to configure this feature. + +## Justification (internal) + +Customer roadmap feature. + +## Risk notes + +- TTLs mean that a deleted or rotated Barbican key won't be immediately reflected. +- New async primitives (async::call_once) and the WebCache are significant new library code shipping for the first time. +- Cache size tuning and system wide keyring quota adjustment needed for production workloads (refer to the docs). + +## Behavior change + +Repeated SSE-KMS access no longer hits the KMS backend on every +request; decrypted secrets are cached in-memory and in the Linux +kernel keyring. KMS-cache is ON out of the box. + +## Upgrade notes + +Feature flag: rgw_crypt_s3_kms_cache_enabled. Set false to disable caching entirely and revert to per-request KMS lookups. From 1a65d4a7f638fa160ce96be648d19998b533a4ca Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Mon, 24 Mar 2025 12:50:16 -0400 Subject: [PATCH 02/28] common/async: yield_waiter overloads for unique_lock if async_wait() can race with complete() across threads, the yield_waiter's handler_state needs to be protected by a mutex. add an async_wait() overload for unique_lock that behaves like condition_variable::wait(): the lock is released immediately before suspending, and reacquired immediately before calling its completion handler Signed-off-by: Casey Bodley (cherry picked from commit 604fad5692c61d23a22e0512b2b83c1e3253b2aa) --- src/common/async/yield_waiter.h | 106 ++++++++++++++-- src/test/common/test_async_yield_waiter.cc | 135 +++++++++++++++++++++ 2 files changed, 230 insertions(+), 11 deletions(-) diff --git a/src/common/async/yield_waiter.h b/src/common/async/yield_waiter.h index 9c14d9bafe4b..401e189c7e75 100644 --- a/src/common/async/yield_waiter.h +++ b/src/common/async/yield_waiter.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -25,6 +26,29 @@ namespace ceph::async { +namespace detail { + +// handler wrapper that reacquires a lock immediately before completion +template +struct lock_handler { + Handler handler; + BasicLockable* lock = nullptr; + + template + void operator()(Args&& ...args) { + if (lock) { + lock->lock(); + } + std::move(handler)(std::forward(args)...); + } +}; + +// deduction guide required by windows? +template +lock_handler(Handler&&, BasicLockable*) -> lock_handler; + +} // namespace detail + /// Captures a yield_context handler for deferred completion or cancellation. template class yield_waiter { @@ -52,7 +76,25 @@ class yield_waiter { if (slot.is_connected()) { slot.template emplace(this); } - state.emplace(std::move(h)); + constexpr std::unique_lock* lock = nullptr; + state.emplace(std::move(h), lock); + }, token); + } + + /// Suspends the given yield_context until the captured handler is invoked + /// via complete() or cancel(). The given lock is released immediately before + /// it suspends and reacquired immediately after it resumes. + template + auto async_wait(std::unique_lock& lock, CompletionToken&& token) + { + return boost::asio::async_initiate( + [this, &lock] (handler_type h) { + auto slot = get_associated_cancellation_slot(h); + if (slot.is_connected()) { + slot.template emplace(this); + } + state.emplace(std::move(h), &lock); + lock.unlock(); // unlock before suspend }, token); } @@ -61,8 +103,10 @@ class yield_waiter { { auto s = std::move(*state); state.reset(); - auto h = boost::asio::append(std::move(s.handler), ec, std::move(value)); - boost::asio::dispatch(std::move(h)); + boost::asio::dispatch( + boost::asio::append( + detail::lock_handler{std::move(s.handler), s.lock}, + ec, std::move(value))); } /// Destroy the completion handler. @@ -80,10 +124,10 @@ class yield_waiter { struct handler_state { handler_type handler; work_guard work; + std::unique_lock* lock = nullptr; - explicit handler_state(handler_type&& h) - : handler(std::move(h)), - work(make_work_guard(handler)) + handler_state(handler_type&& h, std::unique_lock* lock) + : handler(std::move(h)), work(make_work_guard(handler)), lock(lock) {} }; std::optional state; @@ -134,7 +178,25 @@ class yield_waiter { if (slot.is_connected()) { slot.template emplace(this); } - state.emplace(std::move(h)); + constexpr std::unique_lock* lock = nullptr; + state.emplace(std::move(h), lock); + }, token); + } + + /// Suspends the given yield_context until the captured handler is invoked + /// via complete() or cancel(). The given lock is released immediately before + /// it suspends and reacquired immediately after it resumes. + template + auto async_wait(std::unique_lock& lock, CompletionToken&& token) + { + return boost::asio::async_initiate( + [this, &lock] (handler_type h) { + auto slot = get_associated_cancellation_slot(h); + if (slot.is_connected()) { + slot.template emplace(this); + } + state.emplace(std::move(h), &lock); + lock.unlock(); // unlock before suspend }, token); } @@ -143,7 +205,9 @@ class yield_waiter { { auto s = std::move(*state); state.reset(); - boost::asio::dispatch(boost::asio::append(std::move(s.handler), ec)); + boost::asio::dispatch( + boost::asio::append( + detail::lock_handler{std::move(s.handler), s.lock}, ec)); } /// Destroy the completion handler. @@ -161,10 +225,10 @@ class yield_waiter { struct handler_state { handler_type handler; work_guard work; + std::unique_lock* lock = nullptr; - explicit handler_state(handler_type&& h) - : handler(std::move(h)), - work(make_work_guard(handler)) + handler_state(handler_type&& h, std::unique_lock* lock) + : handler(std::move(h)), work(make_work_guard(handler)), lock(lock) {} }; std::optional state; @@ -189,3 +253,23 @@ class yield_waiter { }; } // namespace ceph::async + +namespace boost::asio { + +// forward the handler's associated executor, allocator, cancellation slot, etc +template