From 0608ee709ee69e0bfcbe7368af34481b3ea3bbc5 Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 07:57:28 +0000 Subject: [PATCH 1/5] upgrade to 18.2.7 from existing PR --- SPECS/ceph/ceph.signatures.json | 8 ++--- SPECS/ceph/ceph.spec | 57 ++++++++++++++++++++++++++++++--- cgmanifest.json | 4 +-- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/SPECS/ceph/ceph.signatures.json b/SPECS/ceph/ceph.signatures.json index 9097ec9ee95..42c0fbb071c 100644 --- a/SPECS/ceph/ceph.signatures.json +++ b/SPECS/ceph/ceph.signatures.json @@ -1,5 +1,5 @@ { - "Signatures": { - "ceph-18.2.2.tar.gz": "e70bb5246b4a5d7aa78eb548677a05cc21d0d47945ba2937fddc7511134ffb57" - } -} + "Signatures": { + "ceph-18.2.7.tar.gz": "42e7da3fa7936bb541399c16f0bc07f13d28be889e2c1df119335f1985ac9a74" + } +} \ No newline at end of file diff --git a/SPECS/ceph/ceph.spec b/SPECS/ceph/ceph.spec index d32cfdfe070..3d0f3b99d97 100644 --- a/SPECS/ceph/ceph.spec +++ b/SPECS/ceph/ceph.spec @@ -4,7 +4,7 @@ Summary: User space components of the Ceph file system Name: ceph -Version: 18.2.2 +Version: 18.2.7 Release: 12%{?dist} License: LGPLv2 and LGPLv3 and CC-BY-SA and GPLv2 and Boost and BSD and MIT and Public Domain and GPLv3 and ASL-2.0 URL: https://ceph.io/ @@ -28,8 +28,8 @@ Patch13: CVE-2020-10724.patch Patch14: CVE-2025-1744.patch Patch15: CVE-2021-28361.patch Patch16: CVE-2020-14378.patch -Patch17: CVE-2025-52555.patch -Patch18: CVE-2024-48916.patch +#Patch17: CVE-2025-52555.patch +#Patch18: CVE-2024-48916.patch Patch19: CVE-2025-9648.patch Patch20: CVE-2024-47866.patch # @@ -433,6 +433,21 @@ ceph-mgr-diskprediction-local is a ceph-mgr module that tries to predict disk failures using local algorithms and machine-learning databases. %endif +%package mgr-modules-core +Summary: Ceph Manager modules which are always enabled +BuildArch: noarch +Requires: python%{python3_pkgversion}-bcrypt +Requires: python%{python3_pkgversion}-pecan +Requires: python%{python3_pkgversion}-pyOpenSSL +Requires: python%{python3_pkgversion}-requests +Requires: python%{python3_pkgversion}-dateutil +Requires: python%{python3_pkgversion}-cherrypy +Requires: python%{python3_pkgversion}-PyYAML +Requires: python%{python3_pkgversion}-werkzeug +%description mgr-modules-core +ceph-mgr-modules-core provides a set of modules which are always +enabled by ceph-mgr. + %package mgr-k8sevents BuildArch: noarch Summary: Ceph Manager module to orchestrate ceph-events to kubernetes' events API @@ -1171,6 +1186,8 @@ install -m 644 -D monitoring/ceph-mixin/prometheus_alerts.yml %{buildroot}/etc/p %dir %{_libdir}/ceph %dir %{_libdir}/ceph/erasure-code %{_libdir}/ceph/erasure-code/libec_*.so* +%dir %{_libdir}/ceph/extblkdev +%{_libdir}/ceph/extblkdev/libceph_*.so* %dir %{_libdir}/ceph/compressor %{_libdir}/ceph/compressor/libceph_*.so* %{_unitdir}/ceph-crash.service @@ -1394,6 +1411,38 @@ if [ $1 -eq 1 ] ; then /usr/bin/systemctl try-restart ceph-mgr.target >/dev/null 2>&1 || : fi +%files mgr-modules-core +%dir %{_datadir}/ceph/mgr +%{_datadir}/ceph/mgr/alerts +%{_datadir}/ceph/mgr/balancer +%{_datadir}/ceph/mgr/crash +%{_datadir}/ceph/mgr/devicehealth +%{_datadir}/ceph/mgr/influx +%{_datadir}/ceph/mgr/insights +%{_datadir}/ceph/mgr/iostat +%{_datadir}/ceph/mgr/localpool +%{_datadir}/ceph/mgr/mds_autoscaler +%{_datadir}/ceph/mgr/mirroring +%{_datadir}/ceph/mgr/nfs +%{_datadir}/ceph/mgr/orchestrator +%{_datadir}/ceph/mgr/osd_perf_query +%{_datadir}/ceph/mgr/osd_support +%{_datadir}/ceph/mgr/pg_autoscaler +%{_datadir}/ceph/mgr/progress +%{_datadir}/ceph/mgr/prometheus +%{_datadir}/ceph/mgr/rbd_support +# a dependent module of restful is not available and +# hence not enabling it at this moment +%{_datadir}/ceph/mgr/selftest +%{_datadir}/ceph/mgr/snap_schedule +%{_datadir}/ceph/mgr/stats +%{_datadir}/ceph/mgr/status +%{_datadir}/ceph/mgr/telegraf +%{_datadir}/ceph/mgr/telemetry +%{_datadir}/ceph/mgr/test_orchestrator +%{_datadir}/ceph/mgr/volumes +%{_datadir}/ceph/mgr/zabbix + %files mgr-k8sevents %{_datadir}/ceph/mgr/k8sevents @@ -2127,4 +2176,4 @@ exit 0 * Fri Aug 21 2020 Thomas Crain 15.2.4-1 - Initial CBL-Mariner import from Ceph source (license: LGPLv2.1) -- License verified +- License verified \ No newline at end of file diff --git a/cgmanifest.json b/cgmanifest.json index f4906091a8a..113e0c7be43 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -1587,8 +1587,8 @@ "type": "other", "other": { "name": "ceph", - "version": "18.2.2", - "downloadUrl": "https://download.ceph.com/tarballs/ceph-18.2.2.tar.gz" + "version": "18.2.7", + "downloadUrl": "https://download.ceph.com/tarballs/ceph-18.2.7.tar.gz" } } }, From df35a405404852899cd27ffd9ccc5bc2aa1db7ba Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 08:04:43 +0000 Subject: [PATCH 2/5] patch cepf for CVE-2025-30156(backported) --- SPECS/ceph/0001-test-CVE-2025-30156.patch | 3442 +++++++++++++++++++++ SPECS/ceph/ceph.spec | 6 +- 2 files changed, 3447 insertions(+), 1 deletion(-) create mode 100644 SPECS/ceph/0001-test-CVE-2025-30156.patch diff --git a/SPECS/ceph/0001-test-CVE-2025-30156.patch b/SPECS/ceph/0001-test-CVE-2025-30156.patch new file mode 100644 index 00000000000..1306f03ced7 --- /dev/null +++ b/SPECS/ceph/0001-test-CVE-2025-30156.patch @@ -0,0 +1,3442 @@ +From 7830b1b0624fe552ed46bd515205356077b5af59 Mon Sep 17 00:00:00 2001 +From: akhila-guruju +Date: Wed, 9 Sep 2026 06:39:44 +0000 +Subject: [PATCH] CVE-2025-30156 minimal backport + +--- + src/auth/Auth.h | 4 + + src/auth/AuthClientHandler.h | 1 + + src/auth/Crypto.cc | 705 +++++++++++++++++++++++++- + src/auth/Crypto.h | 106 +++- + src/auth/RotatingKeyRing.cc | 6 + + src/auth/RotatingKeyRing.h | 1 + + src/auth/cephx/CephxClientHandler.cc | 11 +- + src/auth/cephx/CephxClientHandler.h | 1 + + src/auth/cephx/CephxKeyServer.cc | 88 +++- + src/auth/cephx/CephxKeyServer.h | 20 +- + src/auth/cephx/CephxProtocol.cc | 68 ++- + src/auth/cephx/CephxProtocol.h | 98 ++++ + src/auth/cephx/CephxServiceHandler.cc | 19 +- + src/auth/cephx/CephxServiceHandler.h | 3 + + src/auth/cephx/CephxSessionHandler.cc | 60 ++- + src/ceph_mon.cc | 9 + + src/ceph_osd.cc | 2 +- + src/common/ceph_context.cc | 5 + + src/common/ceph_context.h | 1 + + src/common/options/mon.yaml.in | 10 + + src/crimson/mon/MonClient.cc | 16 + + src/crimson/osd/main.cc | 2 +- + src/include/ceph_fs.h | 5 +- + src/mon/AuthMonitor.cc | 174 ++++++- + src/mon/AuthMonitor.h | 2 + + src/mon/MonClient.cc | 16 + + src/mon/MonClient.h | 1 + + src/mon/MonCommands.h | 25 +- + src/mon/MonMap.cc | 43 +- + src/mon/MonMap.h | 6 + + src/mon/Monitor.cc | 38 +- + src/mon/Monitor.h | 6 + + src/mon/MonmapMonitor.cc | 87 ++++ + src/mon/MonmapMonitor.h | 1 + + src/mon/mon_types.h | 7 + + src/mount/mount.ceph.c | 8 +- + src/mount/mount.ceph.h | 4 +- + src/rgw/rgw_rest_s3.cc | 2 +- + src/rgw/rgw_sts.cc | 2 +- + src/test/crypto.cc | 334 ++++++++++++ + src/tools/ceph_authtool.cc | 17 +- + src/tools/monmaptool.cc | 3 + + 42 files changed, 1916 insertions(+), 101 deletions(-) + +diff --git a/src/auth/Auth.h b/src/auth/Auth.h +index 5521c8d3f..2b3c7ebfb 100644 +--- a/src/auth/Auth.h ++++ b/src/auth/Auth.h +@@ -294,6 +294,10 @@ struct RotatingSecrets { + return secrets.empty(); + } + ++ void wipe() { ++ secrets.clear(); ++ } ++ + void dump(); + }; + WRITE_CLASS_ENCODER(RotatingSecrets) +diff --git a/src/auth/AuthClientHandler.h b/src/auth/AuthClientHandler.h +index aba21b415..c7eb07b4b 100644 +--- a/src/auth/AuthClientHandler.h ++++ b/src/auth/AuthClientHandler.h +@@ -61,6 +61,7 @@ public: + virtual AuthAuthorizer *build_authorizer(uint32_t service_id) const = 0; + + virtual bool need_tickets() = 0; ++ virtual void invalidate_all_tickets() {} + + virtual void set_global_id(uint64_t id) = 0; + +diff --git a/src/auth/Crypto.cc b/src/auth/Crypto.cc +index ce666e8bd..28cd1d2e0 100644 +--- a/src/auth/Crypto.cc ++++ b/src/auth/Crypto.cc +@@ -12,11 +12,15 @@ + */ + + #include ++#include ++#include ++#include + #include + #include + #include + + #include ++#include + + #include "Crypto.h" + +@@ -25,14 +29,20 @@ + #include "common/armor.h" + #include "common/ceph_context.h" + #include "common/ceph_crypto.h" ++#include "common/ceph_mutex.h" + #include "common/hex.h" + #include "common/safe_io.h" + #include "include/ceph_fs.h" + #include "include/compat.h" + #include "common/Formatter.h" + #include "common/debug.h" ++#include "global/global_context.h" + #include + ++#include ++ ++#define dout_subsys ceph_subsys_auth ++ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +@@ -46,6 +56,8 @@ using ceph::bufferlist; + using ceph::bufferptr; + using ceph::Formatter; + ++using boost::endian::native_to_big; ++ + + // use getentropy() if available. it uses the same source of randomness + // as /dev/urandom without the filesystem overhead +@@ -148,6 +160,20 @@ int CryptoRandom::open_urandom() + // fallback implementation of the bufferlist-free + // interface. + ++int CryptoKeyHandler::encrypt(const ceph::bufferlist& in, ++ ceph::bufferlist& out, ++ std::string *error) const ++{ ++ return encrypt(g_ceph_context, in, out, error); ++} ++ ++int CryptoKeyHandler::decrypt(const ceph::bufferlist& in, ++ ceph::bufferlist& out, ++ std::string *error) const ++{ ++ return decrypt(g_ceph_context, in, out, error); ++} ++ + std::size_t CryptoKeyHandler::encrypt( + const CryptoKeyHandler::in_slice_t& in, + const CryptoKeyHandler::out_slice_t& out) const +@@ -162,6 +188,117 @@ std::size_t CryptoKeyHandler::encrypt( + if (ret != 0 || !error.empty()) { + throw std::runtime_error(std::move(error)); + } ++ if (out.buf == nullptr) { ++ return ciphertext.length(); ++ } ++ if (out.max_length < ciphertext.length()) { ++ throw std::runtime_error("output buffer too small"); ++ } ++ memcpy(out.buf, ciphertext.c_str(), ciphertext.length()); ++ return ciphertext.length(); ++} ++ ++std::size_t CryptoKeyHandler::decrypt( ++ const CryptoKeyHandler::in_slice_t& in, ++ const CryptoKeyHandler::out_slice_t& out) const ++{ ++ ceph::bufferptr inptr(reinterpret_cast(in.buf), in.length); ++ ceph::bufferlist ciphertext; ++ ciphertext.append(std::move(inptr)); ++ ++ ceph::bufferlist plaintext; ++ std::string error; ++ const int ret = decrypt(ciphertext, plaintext, &error); ++ if (ret != 0 || !error.empty()) { ++ throw std::runtime_error(std::move(error)); ++ } ++ if (out.buf == nullptr) { ++ return plaintext.length(); ++ } ++ if (out.max_length < plaintext.length()) { ++ throw std::runtime_error("output buffer too small"); ++ } ++ memcpy(out.buf, plaintext.c_str(), plaintext.length()); ++ return plaintext.length(); ++} ++ ++std::size_t CryptoKeyHandler::encrypt_ext( ++ CephContext *cct, ++ uint32_t usage, ++ const CryptoKeyHandler::in_slice_t& in, ++ const CryptoKeyHandler::in_slice_t *confounder, ++ const CryptoKeyHandler::out_slice_t& out) const ++{ ++ ceph::bufferptr inptr(reinterpret_cast(in.buf), in.length); ++ ceph::bufferlist plaintext; ++ plaintext.append(std::move(inptr)); ++ ++ ceph::bufferlist confounder_bl; ++ if (confounder) { ++ ceph::bufferptr confounder_ptr( ++ reinterpret_cast(confounder->buf), confounder->length); ++ confounder_bl.append(std::move(confounder_ptr)); ++ } ++ ++ ceph::bufferlist ciphertext; ++ std::string error; ++ const int ret = encrypt_ext(cct, usage, plaintext, ++ confounder ? &confounder_bl : nullptr, ++ ciphertext, &error); ++ if (ret != 0 || !error.empty()) { ++ throw std::runtime_error(std::move(error)); ++ } ++ if (out.buf == nullptr) { ++ return ciphertext.length(); ++ } ++ if (out.max_length < ciphertext.length()) { ++ throw std::runtime_error("output buffer too small"); ++ } ++ memcpy(out.buf, ciphertext.c_str(), ciphertext.length()); ++ return ciphertext.length(); ++} ++ ++std::size_t CryptoKeyHandler::decrypt_ext( ++ CephContext *cct, ++ uint32_t usage, ++ const CryptoKeyHandler::in_slice_t& in, ++ const CryptoKeyHandler::out_slice_t& out) const ++{ ++ ceph::bufferptr inptr(reinterpret_cast(in.buf), in.length); ++ ceph::bufferlist ciphertext; ++ ciphertext.append(std::move(inptr)); ++ ++ ceph::bufferlist plaintext; ++ std::string error; ++ const int ret = decrypt_ext(cct, usage, ciphertext, plaintext, &error); ++ if (ret != 0 || !error.empty()) { ++ throw std::runtime_error(std::move(error)); ++ } ++ if (out.buf == nullptr) { ++ return plaintext.length(); ++ } ++ if (out.max_length < plaintext.length()) { ++ throw std::runtime_error("output buffer too small"); ++ } ++ memcpy(out.buf, plaintext.c_str(), plaintext.length()); ++ return plaintext.length(); ++} ++ ++std::size_t CryptoKeyHandler::encrypt( ++ CephContext *cct, ++ const CryptoKeyHandler::in_slice_t& in, ++ const CryptoKeyHandler::out_slice_t& out) const ++{ ++ ceph::bufferptr inptr(reinterpret_cast(in.buf), in.length); ++ ceph::bufferlist plaintext; ++ plaintext.append(std::move(inptr)); ++ ++ ceph::bufferlist ciphertext; ++ std::string error; ++ const int ret = encrypt(cct, plaintext, ciphertext, &error); ++ if (ret != 0 || !error.empty()) { ++ throw std::runtime_error(std::move(error)); ++ } + + // we need to specify the template parameter explicitly as ::length() + // returns unsigned int, not size_t. +@@ -173,6 +310,7 @@ std::size_t CryptoKeyHandler::encrypt( + } + + std::size_t CryptoKeyHandler::decrypt( ++ CephContext *cct, + const CryptoKeyHandler::in_slice_t& in, + const CryptoKeyHandler::out_slice_t& out) const + { +@@ -182,7 +320,7 @@ std::size_t CryptoKeyHandler::decrypt( + + ceph::bufferlist plaintext; + std::string error; +- const int ret = decrypt(ciphertext, plaintext, &error); ++ const int ret = decrypt(cct, ciphertext, plaintext, &error); + if (ret != 0 || !error.empty()) { + throw std::runtime_error(std::move(error)); + } +@@ -210,6 +348,19 @@ sha256_digest_t CryptoKeyHandler::hmac_sha256( + return ret; + } + ++sha256_digest_t CryptoKeyHandler::hmac_sha256( ++ const in_slice_t& in) const ++{ ++ TOPNSPC::crypto::HMACSHA256 hmac((const unsigned char*)secret.c_str(), secret.length()); ++ ++ hmac.Update(in.buf, in.length); ++ ++ sha256_digest_t ret; ++ hmac.Final(ret.v); ++ ++ return ret; ++} ++ + // --------------------------------------------------- + + class CryptoNoneKeyHandler : public CryptoKeyHandler { +@@ -221,12 +372,12 @@ public: + using CryptoKeyHandler::encrypt; + using CryptoKeyHandler::decrypt; + +- int encrypt(const bufferlist& in, ++ int encrypt(CephContext *cct, const bufferlist& in, + bufferlist& out, std::string *error) const override { + out = in; + return 0; + } +- int decrypt(const bufferlist& in, ++ int decrypt(CephContext *cct, const bufferlist& in, + bufferlist& out, std::string *error) const override { + out = in; + return 0; +@@ -246,7 +397,7 @@ public: + int validate_secret(const bufferptr& secret) override { + return 0; + } +- CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override { ++ CryptoKeyHandler *get_key_handler_ext(const bufferptr& secret, uint32_t usage, string& error) override { + return new CryptoNoneKeyHandler; + } + }; +@@ -264,7 +415,7 @@ public: + } + int create(CryptoRandom *random, bufferptr& secret) override; + int validate_secret(const bufferptr& secret) override; +- CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override; ++ CryptoKeyHandler *get_key_handler_ext(const bufferptr& secret, uint32_t usage /* unused */, string& error) override; + }; + + // when we say AES, we mean AES-128 +@@ -302,9 +453,10 @@ public: + return 0; + } + +- int encrypt(const ceph::bufferlist& in, ++ int encrypt(CephContext *cct, const ceph::bufferlist& in, + ceph::bufferlist& out, + std::string* /* unused */) const override { ++ ldout(cct, 20) << "CryptoAESKeyHandler::encrypt()" << dendl; + // we need to take into account the PKCS#7 padding. There *always* will + // be at least one byte of padding. This stays even to input aligned to + // AES_BLOCK_LEN. Otherwise we would face ambiguities during decryption. +@@ -344,9 +496,10 @@ public: + return 0; + } + +- int decrypt(const ceph::bufferlist& in, ++ int decrypt(CephContext *cct, const ceph::bufferlist& in, + ceph::bufferlist& out, + std::string* /* unused */) const override { ++ ldout(cct, 20) << "CryptoAESKeyHandler::decrypt()" << dendl; + // PKCS#7 padding enlarges even empty plain-text to take 16 bytes. + if (in.length() < AES_BLOCK_LEN || in.length() % AES_BLOCK_LEN) { + return -1; +@@ -376,7 +529,7 @@ public: + return 0; + } + +- std::size_t encrypt(const in_slice_t& in, ++ std::size_t encrypt(CephContext *cct, const in_slice_t& in, + const out_slice_t& out) const override { + if (out.buf == nullptr) { + // 16 + p2align(10, 16) -> 16 +@@ -417,7 +570,7 @@ public: + return main_encrypt_size + tail_encrypt_size; + } + +- std::size_t decrypt(const in_slice_t& in, ++ std::size_t decrypt(CephContext *cct, const in_slice_t& in, + const out_slice_t& out) const override { + if (in.length % AES_BLOCK_LEN != 0 || in.length < AES_BLOCK_LEN) { + throw std::runtime_error("input not aligned to AES_BLOCK_LEN"); +@@ -466,8 +619,9 @@ int CryptoAES::validate_secret(const bufferptr& secret) + return 0; + } + +-CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, +- string& error) ++CryptoKeyHandler *CryptoAES::get_key_handler_ext(const bufferptr& secret, ++ uint32_t usage, ++ string& error) + { + CryptoAESKeyHandler *ckh = new CryptoAESKeyHandler; + ostringstream oss; +@@ -480,6 +634,489 @@ CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, + } + + ++// --------------------------------------------------- ++ ++/* ++ * AES256CTS-HMAC384-192 ++ */ ++class CryptoAES256KRB5 : public CryptoHandler { ++public: ++ CryptoAES256KRB5() { } ++ ~CryptoAES256KRB5() override {} ++ int get_type() const override { ++ return CEPH_CRYPTO_AES256KRB5; ++ } ++ int create(CryptoRandom *random, bufferptr& secret) override; ++ int validate_secret(const bufferptr& secret) override; ++ CryptoKeyHandler *get_key_handler_ext(const bufferptr& secret, uint32_t usage, string& error) override; ++}; ++ ++static constexpr const std::size_t AES256KRB5_KEY_LEN{32}; ++static constexpr const std::size_t AES256KRB5_BLOCK_LEN{16}; ++static constexpr const std::size_t AES256KRB5_HASH_LEN{24}; ++static constexpr const std::size_t SHA384_LEN{48}; ++ ++class CryptoAES256KRB5KeyHandler : public CryptoKeyHandler { ++ EVP_CIPHER *cipher{nullptr}; ++ uint32_t default_usage = 0; ++ ++ struct usage_keys { ++ ceph::bufferlist ki; ++ const unsigned char *ki_raw = nullptr; ++ ceph::bufferlist ke; ++ const unsigned char *ke_raw = nullptr; ++ }; ++ ++ mutable ceph::mutex lock = ceph::make_mutex("CryptoAES256KRB5KeyHandler"); ++ mutable std::map keys; ++ ++static void dump_buf(CephContext *cct, string title, const unsigned char *buf, int len) ++{ ++ std::stringstream ss; ++ ss << std::endl << title << std::endl; ++ for (int i = 0; i < len; ++i) { ++ if (i != 0 && i % 16 == 0) { ++ ss << std::endl; ++ } ++ ss << fmt::format("{:02x} ", buf[i]); ++ } ++ ss << std::endl; ++ ldout(cct, 0) << ss.str() << dendl; ++} ++ ++ static int calc_hmac_sha384(const unsigned char *data, ++ int data_len, ++ const unsigned char* hmac_key, ++ int key_size, ++ const unsigned char *iv, ++ int iv_size, ++ char *out, ++ int out_size, ++ ostringstream& err) { ++ unsigned int len = 0; ++ char _out[SHA384_LEN]; ++ char *pout; ++ bool need_trim = (out_size < (int)sizeof(_out)); ++ if (need_trim) { ++ pout = _out; ++ } else { ++ pout = out; ++ } ++ ++ /* IV is prepended to the plaintext */ ++ ceph::bufferptr iv_buf(reinterpret_cast(iv), iv_size); ++ ceph::bufferlist source; ++ source.push_back(iv_buf); ++ source.append((const char *)data, data_len); ++ ++ HMAC(EVP_sha384(), hmac_key, key_size, ++ reinterpret_cast(source.c_str()), source.length(), ++ (unsigned char *)pout, &len); ++ ++ if (len != SHA384_LEN) { ++ err << "Unexpected calculated SHA384 length"; ++ return -EIO; ++ } ++ ++ if (need_trim) { ++ memcpy(out, pout, out_size); ++ len = out_size; ++ } ++ ++ return len; ++ } ++ ++ static int calc_kx(const ceph::bufferptr& secret, ++ uint32_t usage, ++ uint8_t type, ++ int k, ++ ceph::bufferlist& out, ++ ostringstream& err) { ++ ++ struct plain_data { ++ unsigned char prefix[4] = { 0, 0, 0, 1 }; ++ uint32_t usage; ++ uint8_t type; ++ uint8_t c = 0; ++ uint32_t k; ++ ++ plain_data(uint32_t _usage, uint8_t _type, uint32_t _k) : ++ usage(native_to_big(_usage)), ++ type(_type), ++ k(native_to_big(_k * 8)) {} ++ } __attribute__((packed)) data(usage, type, k); ++ ++ ceph::bufferptr bp(reinterpret_cast(&data), sizeof(data)); ++ ++ ceph::bufferptr sha384(SHA384_LEN); ++ int r = calc_hmac_sha384((const unsigned char *)bp.c_str(), bp.length(), ++ reinterpret_cast(secret.c_str()), ++ secret.length(), ++ nullptr, 0, /* no IV */ ++ sha384.c_str(), ++ SHA384_LEN, ++ err); ++ ++ bufferlist bl; ++ bl.append(sha384); ++ bl.splice(0, k, &out); ++ ++ return r; ++ } ++ ++ int init_usage_keys(uint32_t usage, usage_keys *usage_key, ++ ostringstream& err) const { ++ int r = calc_kx(secret, usage, 0x55, AES256KRB5_HASH_LEN, ++ usage_key->ki, err); ++ if (r < 0) { ++ return r; ++ } ++ usage_key->ki_raw = reinterpret_cast( ++ usage_key->ki.c_str()); ++ ++ r = calc_kx(secret, usage, 0xAA, AES256KRB5_KEY_LEN, ++ usage_key->ke, err); ++ if (r < 0) { ++ return r; ++ } ++ usage_key->ke_raw = reinterpret_cast( ++ usage_key->ke.c_str()); ++ return 0; ++ } ++ ++ const usage_keys *get_usage_keys(uint32_t usage) const { ++ std::lock_guard l(lock); ++ auto [iter, inserted] = keys.try_emplace(usage); ++ if (inserted) { ++ ostringstream err; ++ if (init_usage_keys(usage, &iter->second, err) < 0) { ++ keys.erase(iter); ++ return nullptr; ++ } ++ } ++ return &iter->second; ++ } ++ ++ int encrypt_AES256_CTS(CephContext *cct, ++ ceph::bufferlist& plaintext, ++ const unsigned char* iv, int iv_size, ++ uint32_t usage, ++ unsigned char *ciphertext, ++ int ciphertext_len) const { ++ if (!cipher) { ++ return -EINVAL; /* initialization error */ ++ } ++ ++ if ((size_t)ciphertext_len < plaintext.length()) { ++ return -EINVAL; ++ } ++ ++ OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0), ++ OSSL_PARAM_construct_end()}; ++ ++ std::unique_ptr ctx( ++ EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); ++ if (!ctx) { ++ ldout(cct, 20) << "EVP_CIPHER_CTX_new() returned null" << dendl; ++ return -EIO; ++ } ++ ++ auto usage_key = get_usage_keys(usage); ++ if (!usage_key) { ++ return -EIO; ++ } ++ ++ if (!EVP_EncryptInit_ex2(ctx.get(), cipher, usage_key->ke_raw, iv, params)) { ++ ldout(cct, 20) << "EVP_EncryptInit() failed" << dendl; ++ return -EIO; ++ } ++ ++ int encrypted_len = 0; ++ int len; ++ ++ auto ret = EVP_EncryptUpdate(ctx.get(), ciphertext + encrypted_len, &len, (const unsigned char *)plaintext.c_str(), plaintext.length()); ++ if (ret != 1) { ++ ldout(cct, 20) << "EVP_EncryptUpdate(len=" << plaintext.length() << ") returned " << ret << dendl; ++ return -EIO; ++ } ++ encrypted_len += len; ++ ++ ret = EVP_EncryptFinal_ex(ctx.get(), ciphertext + encrypted_len, &len); ++ if (ret != 1) { ++ ldout(cct, 20) << "EVP_EncryptFinal_ex() returned " << ret << dendl; ++ return -EIO; ++ } ++ encrypted_len += len; ++ ++ return encrypted_len; ++ } ++ ++ int decrypt_AES256_CTS(ceph::bufferlist& ciphertext, ++ const unsigned char* key, const unsigned char* iv, ++ int iv_size, ++ ceph::bufferptr& plaintext) const { ++ if (!cipher) { ++ return -EINVAL; /* initialization error really */ ++ } ++ ++ OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0), ++ OSSL_PARAM_construct_end()}; ++ ++ std::unique_ptr ctx( ++ EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); ++ if (!ctx) { ++ return -EIO; ++ } ++ ++ if (!EVP_DecryptInit_ex2(ctx.get(), cipher, key, iv, params)) { ++ return -EIO; ++ } ++ ++ int len; ++ auto dest = reinterpret_cast(plaintext.c_str()); ++ int plaintext_len = 0; ++ ++ int max = ciphertext.length(); ++ auto iter = ciphertext.cbegin(); ++ while (!iter.end()) { ++ const char *p; ++ int chunk_len = iter.get_ptr_and_advance(max, &p); ++ ++ if (EVP_DecryptUpdate(ctx.get(), dest + plaintext_len, &len, (const unsigned char *)p, chunk_len) != 1) { ++ return -EIO; ++ } ++ plaintext_len += len; ++ } ++ ++ if (EVP_DecryptFinal_ex(ctx.get(), dest + plaintext_len, &len) != 1) { ++ return -EIO; ++ } ++ plaintext_len += len; ++ ++ return 0; ++ } ++ ++public: ++ CryptoAES256KRB5KeyHandler() : CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()) { ++ } ++ ~CryptoAES256KRB5KeyHandler() override { ++ EVP_CIPHER_free(cipher); ++ } ++ ++ using CryptoKeyHandler::encrypt; ++ using CryptoKeyHandler::decrypt; ++ ++ int init(const ceph::bufferptr& s, uint32_t usage, ostringstream& err) { ++ cipher = EVP_CIPHER_fetch(NULL, "AES-256-CBC-CTS", NULL); ++ if (!cipher) { ++ err << "Failed to fetch OpenSSL cipher AES-256-CBC-CTS"; ++ return -EINVAL; ++ } ++ secret = s; ++ default_usage = usage; ++ auto& usage_key = keys[usage]; ++ return init_usage_keys(usage, &usage_key, err); ++ } ++ ++ int encrypt_ext(CephContext *cct, const ceph::bufferlist& in, ++ const ceph::bufferlist *confounder, ++ ceph::bufferlist& out, ++ std::string* /* unused */) const override { ++ return encrypt_ext(cct, default_usage, in, confounder, out, nullptr); ++ } ++ ++ int encrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::bufferlist& in, ++ const ceph::bufferlist *confounder, ++ ceph::bufferlist& out, ++ std::string* /* unused */) const override { ++ ldout(cct, 20) << "CryptoAES256KRB5KeyHandler::encrypt()" << dendl; ++ // encrypted (confounder | data) | hash ++ ceph::bufferptr out_tmp{static_cast( ++ AES256KRB5_BLOCK_LEN + in.length() + AES256KRB5_HASH_LEN)}; ++ ++ /* encrypted (confounder data) */ ++ char *aes_enc = out_tmp.c_str(); ++ int aes_enc_len = AES256KRB5_BLOCK_LEN + in.length(); ++ ++ ceph::bufferlist incopy; ++ bufferptr confounder_buf(AES256KRB5_BLOCK_LEN); ++ ++ if (!confounder) { ++ cct->random()->get_bytes(confounder_buf.c_str(), confounder_buf.length()); ++ incopy.append(confounder_buf); ++ } else { ++ if (confounder->length() != AES256KRB5_BLOCK_LEN) { ++ ldout(cct, 0) << "ERROR: confounder length is expected to be equal to block size (" << AES256KRB5_BLOCK_LEN << ")" << dendl; ++ return -EINVAL; ++ } ++ incopy.append(*confounder); ++ } ++ ++ // combine confounder with input data ++ incopy.append(in); ++ ++ // reinitialize IV each time. It might be unnecessary depending on ++ // actual implementation but at the interface layer we are obliged ++ // to deliver IV as non-const. ++ static_assert(strlen_ct(CEPH_AES_IV) == AES256KRB5_BLOCK_LEN); ++ unsigned char iv[AES_BLOCK_LEN]; ++ memset(iv, 0, sizeof(iv)); ++ ++ int r = encrypt_AES256_CTS(cct, incopy, iv, sizeof(iv), usage, ++ (unsigned char *)aes_enc, aes_enc_len); ++ if (r < 0) { ++ return r; ++ } ++ aes_enc_len = r; ++ ++ char *hmac = out_tmp.c_str() + AES256KRB5_BLOCK_LEN + in.length(); ++ ++ auto usage_key = get_usage_keys(usage); ++ if (!usage_key) { ++ return -EIO; ++ } ++ ++ ostringstream err; ++ r = calc_hmac_sha384((const unsigned char *)aes_enc, aes_enc_len, ++ usage_key->ki_raw, usage_key->ki.length(), ++ iv, sizeof(iv), ++ hmac, AES256KRB5_HASH_LEN, err); ++ if (r < 0) { ++ return r; ++ } ++ ++ out.append(out_tmp); ++ return 0; ++ } ++ ++ int decrypt(CephContext *cct, const ceph::bufferlist& in, ++ ceph::bufferlist& out, ++ std::string* /* unused */) const override { ++ return decrypt_ext(cct, default_usage, in, out, nullptr); ++ } ++ ++ int decrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::bufferlist& in, ++ ceph::bufferlist& out, ++ std::string* /* unused */) const override { ++ ++ ldout(cct, 20) << "CryptoAES256KRB5KeyHandler::decrypt()" << dendl; ++ if (in.length() < AES256KRB5_BLOCK_LEN + AES256KRB5_HASH_LEN) { /* minimum size: confounder + hmac */ ++ return -EINVAL; ++ } ++ ++ // needed because of .c_str() on const. It's a shallow copy. ++ bufferlist incopy(in); ++ ++ ceph::bufferlist indata; ++ ++ /* after this: ++ * indata holds: encrypted (confounder | plaintext) ++ * incopy holds: hmac hash of indata ++ */ ++ incopy.splice(0, in.length() - AES256KRB5_HASH_LEN, &indata); ++ ++ auto& inhash = incopy; ++ ++ // make a local, modifiable copy of IV. ++ static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN); ++ unsigned char iv[AES_BLOCK_LEN]; ++ memset(iv, 0, sizeof(iv)); ++ ++ ++ auto usage_key = get_usage_keys(usage); ++ if (!usage_key) { ++ return -EIO; ++ } ++ ++ /* first need to compare hmac to calculated hmac */ ++ char hmac[AES256KRB5_HASH_LEN]; ++ ostringstream err; ++ int r = calc_hmac_sha384((const unsigned char *)indata.c_str(), indata.length(), ++ usage_key->ki_raw, usage_key->ki.length(), ++ iv, sizeof(iv), ++ hmac, sizeof(hmac), err); ++ if (r < 0) { ++ return r; ++ } ++ ++ int len = r; ++ ++ if ((size_t)len != inhash.length()) { ++ return -EPERM; ++ } ++ ++ if (CRYPTO_memcmp(hmac, inhash.c_str(), sizeof(hmac)) != 0) { ++ return -EPERM; ++ } ++ ++ /* will consist of confounder | plaintext */ ++ bufferptr tmp_out(indata.length()); ++ ++ r = decrypt_AES256_CTS(indata, ++ usage_key->ke_raw, iv, sizeof(iv), ++ tmp_out); ++ if (r < 0) { ++ return r; ++ } ++ ++ auto confounder_len = AES256KRB5_BLOCK_LEN; ++ ++ if (tmp_out.length() < confounder_len) { ++ /* should at least consist of the confounder */ ++ return -EPERM; ++ } ++ ++ int data_len = tmp_out.length() - AES256KRB5_BLOCK_LEN; ++ ++ out.append(tmp_out.c_str() + AES256KRB5_BLOCK_LEN, data_len); ++ ++ return 0; ++ } ++ ++ int encrypt(CephContext *cct, const ceph::bufferlist& in, ++ ceph::bufferlist& out, ++ std::string* unused) const override { ++ return encrypt_ext(cct, in, nullptr, out, unused); ++ } ++}; ++ ++ ++// ------------------------------------------------------------ ++ ++int CryptoAES256KRB5::create(CryptoRandom *random, bufferptr& secret) ++{ ++ bufferptr buf(AES256KRB5_KEY_LEN); ++ random->get_bytes(buf.c_str(), buf.length()); ++ secret = std::move(buf); ++ return 0; ++} ++ ++int CryptoAES256KRB5::validate_secret(const bufferptr& secret) ++{ ++ if (secret.length() < AES256KRB5_KEY_LEN) { ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++CryptoKeyHandler *CryptoAES256KRB5::get_key_handler_ext(const bufferptr& secret, ++ uint32_t usage, ++ string& error) ++{ ++ CryptoAES256KRB5KeyHandler *ckh = new CryptoAES256KRB5KeyHandler; ++ ostringstream oss; ++ if (ckh->init(secret, usage, oss) < 0) { ++ error = oss.str(); ++ delete ckh; ++ return NULL; ++ } ++ return ckh; ++} ++ ++ + + + // -- +@@ -606,10 +1243,56 @@ CryptoHandler *CryptoHandler::create(int type) + return new CryptoNone; + case CEPH_CRYPTO_AES: + return new CryptoAES; ++ case CEPH_CRYPTO_AES256KRB5: ++ return new CryptoAES256KRB5; + default: + return NULL; + } + } + ++int CryptoManager::get_key_type(const std::string& s) ++{ ++ std::string l = s; ++ std::transform(l.begin(), l.end(), l.begin(), ::tolower); ++ if (l == "aes") { ++ return CEPH_CRYPTO_AES; ++ } ++ if (l == "aes256k") { ++ return CEPH_CRYPTO_AES256KRB5; ++ } ++ if (l == "none") { ++ return CEPH_CRYPTO_NONE; ++ } ++ return -ENOENT; ++} ++ ++std::string_view CryptoManager::get_key_type_name(int type) ++{ ++ switch (type) { ++ case CEPH_CRYPTO_NONE: ++ return "none"; ++ case CEPH_CRYPTO_AES: ++ return "aes"; ++ case CEPH_CRYPTO_AES256KRB5: ++ return "aes256k"; ++ default: ++ return "unknown"; ++ } ++} ++ ++const std::set& CryptoManager::get_supported_crypto_types() ++{ ++ static const std::set types = { ++ CEPH_CRYPTO_NONE, CEPH_CRYPTO_AES, CEPH_CRYPTO_AES256KRB5 ++ }; ++ return types; ++} ++ ++bool CryptoManager::crypto_type_supported(int type) ++{ ++ const auto& types = get_supported_crypto_types(); ++ return types.find(type) != types.end(); ++} ++ + #pragma clang diagnostic pop + #pragma GCC diagnostic pop +diff --git a/src/auth/Crypto.h b/src/auth/Crypto.h +index a29ac1abd..d07336433 100644 +--- a/src/auth/Crypto.h ++++ b/src/auth/Crypto.h +@@ -20,7 +20,9 @@ + #include "include/utime.h" + #include "include/buffer.h" + ++#include + #include ++#include + + class CryptoKeyContext; + namespace ceph { class Formatter; } +@@ -71,17 +73,63 @@ public: + virtual ~CryptoKeyHandler() {} + + virtual int encrypt(const ceph::buffer::list& in, +- ceph::buffer::list& out, std::string *error) const = 0; ++ ceph::buffer::list& out, std::string *error) const; ++ virtual int encrypt(CephContext *cct, ++ const ceph::buffer::list& in, ++ ceph::buffer::list& out, std::string *error) const { ++ return encrypt(in, out, error); ++ } ++ ++ /* should either used internally, or for unitests. Confounder should be nullptr otherwise */ ++ virtual int encrypt_ext(CephContext *cct, ++ const ceph::buffer::list& in, ++ const ceph::buffer::list *confounder, ++ ceph::buffer::list& out, std::string *error) const { ++ if (confounder) { ++ return -ENOTSUP; ++ } ++ return encrypt(cct, in, out, error); ++ } ++ virtual int encrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::buffer::list& in, ++ const ceph::buffer::list *confounder, ++ ceph::buffer::list& out, std::string *error) const { ++ return encrypt_ext(cct, in, confounder, out, error); ++ } + virtual int decrypt(const ceph::buffer::list& in, +- ceph::buffer::list& out, std::string *error) const = 0; ++ ceph::buffer::list& out, std::string *error) const; ++ virtual int decrypt(CephContext *cct, ++ const ceph::buffer::list& in, ++ ceph::buffer::list& out, std::string *error) const { ++ return decrypt(in, out, error); ++ } ++ virtual int decrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::buffer::list& in, ++ ceph::buffer::list& out, std::string *error) const { ++ return decrypt(cct, in, out, error); ++ } + + // TODO: provide nullptr in the out::buf to get/estimate size requirements? + // Or maybe dedicated methods? + virtual std::size_t encrypt(const in_slice_t& in, ++ const out_slice_t& out) const; ++ virtual std::size_t encrypt(CephContext *cct, ++ const in_slice_t& in, + const out_slice_t& out) const; ++ virtual std::size_t encrypt_ext(CephContext *cct, uint32_t usage, ++ const in_slice_t& in, ++ const in_slice_t *confounder, ++ const out_slice_t& out) const; + virtual std::size_t decrypt(const in_slice_t& in, ++ const out_slice_t& out) const; ++ virtual std::size_t decrypt(CephContext *cct, ++ const in_slice_t& in, + const out_slice_t& out) const; ++ virtual std::size_t decrypt_ext(CephContext *cct, uint32_t usage, ++ const in_slice_t& in, ++ const out_slice_t& out) const; + ++ sha256_digest_t hmac_sha256(const in_slice_t& in) const; + sha256_digest_t hmac_sha256(const ceph::bufferlist& in) const; + }; + +@@ -158,30 +206,49 @@ public: + ceph::buffer::list& out, + std::string *error) const { + ceph_assert(ckh); // Bad key? +- return ckh->encrypt(in, out, error); ++ return ckh->encrypt(cct, in, out, error); + } + int decrypt(CephContext *cct, const ceph::buffer::list& in, + ceph::buffer::list& out, + std::string *error) const { + ceph_assert(ckh); // Bad key? +- return ckh->decrypt(in, out, error); ++ return ckh->decrypt(cct, in, out, error); ++ } ++ int encrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::buffer::list& in, ++ ceph::buffer::list& out, ++ std::string *error) const { ++ ceph_assert(ckh); ++ return ckh->encrypt_ext(cct, usage, in, nullptr, out, error); ++ } ++ int decrypt_ext(CephContext *cct, uint32_t usage, ++ const ceph::buffer::list& in, ++ ceph::buffer::list& out, ++ std::string *error) const { ++ ceph_assert(ckh); ++ return ckh->decrypt_ext(cct, usage, in, out, error); + } + + using in_slice_t = CryptoKeyHandler::in_slice_t; + using out_slice_t = CryptoKeyHandler::out_slice_t; + +- std::size_t encrypt(CephContext*, const in_slice_t& in, ++ std::size_t encrypt(CephContext *cct, const in_slice_t& in, + const out_slice_t& out) { + ceph_assert(ckh); +- return ckh->encrypt(in, out); ++ return ckh->encrypt(cct, in, out); + } +- std::size_t decrypt(CephContext*, const in_slice_t& in, ++ std::size_t decrypt(CephContext *cct, const in_slice_t& in, + const out_slice_t& out) { + ceph_assert(ckh); +- return ckh->encrypt(in, out); ++ return ckh->encrypt(cct, in, out); ++ } ++ ++ sha256_digest_t hmac_sha256(CephContext*, const ceph::buffer::list& in) const { ++ ceph_assert(ckh); ++ return ckh->hmac_sha256(in); + } + +- sha256_digest_t hmac_sha256(CephContext*, const ceph::buffer::list& in) { ++ sha256_digest_t hmac_sha256(CephContext *cct, const in_slice_t& in) const { + ceph_assert(ckh); + return ckh->hmac_sha256(in); + } +@@ -214,10 +281,29 @@ public: + virtual int create(CryptoRandom *random, ceph::buffer::ptr& secret) = 0; + virtual int validate_secret(const ceph::buffer::ptr& secret) = 0; + virtual CryptoKeyHandler *get_key_handler(const ceph::buffer::ptr& secret, +- std::string& error) = 0; ++ std::string& error) { ++ return get_key_handler_ext(secret, 0, error); ++ } ++ virtual CryptoKeyHandler *get_key_handler_ext( ++ const ceph::buffer::ptr& secret, uint32_t usage, std::string& error) { ++ return get_key_handler(secret, error); ++ } + + static CryptoHandler *create(int type); + }; + + ++/* ++ * Maps CephX cipher names used in configuration to CEPH_CRYPTO_* ids. ++ */ ++class CryptoManager { ++public: ++ /// returns a CEPH_CRYPTO_* id, or -ENOENT if the name is unknown ++ static int get_key_type(const std::string& s); ++ static std::string_view get_key_type_name(int type); ++ static bool crypto_type_supported(int type); ++ static const std::set& get_supported_crypto_types(); ++}; ++ ++ + #endif +diff --git a/src/auth/RotatingKeyRing.cc b/src/auth/RotatingKeyRing.cc +index 4bc6af6ad..2c03b15c8 100644 +--- a/src/auth/RotatingKeyRing.cc ++++ b/src/auth/RotatingKeyRing.cc +@@ -28,6 +28,12 @@ void RotatingKeyRing::set_secrets(RotatingSecrets&& s) + dump_rotating(); + } + ++void RotatingKeyRing::wipe() ++{ ++ std::lock_guard l{lock}; ++ secrets.wipe(); ++} ++ + void RotatingKeyRing::dump_rotating() const + { + ldout(cct, 10) << "dump_rotating:" << dendl; +diff --git a/src/auth/RotatingKeyRing.h b/src/auth/RotatingKeyRing.h +index 534eb5136..8509a546e 100644 +--- a/src/auth/RotatingKeyRing.h ++++ b/src/auth/RotatingKeyRing.h +@@ -43,6 +43,7 @@ public: + bool need_new_secrets() const; + bool need_new_secrets(utime_t now) const; + void set_secrets(RotatingSecrets&& s); ++ void wipe(); + void dump_rotating() const; + bool get_secret(const EntityName& name, CryptoKey& secret) const override; + bool get_service_secret(uint32_t service_id, uint64_t secret_id, +diff --git a/src/auth/cephx/CephxClientHandler.cc b/src/auth/cephx/CephxClientHandler.cc +index 76ccca735..435800d8b 100644 +--- a/src/auth/cephx/CephxClientHandler.cc ++++ b/src/auth/cephx/CephxClientHandler.cc +@@ -212,7 +212,8 @@ int CephxClientHandler::handle_response( + if (cbl.length() && connection_secret) { + auto p = cbl.cbegin(); + string err; +- if (decode_decrypt(cct, *connection_secret, *session_key, p, ++ if (decode_decrypt(cct, *connection_secret, *session_key, ++ CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET, p, + err)) { + lderr(cct) << __func__ << " failed to decrypt connection_secret" + << dendl; +@@ -268,7 +269,8 @@ int CephxClientHandler::handle_response( + return -ENOENT; + } + std::string error; +- if (decode_decrypt(cct, secrets, secret_key, indata, error)) { ++ if (decode_decrypt(cct, secrets, secret_key, ++ CEPHX_KEY_USAGE_ROTATING_SECRET, indata, error)) { + ldout(cct, 0) << "could not set rotating key: decode_decrypt failed. error:" + << error << dendl; + return -EINVAL; +@@ -331,3 +333,8 @@ bool CephxClientHandler::need_tickets() + + return _need_tickets(); + } ++ ++void CephxClientHandler::invalidate_all_tickets() ++{ ++ tickets.invalidate_all_tickets(); ++} +diff --git a/src/auth/cephx/CephxClientHandler.h b/src/auth/cephx/CephxClientHandler.h +index 601a5c69f..c5c899930 100644 +--- a/src/auth/cephx/CephxClientHandler.h ++++ b/src/auth/cephx/CephxClientHandler.h +@@ -65,6 +65,7 @@ public: + AuthAuthorizer *build_authorizer(uint32_t service_id) const override; + + bool need_tickets() override; ++ void invalidate_all_tickets() override; + + void set_global_id(uint64_t id) override { + global_id = id; +diff --git a/src/auth/cephx/CephxKeyServer.cc b/src/auth/cephx/CephxKeyServer.cc +index 236ac451a..4c3b00657 100644 +--- a/src/auth/cephx/CephxKeyServer.cc ++++ b/src/auth/cephx/CephxKeyServer.cc +@@ -145,6 +145,21 @@ KeyServer::KeyServer(CephContext *cct_, KeyRing *extra_secrets) + { + } + ++void KeyServer::set_crypto_policy( ++ int new_service_cipher, const std::vector& new_allowed_ciphers) ++{ ++ std::scoped_lock l{lock}; ++ service_cipher = new_service_cipher; ++ allowed_ciphers = new_allowed_ciphers; ++} ++ ++bool KeyServer::is_cipher_allowed(int cipher) const ++{ ++ std::scoped_lock l{lock}; ++ return std::find(allowed_ciphers.begin(), allowed_ciphers.end(), cipher) != ++ allowed_ciphers.end(); ++} ++ + int KeyServer::start_server() + { + std::scoped_lock l{lock}; +@@ -157,6 +172,38 @@ void KeyServer::dump() + _dump_rotating_secrets(); + } + ++void KeyServer::dump_key_metadata(Formatter *f) const ++{ ++ std::scoped_lock l{lock}; ++ f->open_array_section("entities"); ++ for (const auto& [name, auth] : data.secrets) { ++ f->open_object_section("entity"); ++ f->dump_string("name", name.to_str()); ++ f->dump_string("key_type", ++ CryptoManager::get_key_type_name(auth.key.get_type())); ++ if (!auth.pending_key.empty()) { ++ f->dump_string("pending_key_type", ++ CryptoManager::get_key_type_name( ++ auth.pending_key.get_type())); ++ } ++ f->close_section(); ++ } ++ f->close_section(); ++ ++ f->open_array_section("rotating_service_keys"); ++ for (const auto& [service_id, secrets] : data.rotating_secrets) { ++ for (const auto& [id, secret] : secrets.secrets) { ++ f->open_object_section("key"); ++ f->dump_string("service", ceph_entity_type_name(service_id)); ++ f->dump_unsigned("id", id); ++ f->dump_string("key_type", ++ CryptoManager::get_key_type_name(secret.key.get_type())); ++ f->close_section(); ++ } ++ } ++ f->close_section(); ++} ++ + void KeyServer::_dump_rotating_secrets() + { + ldout(cct, 30) << "_dump_rotating_secrets" << dendl; +@@ -182,7 +229,12 @@ int KeyServer::_rotate_secret(uint32_t service_id, KeyServerData &pending_data) + + while (r.need_new_secrets(now)) { + ExpiringCryptoKey ek; +- generate_secret(ek.key); ++ int key_type = service_cipher; ++ if (key_type < 0 || key_type == CEPH_CRYPTO_NONE) { ++ key_type = CEPH_CRYPTO_AES256KRB5; ++ } ++ ++ generate_secret(ek.key, key_type); + if (r.empty()) { + ek.expiration = now; + } else { +@@ -257,17 +309,18 @@ std::map KeyServer::get_used_pending_keys() + return ret; + } + +-bool KeyServer::generate_secret(CryptoKey& secret) ++bool KeyServer::generate_secret(CryptoKey& secret, std::optional key_type) + { ++ int type = key_type.value_or(CEPH_CRYPTO_AES256KRB5); + bufferptr bp; +- CryptoHandler *crypto = cct->get_crypto_handler(CEPH_CRYPTO_AES); ++ CryptoHandler *crypto = cct->get_crypto_handler(type); + if (!crypto) + return false; + + if (crypto->create(cct->random(), bp) < 0) + return false; + +- secret.set_secret(CEPH_CRYPTO_AES, bp, ceph_clock_now()); ++ secret.set_secret(type, bp, ceph_clock_now()); + + return true; + } +@@ -360,7 +413,7 @@ void KeyServer::encode_plaintext(bufferlist &bl) + bl.append(os.str()); + } + +-bool KeyServer::prepare_rotating_update(bufferlist& rotating_bl) ++bool KeyServer::prepare_rotating_update(bufferlist& rotating_bl, bool wipe) + { + std::scoped_lock l{lock}; + ldout(cct, 20) << __func__ << " before: data.rotating_ver=" << data.rotating_ver +@@ -368,7 +421,14 @@ bool KeyServer::prepare_rotating_update(bufferlist& rotating_bl) + + KeyServerData pending_data(nullptr); + pending_data.rotating_ver = data.rotating_ver + 1; +- pending_data.rotating_secrets = data.rotating_secrets; ++ if (wipe) { ++ auto auth = data.rotating_secrets.find(CEPH_ENTITY_TYPE_AUTH); ++ if (auth != data.rotating_secrets.end()) { ++ pending_data.rotating_secrets.emplace(auth->first, auth->second); ++ } ++ } else { ++ pending_data.rotating_secrets = data.rotating_secrets; ++ } + + int added = 0; + added += _rotate_secret(CEPH_ENTITY_TYPE_AUTH, pending_data); +@@ -405,7 +465,8 @@ bool KeyServer::get_rotating_encrypted(const EntityName& name, + RotatingSecrets secrets = rotate_iter->second; + + std::string error; +- if (encode_encrypt(cct, secrets, specific_key, enc_bl, error)) ++ if (encode_encrypt(cct, secrets, specific_key, ++ CEPHX_KEY_USAGE_ROTATING_SECRET, enc_bl, error)) + return false; + + return true; +@@ -429,6 +490,7 @@ bool KeyServer::get_service_caps(const EntityName& name, uint32_t service_id, + + int KeyServer::_build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, ++ std::optional key_type, + CephXSessionAuthInfo& info, + double ttl) + { +@@ -437,7 +499,7 @@ int KeyServer::_build_session_auth_info(uint32_t service_id, + info.ticket.init_timestamps(ceph_clock_now(), ttl); + info.validity.set_from_double(ttl); + +- generate_secret(info.session_key); ++ generate_secret(info.session_key, key_type); + + // mon keys are stored externally. and the caps are blank anyway. + if (service_id != CEPH_ENTITY_TYPE_MON) { +@@ -451,6 +513,7 @@ int KeyServer::_build_session_auth_info(uint32_t service_id, + + int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, ++ std::optional key_type, + CephXSessionAuthInfo& info) + { + double ttl; +@@ -459,21 +522,26 @@ int KeyServer::build_session_auth_info(uint32_t service_id, + return -EACCES; + } + ++ int ktype = std::min( ++ key_type.value_or(info.service_secret.get_type()), ++ info.service_secret.get_type()); ++ + std::scoped_lock l{lock}; +- return _build_session_auth_info(service_id, parent_ticket, info, ttl); ++ return _build_session_auth_info(service_id, parent_ticket, ktype, info, ttl); + } + + int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, ++ std::optional key_type, + CephXSessionAuthInfo& info) + { + info.service_secret = service_secret; + info.secret_id = secret_id; + + std::scoped_lock l{lock}; +- return _build_session_auth_info(service_id, parent_ticket, info, ++ return _build_session_auth_info(service_id, parent_ticket, key_type, info, + cct->_conf->auth_service_ticket_ttl); + } + +diff --git a/src/auth/cephx/CephxKeyServer.h b/src/auth/cephx/CephxKeyServer.h +index 64915c8ce..14a028d05 100644 +--- a/src/auth/cephx/CephxKeyServer.h ++++ b/src/auth/cephx/CephxKeyServer.h +@@ -20,6 +20,9 @@ + #include "common/ceph_mutex.h" + #include "include/common_fwd.h" + ++#include ++#include ++ + struct KeyServerData { + version_t version; + +@@ -195,18 +198,27 @@ class KeyServer : public KeyStore { + KeyServerData data; + std::map used_pending_keys; + mutable ceph::mutex lock; ++ int service_cipher = CEPH_CRYPTO_AES; ++ std::vector allowed_ciphers{ ++ CEPH_CRYPTO_AES, CEPH_CRYPTO_AES256KRB5}; + + int _rotate_secret(uint32_t service_id, KeyServerData &pending_data); + void _dump_rotating_secrets(); + int _build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, ++ std::optional key_type, + CephXSessionAuthInfo& info, + double ttl); + bool _get_service_caps(const EntityName& name, uint32_t service_id, + AuthCapsInfo& caps) const; + public: + KeyServer(CephContext *cct_, KeyRing *extra_secrets); +- bool generate_secret(CryptoKey& secret); ++ bool generate_secret(CryptoKey& secret, ++ std::optional key_type = std::nullopt); ++ ++ void set_crypto_policy(int service_cipher, ++ const std::vector& allowed_ciphers); ++ bool is_cipher_allowed(int cipher) const; + + bool get_secret(const EntityName& name, CryptoKey& secret) const override; + bool get_auth(const EntityName& name, EntityAuth& auth) const; +@@ -221,14 +233,17 @@ public: + void rotate_timeout(double timeout); + + void dump(); ++ void dump_key_metadata(ceph::Formatter *f) const; + + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, ++ std::optional key_type, + CephXSessionAuthInfo& info); + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, ++ std::optional key_type, + CephXSessionAuthInfo& info); + + /* get current secret for specific service type */ +@@ -304,7 +319,8 @@ public: + } + } + +- bool prepare_rotating_update(ceph::buffer::list& rotating_bl); ++ bool prepare_rotating_update(ceph::buffer::list& rotating_bl, ++ bool wipe = false); + + bool get_rotating_encrypted(const EntityName& name, ceph::buffer::list& enc_bl) const; + +diff --git a/src/auth/cephx/CephxProtocol.cc b/src/auth/cephx/CephxProtocol.cc +index 87a8b86b9..bf88b0627 100644 +--- a/src/auth/cephx/CephxProtocol.cc ++++ b/src/auth/cephx/CephxProtocol.cc +@@ -39,8 +39,21 @@ void cephx_calc_client_server_challenge(CephContext *cct, CryptoKey& secret, uin + b.client_challenge = client_challenge; + + bufferlist enc; +- if (encode_encrypt(cct, b, secret, enc, error)) +- return; ++ switch (secret.get_type()) { ++ case CEPH_CRYPTO_AES: ++ if (encode_encrypt(cct, b, secret, enc, error)) ++ return; ++ break; ++ default: ++ /* ++ * AES256KRB5 has a builtin confounder that randomizes the result, ++ * so just encode_encrypt() cannot be used. We should use ++ * a cryptographic has anyway, keeping the old behavior ++ * for AES for backward compatibility. ++ */ ++ if (encode_hash(cct, b, secret, enc, error)) ++ return; ++ }; + + uint64_t k = 0; + const ceph_le64 *p = (const ceph_le64 *)enc.c_str(); +@@ -73,7 +86,8 @@ bool cephx_build_service_ticket_blob(CephContext *cct, CephXSessionAuthInfo& inf + if (!info.service_secret.get_secret().length()) + error = "invalid key"; // Bad key? + else +- encode_encrypt_enc_bl(cct, ticket_info, info.service_secret, blob.blob, error); ++ encode_encrypt_enc_bl(cct, ticket_info, info.service_secret, ++ CEPHX_KEY_USAGE_TICKET_INFO, blob.blob, error); + if (!error.empty()) { + ldout(cct, -1) << "cephx_build_service_ticket_blob failed with error " + << error << dendl; +@@ -119,7 +133,8 @@ bool cephx_build_service_ticket_reply(CephContext *cct, + msg_a.session_key = info.session_key; + msg_a.validity = info.validity; + std::string error; +- if (encode_encrypt(cct, msg_a, principal_secret, reply, error)) { ++ if (encode_encrypt(cct, msg_a, principal_secret, ++ CEPHX_KEY_USAGE_TICKET_SESSION_KEY, reply, error)) { + ldout(cct, -1) << "error encoding encrypted: " << error << dendl; + return false; + } +@@ -137,7 +152,8 @@ bool cephx_build_service_ticket_reply(CephContext *cct, + + encode((__u8)should_encrypt_ticket, reply); + if (should_encrypt_ticket) { +- if (encode_encrypt(cct, service_ticket_bl, ticket_enc_key, reply, error)) { ++ if (encode_encrypt(cct, service_ticket_bl, ticket_enc_key, ++ CEPHX_KEY_USAGE_TICKET_BLOB, reply, error)) { + ldout(cct, -1) << "error encoding encrypted ticket: " << error << dendl; + return false; + } +@@ -163,7 +179,8 @@ bool CephXTicketHandler::verify_service_ticket_reply( + + CephXServiceTicket msg_a; + std::string error; +- if (decode_decrypt(cct, msg_a, secret, indata, error)) { ++ if (decode_decrypt(cct, msg_a, secret, ++ CEPHX_KEY_USAGE_TICKET_SESSION_KEY, indata, error)) { + ldout(cct, 0) << __func__ << " failed decode_decrypt, error is: " << error + << dendl; + return false; +@@ -176,7 +193,8 @@ bool CephXTicketHandler::verify_service_ticket_reply( + if (ticket_enc) { + ldout(cct, 10) << __func__ << " got encrypted ticket" << dendl; + std::string error; +- if (decode_decrypt(cct, service_ticket_bl, session_key, indata, error)) { ++ if (decode_decrypt(cct, service_ticket_bl, session_key, ++ CEPHX_KEY_USAGE_TICKET_BLOB, indata, error)) { + ldout(cct, 10) << __func__ << " decode_decrypt failed " + << "with " << error << dendl; + return false; +@@ -278,6 +296,13 @@ void CephXTicketManager::invalidate_ticket(uint32_t service_id) + iter->second.invalidate_ticket(); + } + ++void CephXTicketManager::invalidate_all_tickets() ++{ ++ for (auto& entry : tickets_map) { ++ entry.second.invalidate_ticket(); ++ } ++} ++ + /* + * PRINCIPAL: verify our attempt to authenticate succeeded. fill out + * this ServiceTicket with the result. +@@ -338,7 +363,8 @@ CephXAuthorizer *CephXTicketHandler::build_authorizer(uint64_t global_id) const + msg.nonce = a->nonce; + + std::string error; +- if (encode_encrypt(cct, msg, session_key, a->bl, error)) { ++ if (encode_encrypt(cct, msg, session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE, a->bl, error)) { + ldout(cct, 0) << "failed to encrypt authorizer: " << error << dendl; + delete a; + return 0; +@@ -404,7 +430,8 @@ bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, + } + + std::string error; +- decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket_blob.blob, error); ++ decode_decrypt_enc_bl(cct, ticket_info, service_secret, ++ CEPHX_KEY_USAGE_TICKET_INFO, ticket_blob.blob, error); + if (!error.empty()) { + ldout(cct, 0) << "ceph_decode_ticket could not decrypt ticket info. error:" + << error << dendl; +@@ -468,7 +495,8 @@ bool cephx_verify_authorizer(CephContext *cct, const KeyStore& keys, + if (!service_secret.get_secret().length()) + error = "invalid key"; // Bad key? + else +- decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket.blob, error); ++ decode_decrypt_enc_bl(cct, ticket_info, service_secret, ++ CEPHX_KEY_USAGE_TICKET_INFO, ticket.blob, error); + if (!error.empty()) { + ldout(cct, 0) << "verify_authorizer could not decrypt ticket info: error: " + << error << dendl; +@@ -485,7 +513,8 @@ bool cephx_verify_authorizer(CephContext *cct, const KeyStore& keys, + + // CephXAuthorize + CephXAuthorize auth_msg; +- if (decode_decrypt(cct, auth_msg, ticket_info.session_key, indata, error)) { ++ if (decode_decrypt(cct, auth_msg, ticket_info.session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE, indata, error)) { + ldout(cct, 0) << "verify_authorizercould not decrypt authorize request with error: " + << error << dendl; + return false; +@@ -500,7 +529,9 @@ bool cephx_verify_authorizer(CephContext *cct, const KeyStore& keys, + ldout(cct,10) << __func__ << " adding server_challenge " << c->server_challenge + << dendl; + +- encode_encrypt_enc_bl(cct, *c, ticket_info.session_key, *reply_bl, error); ++ encode_encrypt_enc_bl(cct, *c, ticket_info.session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE, ++ *reply_bl, error); + if (!error.empty()) { + ldout(cct, 10) << "verify_authorizer: encode_encrypt error: " << error << dendl; + return false; +@@ -538,7 +569,8 @@ bool cephx_verify_authorizer(CephContext *cct, const KeyStore& keys, + } + reply.connection_secret = *connection_secret; + } +- if (encode_encrypt(cct, reply, ticket_info.session_key, *reply_bl, error)) { ++ if (encode_encrypt(cct, reply, ticket_info.session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE_REPLY, *reply_bl, error)) { + ldout(cct, 10) << "verify_authorizer: encode_encrypt error: " << error << dendl; + return false; + } +@@ -554,7 +586,8 @@ bool CephXAuthorizer::verify_reply(bufferlist::const_iterator& indata, + CephXAuthorizeReply reply; + + std::string error; +- if (decode_decrypt(cct, reply, session_key, indata, error)) { ++ if (decode_decrypt(cct, reply, session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE_REPLY, indata, error)) { + ldout(cct, 0) << "verify_reply couldn't decrypt with error: " << error << dendl; + return false; + } +@@ -585,7 +618,9 @@ bool CephXAuthorizer::add_challenge(CephContext *cct, + if (!p.end()) { + std::string error; + CephXAuthorizeChallenge ch; +- decode_decrypt_enc_bl(cct, ch, session_key, challenge, error); ++ decode_decrypt_enc_bl(cct, ch, session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE, ++ challenge, error); + if (!error.empty()) { + ldout(cct, 0) << "failed to decrypt challenge (" << challenge.length() << " bytes): " + << error << dendl; +@@ -596,7 +631,8 @@ bool CephXAuthorizer::add_challenge(CephContext *cct, + } + + std::string error; +- if (encode_encrypt(cct, msg, session_key, bl, error)) { ++ if (encode_encrypt(cct, msg, session_key, ++ CEPHX_KEY_USAGE_AUTHORIZE, bl, error)) { + ldout(cct, 0) << __func__ << " failed to encrypt authorizer: " << error << dendl; + return false; + } +diff --git a/src/auth/cephx/CephxProtocol.h b/src/auth/cephx/CephxProtocol.h +index 8a28b7306..b3469df46 100644 +--- a/src/auth/cephx/CephxProtocol.h ++++ b/src/auth/cephx/CephxProtocol.h +@@ -30,6 +30,15 @@ + #define CEPHX_REQUEST_TYPE_MASK 0x0F00 + #define CEPHX_CRYPT_ERR 1 + ++#define CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET 0x03 ++#define CEPHX_KEY_USAGE_TICKET_SESSION_KEY 0x04 ++#define CEPHX_KEY_USAGE_TICKET_BLOB 0x05 ++#define CEPHX_KEY_USAGE_AUTHORIZE 0x10 ++#define CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE 0x11 ++#define CEPHX_KEY_USAGE_AUTHORIZE_REPLY 0x12 ++#define CEPHX_KEY_USAGE_ROTATING_SECRET 0x20 ++#define CEPHX_KEY_USAGE_TICKET_INFO 0x30 ++ + #include "auth/Auth.h" + #include + #include +@@ -328,6 +337,7 @@ struct CephXTicketManager { + void set_have_need_key(uint32_t service_id, uint32_t& have, uint32_t& need); + void validate_tickets(uint32_t mask, uint32_t& have, uint32_t& need); + void invalidate_ticket(uint32_t service_id); ++ void invalidate_all_tickets(); + + private: + CephContext *cct; +@@ -477,6 +487,34 @@ void decode_decrypt_enc_bl(CephContext *cct, T& t, CryptoKey key, + decode(t, iter2); + } + ++template ++void decode_decrypt_enc_bl(CephContext *cct, T& t, const CryptoKey& key, ++ uint32_t usage, ++ const ceph::buffer::list& bl_enc, ++ std::string &error) ++{ ++ uint64_t magic; ++ ceph::buffer::list bl; ++ ++ if (key.decrypt_ext(cct, usage, bl_enc, bl, &error) < 0) { ++ error = "decryption failed"; ++ return; ++ } ++ ++ auto iter2 = bl.cbegin(); ++ __u8 struct_v; ++ using ceph::decode; ++ decode(struct_v, iter2); ++ decode(magic, iter2); ++ if (magic != AUTH_ENC_MAGIC) { ++ std::ostringstream oss; ++ oss << "bad magic in decode_decrypt, " << magic << " != " << AUTH_ENC_MAGIC; ++ error = oss.str(); ++ return; ++ } ++ decode(t, iter2); ++} ++ + template + void encode_encrypt_enc_bl(CephContext *cct, const T& t, const CryptoKey& key, + ceph::buffer::list& out, std::string &error) +@@ -492,6 +530,21 @@ void encode_encrypt_enc_bl(CephContext *cct, const T& t, const CryptoKey& key, + key.encrypt(cct, bl, out, &error); + } + ++template ++void encode_encrypt_enc_bl(CephContext *cct, const T& t, const CryptoKey& key, ++ uint32_t usage, ceph::buffer::list& out, ++ std::string &error) ++{ ++ ceph::buffer::list bl; ++ __u8 struct_v = 1; ++ using ceph::encode; ++ encode(struct_v, bl); ++ uint64_t magic = AUTH_ENC_MAGIC; ++ encode(magic, bl); ++ encode(t, bl); ++ key.encrypt_ext(cct, usage, bl, out, &error); ++} ++ + template + int decode_decrypt(CephContext *cct, T& t, const CryptoKey& key, + ceph::buffer::list::const_iterator& iter, std::string &error) +@@ -510,6 +563,23 @@ int decode_decrypt(CephContext *cct, T& t, const CryptoKey& key, + return 0; + } + ++template ++int decode_decrypt(CephContext *cct, T& t, const CryptoKey& key, ++ uint32_t usage, ++ ceph::buffer::list::const_iterator& iter, ++ std::string &error) ++{ ++ ceph::buffer::list bl_enc; ++ using ceph::decode; ++ try { ++ decode(bl_enc, iter); ++ decode_decrypt_enc_bl(cct, t, key, usage, bl_enc, error); ++ } catch (ceph::buffer::error &e) { ++ error = "error decoding block for decryption"; ++ } ++ return error.empty() ? 0 : CEPHX_CRYPT_ERR; ++} ++ + template + int encode_encrypt(CephContext *cct, const T& t, const CryptoKey& key, + ceph::buffer::list& out, std::string &error) +@@ -524,4 +594,32 @@ int encode_encrypt(CephContext *cct, const T& t, const CryptoKey& key, + return 0; + } + ++template ++int encode_encrypt(CephContext *cct, const T& t, const CryptoKey& key, ++ uint32_t usage, ceph::buffer::list& out, ++ std::string &error) ++{ ++ using ceph::encode; ++ ceph::buffer::list bl_enc; ++ encode_encrypt_enc_bl(cct, t, key, usage, bl_enc, error); ++ if (!error.empty()) { ++ return CEPHX_CRYPT_ERR; ++ } ++ encode(bl_enc, out); ++ return 0; ++} ++ ++template ++int encode_hash(CephContext *cct, const T& t, const CryptoKey& key, ++ ceph::buffer::list& out, std::string &error) ++{ ++ using ceph::encode; ++ ceph::buffer::list bl_enc; ++ /* simple encoding, we don't need to add any magic because this will not be decoded */ ++ ::encode(t, bl_enc); ++ sha256_digest_t hash = key.hmac_sha256(cct, bl_enc); ++ out.append((const char *)&hash, sizeof(hash)); ++ return 0; ++} ++ + #endif +diff --git a/src/auth/cephx/CephxServiceHandler.cc b/src/auth/cephx/CephxServiceHandler.cc +index 977a43ad6..4fc7759fe 100644 +--- a/src/auth/cephx/CephxServiceHandler.cc ++++ b/src/auth/cephx/CephxServiceHandler.cc +@@ -22,6 +22,7 @@ + #include "include/random.h" + #include "common/config.h" + #include "common/debug.h" ++#include "include/str_list.h" + + #define dout_subsys ceph_subsys_auth + #undef dout_prefix +@@ -35,6 +36,11 @@ using ceph::bufferlist; + using ceph::decode; + using ceph::encode; + ++bool CephxServiceHandler::cipher_is_allowed(int cipher) const ++{ ++ return key_server->is_cipher_allowed(cipher); ++} ++ + int CephxServiceHandler::do_start_session( + bool is_new_global_id, + bufferlist *result_bl, +@@ -177,6 +183,14 @@ int CephxServiceHandler::handle_request( + break; + } + ++ if (!cipher_is_allowed(eauth.key.get_type())) { ++ ldout(cct, 0) << "authentication failed for " << entity_name ++ << ": key cipher type " << eauth.key.get_type() ++ << " is not permitted by auth_allowed_ciphers" << dendl; ++ ret = -EACCES; ++ break; ++ } ++ + if (!server_challenge) { + ret = -EACCES; + break; +@@ -243,7 +257,7 @@ int CephxServiceHandler::handle_request( + info.ticket.init_timestamps(ceph_clock_now(), ttl); + info.validity.set_from_double(ttl); + +- key_server->generate_secret(session_key); ++ key_server->generate_secret(session_key, eauth.key.get_type()); + + info.session_key = session_key; + if (psession_key) { +@@ -285,7 +299,8 @@ int CephxServiceHandler::handle_request( + connection_secret_required_len); + } + std::string err; +- if (encode_encrypt(cct, *pconnection_secret, session_key, cbl, ++ if (encode_encrypt(cct, *pconnection_secret, session_key, ++ CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET, cbl, + err)) { + lderr(cct) << __func__ << " failed to encrypt connection secret, " + << err << dendl; +diff --git a/src/auth/cephx/CephxServiceHandler.h b/src/auth/cephx/CephxServiceHandler.h +index e6e093ee4..e75818d91 100644 +--- a/src/auth/cephx/CephxServiceHandler.h ++++ b/src/auth/cephx/CephxServiceHandler.h +@@ -49,6 +49,9 @@ private: + bool& should_enc_ticket); + void build_cephx_response_header(int request_type, int status, + ceph::buffer::list& bl); ++ ++ /// true if cipher is allowed by the current monitor-map policy ++ bool cipher_is_allowed(int cipher) const; + }; + + #endif +diff --git a/src/auth/cephx/CephxSessionHandler.cc b/src/auth/cephx/CephxSessionHandler.cc +index 6b2712568..9616bd8e2 100644 +--- a/src/auth/cephx/CephxSessionHandler.cc ++++ b/src/auth/cephx/CephxSessionHandler.cc +@@ -101,27 +101,47 @@ int CephxSessionHandler::_calc_signature(Message *m, uint64_t *psig) + ceph_le32(header.seq) + }; + +- char exp_buf[CryptoKey::get_max_outbuf_size(sizeof(sigblock))]; +- +- try { +- const CryptoKey::in_slice_t in { +- sizeof(sigblock), +- reinterpret_cast(&sigblock) +- }; +- const CryptoKey::out_slice_t out { +- sizeof(exp_buf), +- reinterpret_cast(&exp_buf) +- }; +- key.encrypt(cct, in, out); +- } catch (std::exception& e) { +- lderr(cct) << __func__ << " failed to encrypt signature block" << dendl; +- return -1; ++ if (key.get_type() <= CEPH_CRYPTO_AES) { ++ char exp_buf[CryptoKey::get_max_outbuf_size(sizeof(sigblock))]; ++ ++ try { ++ const CryptoKey::in_slice_t in { ++ sizeof(sigblock), ++ reinterpret_cast(&sigblock) ++ }; ++ const CryptoKey::out_slice_t out { ++ sizeof(exp_buf), ++ reinterpret_cast(&exp_buf) ++ }; ++ key.encrypt(cct, in, out); ++ } catch (std::exception& e) { ++ lderr(cct) << __func__ << " failed to encrypt signature block" << dendl; ++ return -1; ++ } ++ ++ ceph_le64 blocks[4]; ++ static_assert(sizeof(blocks) <= sizeof(exp_buf)); ++ std::memcpy(blocks, exp_buf, sizeof(blocks)); ++ *psig = blocks[0] ^ blocks[1] ^ blocks[2] ^ blocks[3]; ++ } else { ++ sha256_digest_t exp_buf; ++ ++ try { ++ const CryptoKey::in_slice_t in { ++ sizeof(sigblock), ++ reinterpret_cast(&sigblock) ++ }; ++ exp_buf = key.hmac_sha256(cct, in); ++ } catch (std::exception& e) { ++ lderr(cct) << __func__ << " failed to encrypt signature block" << dendl; ++ return -1; ++ } ++ ++ ceph_le64 blocks[4]; ++ static_assert(sizeof(blocks) == sizeof(exp_buf)); ++ std::memcpy(blocks, &exp_buf, sizeof(blocks)); ++ *psig = blocks[0] ^ blocks[1] ^ blocks[2] ^ blocks[3]; + } +- +- struct enc { +- ceph_le64 a, b, c, d; +- } *penc = reinterpret_cast(exp_buf); +- *psig = penc->a ^ penc->b ^ penc->c ^ penc->d; + } + + ldout(cct, 10) << __func__ << " seq " << m->get_seq() +diff --git a/src/ceph_mon.cc b/src/ceph_mon.cc +index 96d71ed08..67d3f9778 100644 +--- a/src/ceph_mon.cc ++++ b/src/ceph_mon.cc +@@ -216,6 +216,8 @@ static void usage() + << " write the monmap to the local monitor store and exit\n" + << " --extract-monmap \n" + << " extract the monmap from the local monitor store and exit\n" ++ << " --use-mon-keyring\n" ++ << " use the local mon keyring as authoritative for recovery\n" + << " --mon-data \n" + << " where the mon store and keyring are located\n" + << " --set-crush-location =" +@@ -258,6 +260,7 @@ int main(int argc, const char **argv) + bool compact = false; + bool force_sync = false; + bool yes_really = false; ++ bool use_mon_keyring = false; + std::string osdmapfn, inject_monmap, extract_monmap, crush_loc; + + auto args = argv_to_vec(argc, argv); +@@ -334,6 +337,8 @@ int main(int argc, const char **argv) + force_sync = true; + } else if (ceph_argparse_flag(args, i, "--yes-i-really-mean-it", (char*)NULL)) { + yes_really = true; ++ } else if (ceph_argparse_flag(args, i, "--use-mon-keyring", (char*)NULL)) { ++ use_mon_keyring = true; + } else if (ceph_argparse_witharg(args, i, &val, "--osdmap", (char*)NULL)) { + osdmapfn = val; + } else if (ceph_argparse_witharg(args, i, &val, "--inject_monmap", (char*)NULL)) { +@@ -872,6 +877,10 @@ int main(int argc, const char **argv) + *_dout << dendl; + } + ++ if (use_mon_keyring) { ++ mon->use_keyring_as_authoritative(); ++ } ++ + err = mon->preinit(); + if (err < 0) { + derr << "failed to initialize" << dendl; +diff --git a/src/ceph_osd.cc b/src/ceph_osd.cc +index 9e85923ab..5f08dc23f 100644 +--- a/src/ceph_osd.cc ++++ b/src/ceph_osd.cc +@@ -347,7 +347,7 @@ int main(int argc, const char **argv) + keyring.get_auth(ename, eauth)) { + derr << "already have key in keyring " << keyring_path << dendl; + } else { +- eauth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ eauth.key.create(g_ceph_context, CEPH_CRYPTO_AES256KRB5); + keyring.add(ename, eauth); + bufferlist bl; + keyring.encode_plaintext(bl); +diff --git a/src/common/ceph_context.cc b/src/common/ceph_context.cc +index 68b92c45d..812770e65 100644 +--- a/src/common/ceph_context.cc ++++ b/src/common/ceph_context.cc +@@ -717,6 +717,7 @@ CephContext::CephContext(uint32_t module_type_, + _heartbeat_map(NULL), + _crypto_none(NULL), + _crypto_aes(NULL), ++ _crypto_aes256krb5(NULL), + _plugin_registry(NULL), + #ifdef CEPH_DEBUG_MUTEX + _lockdep_obs(NULL), +@@ -778,6 +779,7 @@ CephContext::CephContext(uint32_t module_type_, + + _crypto_none = CryptoHandler::create(CEPH_CRYPTO_NONE); + _crypto_aes = CryptoHandler::create(CEPH_CRYPTO_AES); ++ _crypto_aes256krb5 = CryptoHandler::create(CEPH_CRYPTO_AES256KRB5); + _crypto_random.reset(new CryptoRandom()); + + lookup_or_create_singleton_object("mempool_obs", false, this); +@@ -826,6 +828,7 @@ CephContext::~CephContext() + + delete _crypto_none; + delete _crypto_aes; ++ delete _crypto_aes256krb5; + if (_crypto_inited > 0) { + ceph_assert(_crypto_inited == 1); // or else someone explicitly did + // init but not shutdown +@@ -1012,6 +1015,8 @@ CryptoHandler *CephContext::get_crypto_handler(int type) + return _crypto_none; + case CEPH_CRYPTO_AES: + return _crypto_aes; ++ case CEPH_CRYPTO_AES256KRB5: ++ return _crypto_aes256krb5; + default: + return NULL; + } +diff --git a/src/common/ceph_context.h b/src/common/ceph_context.h +index f18776478..b3b3ac2f6 100644 +--- a/src/common/ceph_context.h ++++ b/src/common/ceph_context.h +@@ -355,6 +355,7 @@ private: + // crypto + CryptoHandler *_crypto_none; + CryptoHandler *_crypto_aes; ++ CryptoHandler *_crypto_aes256krb5; + std::unique_ptr _crypto_random; + + // experimental +diff --git a/src/common/options/mon.yaml.in b/src/common/options/mon.yaml.in +index 8300e31d0..e7d1ecfc0 100644 +--- a/src/common/options/mon.yaml.in ++++ b/src/common/options/mon.yaml.in +@@ -2,6 +2,16 @@ + --- + + options: ++- name: mon_auth_emergency_allowed_ciphers ++ type: str ++ level: advanced ++ desc: Temporarily add CephX ciphers allowed by the monitor map ++ default: "" ++ services: ++ - mon ++ flags: ++ - startup ++ - no_mon_update + - name: osd_crush_update_weight_set + type: bool + level: advanced +diff --git a/src/crimson/mon/MonClient.cc b/src/crimson/mon/MonClient.cc +index 7be09915a..dc0507650 100644 +--- a/src/crimson/mon/MonClient.cc ++++ b/src/crimson/mon/MonClient.cc +@@ -83,6 +83,7 @@ public: + KeyStore& get_keys(); + seastar::future<> renew_tickets(); + seastar::future<> renew_rotating_keyring(); ++ seastar::future<> wipe_secrets_and_tickets(); + + crimson::net::ConnectionRef get_conn(); + +@@ -177,6 +178,16 @@ seastar::future<> Connection::renew_rotating_keyring() + }); + } + ++seastar::future<> Connection::wipe_secrets_and_tickets() ++{ ++ rotating_keyring->wipe(); ++ if (auth) { ++ auth->invalidate_all_tickets(); ++ } ++ return seastar::when_all_succeed( ++ renew_tickets(), renew_rotating_keyring()).discard_result(); ++} ++ + AuthAuthorizer* Connection::get_authorizer(entity_type_t peer) const + { + if (auth) { +@@ -771,6 +782,7 @@ int Client::handle_auth_bad_method(crimson::net::Connection &conn, + seastar::future<> Client::handle_monmap(crimson::net::Connection &conn, + Ref m) + { ++ const auto old_auth_epoch = monmap.auth_epoch; + monmap.decode(m->monmapbl); + const auto peer_addr = conn.get_peer_addr(); + auto cur_mon = monmap.get_name(peer_addr); +@@ -780,6 +792,10 @@ seastar::future<> Client::handle_monmap(crimson::net::Connection &conn, + + if (monmap.get_addr_name(peer_addr, cur_mon)) { + if (active_con) { ++ if (old_auth_epoch < monmap.auth_epoch) { ++ logger().info("auth epoch changed: invalidating tickets and secrets"); ++ return active_con->wipe_secrets_and_tickets(); ++ } + logger().info("handle_monmap: renewing tickets"); + return seastar::when_all_succeed( + active_con->renew_tickets(), +diff --git a/src/crimson/osd/main.cc b/src/crimson/osd/main.cc +index 1e817415d..4c96a1017 100644 +--- a/src/crimson/osd/main.cc ++++ b/src/crimson/osd/main.cc +@@ -56,7 +56,7 @@ seastar::future<> make_keyring() + return seastar::now(); + } else { + CephContext temp_cct{}; +- auth.key.create(&temp_cct, CEPH_CRYPTO_AES); ++ auth.key.create(&temp_cct, CEPH_CRYPTO_AES256KRB5); + keyring.add(name, auth); + bufferlist bl; + keyring.encode_plaintext(bl); +diff --git a/src/include/ceph_fs.h b/src/include/ceph_fs.h +index ad604fe9f..082f02a48 100644 +--- a/src/include/ceph_fs.h ++++ b/src/include/ceph_fs.h +@@ -92,8 +92,9 @@ struct ceph_dir_layout { + } __attribute__ ((packed)); + + /* crypto algorithms */ +-#define CEPH_CRYPTO_NONE 0x0 +-#define CEPH_CRYPTO_AES 0x1 ++#define CEPH_CRYPTO_NONE 0x0 ++#define CEPH_CRYPTO_AES 0x1 ++#define CEPH_CRYPTO_AES256KRB5 0x2 /* AES256-CTS-HMAC384-192 */ + + #define CEPH_AES_IV "cephsageyudagreg" + +diff --git a/src/mon/AuthMonitor.cc b/src/mon/AuthMonitor.cc +index 88627a817..9ff513b14 100644 +--- a/src/mon/AuthMonitor.cc ++++ b/src/mon/AuthMonitor.cc +@@ -12,6 +12,7 @@ + * + */ + ++#include + #include + + #include "mon/AuthMonitor.h" +@@ -272,7 +273,7 @@ void _generate_bootstrap_keys( + EntityName name; + name.from_str("client." + p.first); + EntityAuth auth; +- auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ auth.key.create(g_ceph_context, CEPH_CRYPTO_AES256KRB5); + auth.caps = p.second; + + auth_lst->push_back(make_pair(name, auth)); +@@ -869,6 +870,8 @@ bool AuthMonitor::preprocess_command(MonOpRequestRef op) + string prefix; + cmd_getval(cmdmap, "prefix", prefix); + if (prefix == "auth add" || ++ prefix == "auth rotate" || ++ prefix == "auth wipe-rotating-service-keys" || + prefix == "auth del" || + prefix == "auth rm" || + prefix == "auth get-or-create" || +@@ -963,6 +966,18 @@ bool AuthMonitor::preprocess_command(MonOpRequestRef op) + } + r = 0; + goto done; ++ } else if (prefix == "auth dump-keys") { ++ if (!f) { ++ ss << "auth dump-keys requires a structured output format"; ++ r = -EINVAL; ++ goto done; ++ } ++ f->open_object_section("keys"); ++ mon.key_server.dump_key_metadata(f.get()); ++ f->close_section(); ++ f->flush(rdata); ++ r = 0; ++ goto done; + } else { + ss << "invalid command"; + r = -EINVAL; +@@ -988,6 +1003,19 @@ int AuthMonitor::import_keyring(KeyRing& keyring) + for (map::iterator p = keyring.get_keys().begin(); + p != keyring.get_keys().end(); + ++p) { ++ std::ostringstream error; ++ int r = validate_cipher_type(p->second.key.get_type(), error); ++ if (r < 0) { ++ dout(0) << "import: " << error.str() << dendl; ++ return r; ++ } ++ if (!p->second.pending_key.empty()) { ++ r = validate_cipher_type(p->second.pending_key.get_type(), error); ++ if (r < 0) { ++ dout(0) << "import pending key: " << error.str() << dendl; ++ return r; ++ } ++ } + if (p->second.caps.empty()) { + dout(0) << "import: no caps supplied" << dendl; + return -EINVAL; +@@ -1382,6 +1410,40 @@ bool AuthMonitor::valid_caps(const vector& caps, ostream *out) + return true; + } + ++int AuthMonitor::validate_cipher_type(int cipher, std::ostream& out) const ++{ ++ if (cipher < 0 || cipher == CEPH_CRYPTO_NONE || ++ !CryptoManager::crypto_type_supported(cipher)) { ++ out << "invalid key type " << cipher; ++ return -EINVAL; ++ } ++ if (cipher == CEPH_CRYPTO_AES256KRB5 && ++ !mon.get_quorum_mon_features().contains_all( ++ ceph::features::mon::FEATURE_CEPHX_AUTH_AES256K)) { ++ out << "all monitors must support cephx_auth_aes256k"; ++ return -ENOTSUP; ++ } ++ const auto& allowed = mon.monmap->auth_allowed_ciphers; ++ if (std::find(allowed.begin(), allowed.end(), cipher) == allowed.end()) { ++ out << "key type " << CryptoManager::get_key_type_name(cipher) ++ << " is not permitted by auth_allowed_ciphers"; ++ return -EPERM; ++ } ++ return cipher; ++} ++ ++int AuthMonitor::get_cipher_type(const cmdmap_t& cmdmap, ++ std::ostream& out) const ++{ ++ string requested; ++ cmd_getval(cmdmap, "key_type", requested); ++ int cipher = mon.monmap->auth_preferred_cipher; ++ if (!requested.empty() && requested != "preferred") { ++ cipher = CryptoManager::get_key_type(requested); ++ } ++ return validate_cipher_type(cipher, out); ++} ++ + bool AuthMonitor::prepare_command(MonOpRequestRef op) + { + auto m = op->get_req(); +@@ -1512,6 +1574,16 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + err = -EINVAL; + goto done; + } ++ err = validate_cipher_type(new_inc.key.get_type(), ss); ++ if (err < 0) { ++ goto done; ++ } ++ if (!new_inc.pending_key.empty()) { ++ err = validate_cipher_type(new_inc.pending_key.get_type(), ss); ++ if (err < 0) { ++ goto done; ++ } ++ } + if (!new_caps.empty() && !new_inc.caps.empty()) { + ss << "caps cannot be specified both in keyring and in command"; + err = -EINVAL; +@@ -1541,7 +1613,12 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + if (!has_keyring) { + dout(10) << "AuthMonitor::prepare_command generating random key for " + << auth_inc.name << dendl; +- new_inc.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ int key_type = get_cipher_type(cmdmap, ss); ++ if (key_type < 0) { ++ err = key_type; ++ goto done; ++ } ++ new_inc.key.create(g_ceph_context, key_type); + } + new_inc.caps = new_caps; + +@@ -1596,7 +1673,12 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + auth_inc.op = KeyServerData::AUTH_INC_ADD; + auth_inc.name = entity; + auth_inc.auth = entity_auth; +- auth_inc.auth.pending_key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ int key_type = get_cipher_type(cmdmap, ss); ++ if (key_type < 0) { ++ err = key_type; ++ goto done; ++ } ++ auth_inc.auth.pending_key.create(g_ceph_context, key_type); + push_cephx_inc(auth_inc); + kr.add(entity, auth_inc.auth.key, auth_inc.auth.pending_key); + push_cephx_inc(auth_inc); +@@ -1712,7 +1794,12 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + KeyServerData::Incremental auth_inc; + auth_inc.op = KeyServerData::AUTH_INC_ADD; + auth_inc.name = entity; +- auth_inc.auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ int key_type = get_cipher_type(cmdmap, ss); ++ if (key_type < 0) { ++ err = key_type; ++ goto done; ++ } ++ auth_inc.auth.key.create(g_ceph_context, key_type); + auth_inc.auth.caps = wanted_caps; + + push_cephx_inc(auth_inc); +@@ -1856,7 +1943,12 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + KeyServerData::Incremental auth_inc; + auth_inc.op = KeyServerData::AUTH_INC_ADD; + auth_inc.name = entity; +- auth_inc.auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ int key_type = get_cipher_type(cmdmap, ss); ++ if (key_type < 0) { ++ err = key_type; ++ goto done; ++ } ++ auth_inc.auth.key.create(g_ceph_context, key_type); + auth_inc.auth.caps = wanted_caps; + + push_cephx_inc(auth_inc); +@@ -1902,6 +1994,76 @@ bool AuthMonitor::prepare_command(MonOpRequestRef op) + wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, + get_last_committed() + 1)); + return true; ++ } else if (prefix == "auth wipe-rotating-service-keys") { ++ if (!mon.get_quorum_mon_features().contains_all( ++ ceph::features::mon::FEATURE_CEPHX_AUTH_AES256K)) { ++ ss << "all monitors must support cephx_auth_aes256k"; ++ err = -ENOTSUP; ++ goto done; ++ } ++ if (mon.monmap->auth_service_cipher != CEPH_CRYPTO_AES256KRB5) { ++ ss << "auth_service_cipher must be aes256k before wiping service keys"; ++ err = -EPERM; ++ goto done; ++ } ++ if (!mon.monmon()->is_writeable()) { ++ mon.monmon()->wait_for_writeable( ++ op, new PaxosService::C_RetryMessage(this, op)); ++ return false; ++ } ++ ++ paxos.plug(); ++ KeyServerData::Incremental rot_inc; ++ rot_inc.op = KeyServerData::AUTH_INC_SET_ROTATING; ++ if (!mon.key_server.prepare_rotating_update(rot_inc.rotating_bl, true)) { ++ paxos.unplug(); ++ ss << "failed to replace rotating service keys"; ++ err = -EIO; ++ goto done; ++ } ++ push_cephx_inc(rot_inc); ++ const auto next_epoch = get_last_committed() + 1; ++ mon.monmon()->bump_auth_epoch(next_epoch); ++ request_proposal(mon.monmon()); ++ paxos.unplug(); ++ ++ rs = "wiped rotating service keys"; ++ wait_for_commit(op, new Monitor::C_Command( ++ mon, op, 0, rs, rdata, next_epoch)); ++ return true; ++ } else if (prefix == "auth rotate" && !entity_name.empty()) { ++ EntityAuth entity_auth; ++ if (!mon.key_server.get_auth(entity, entity_auth)) { ++ ss << "entity " << entity << " does not exist"; ++ err = -ENOENT; ++ goto done; ++ } ++ int key_type = get_cipher_type(cmdmap, ss); ++ if (key_type < 0) { ++ err = key_type; ++ goto done; ++ } ++ if (entity_auth.key.create(g_ceph_context, key_type) < 0) { ++ ss << "failed to generate key"; ++ err = -EIO; ++ goto done; ++ } ++ KeyServerData::Incremental auth_inc; ++ auth_inc.op = KeyServerData::AUTH_INC_ADD; ++ auth_inc.name = entity; ++ auth_inc.auth = entity_auth; ++ push_cephx_inc(auth_inc); ++ ++ KeyRing keyring; ++ keyring.add(entity, entity_auth); ++ if (f) { ++ keyring.encode_formatted("auth", f.get(), rdata); ++ } else { ++ keyring.encode_plaintext(rdata); ++ } ++ wait_for_commit(op, new Monitor::C_Command( ++ mon, op, 0, rs, rdata, get_last_committed() + 1)); ++ return true; + } else if ((prefix == "auth del" || prefix == "auth rm") && + !entity_name.empty()) { + KeyServerData::Incremental auth_inc; +@@ -2069,7 +2231,7 @@ bool AuthMonitor::_upgrade_format_to_luminous() + EntityName name = bootstrap_mgr_name; + EntityAuth auth; + encode("allow profile bootstrap-mgr", auth.caps["mon"]); +- auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ auth.key.create(g_ceph_context, mon.monmap->auth_preferred_cipher); + add_entity(name, auth); + changed = true; + } +diff --git a/src/mon/AuthMonitor.h b/src/mon/AuthMonitor.h +index d49a862d3..bc3cb6dc1 100644 +--- a/src/mon/AuthMonitor.h ++++ b/src/mon/AuthMonitor.h +@@ -134,6 +134,8 @@ private: + return valid_caps(type, v, out); + } + bool valid_caps(const std::vector& caps, std::ostream *out); ++ int validate_cipher_type(int cipher, std::ostream& out) const; ++ int get_cipher_type(const cmdmap_t& cmdmap, std::ostream& out) const; + + void on_active() override; + bool should_propose(double& delay) override; +diff --git a/src/mon/MonClient.cc b/src/mon/MonClient.cc +index 76ae4be04..0b5c75d92 100644 +--- a/src/mon/MonClient.cc ++++ b/src/mon/MonClient.cc +@@ -406,6 +406,7 @@ void MonClient::handle_monmap(MMonMap *m) + auto con_addrs = m->get_source_addrs(); + string old_name = monmap.get_name(con_addrs); + const auto old_epoch = monmap.get_epoch(); ++ const auto old_auth_epoch = monmap.auth_epoch; + + auto p = m->monmapbl.cbegin(); + decode(monmap, p); +@@ -443,6 +444,10 @@ void MonClient::handle_monmap(MMonMap *m) + + cct->set_mon_addrs(monmap); + ++ if (old_auth_epoch < monmap.auth_epoch) { ++ _wipe_secrets_and_tickets(); ++ } ++ + sub.got("monmap", monmap.get_epoch()); + map_cond.notify_all(); + want_monmap = false; +@@ -636,6 +641,17 @@ int MonClient::call( + return 0; + } + ++void MonClient::_wipe_secrets_and_tickets() ++{ ++ if (rotating_secrets) { ++ rotating_secrets->wipe(); ++ } ++ if (auth) { ++ auth->invalidate_all_tickets(); ++ } ++ _check_auth_tickets(); ++} ++ + void MonClient::handle_auth(MAuthReply *m) + { + ceph_assert(ceph_mutex_is_locked(monc_lock)); +diff --git a/src/mon/MonClient.h b/src/mon/MonClient.h +index b72bf1f65..5a971dd35 100644 +--- a/src/mon/MonClient.h ++++ b/src/mon/MonClient.h +@@ -106,6 +106,7 @@ public: + } + + private: ++ void _wipe_secrets_and_tickets(); + int _negotiate(MAuthReply *m, + const EntityName& entity_name, + uint32_t want_keys, +diff --git a/src/mon/MonCommands.h b/src/mon/MonCommands.h +index 662527de1..8e37b419f 100644 +--- a/src/mon/MonCommands.h ++++ b/src/mon/MonCommands.h +@@ -159,18 +159,21 @@ COMMAND("auth import", "auth import: read keyring file from -i ", + "auth", "rwx") + COMMAND("auth add " + "name=entity,type=CephString " +- "name=caps,type=CephString,n=N,req=false", ++ "name=caps,type=CephString,n=N,req=false -- " ++ "name=key_type,type=CephString,req=false", + "add auth info for from input file, or random key if no " + "input is given, and/or any caps specified in the command", + "auth", "rwx") + COMMAND("auth get-or-create-key " + "name=entity,type=CephString " +- "name=caps,type=CephString,n=N,req=false", ++ "name=caps,type=CephString,n=N,req=false -- " ++ "name=key_type,type=CephString,req=false", + "get, or add, key for from system/caps pairs specified in the command. If key already exists, any given caps must match the existing caps for that key.", + "auth", "rwx") + COMMAND("auth get-or-create " + "name=entity,type=CephString " +- "name=caps,type=CephString,n=N,req=false", ++ "name=caps,type=CephString,n=N,req=false -- " ++ "name=key_type,type=CephString,req=false", + "add auth info for from input file, or random key if no input given, and/or any caps specified in the command", + "auth", "rwx") + COMMAND("auth get-or-create-pending " +@@ -185,6 +188,17 @@ COMMAND("auth commit-pending " + "name=entity,type=CephString", + "rotate pending key into active position", + "auth", "rwx") ++COMMAND("auth rotate " ++ "name=entity,type=CephString " ++ "name=key_type,type=CephString,req=false", ++ "rotate an entity key", ++ "auth", "rwx") ++COMMAND("auth dump-keys", ++ "dump authentication key type metadata", ++ "auth", "rx") ++COMMAND("auth wipe-rotating-service-keys", ++ "replace rotating service keys and invalidate cached tickets", ++ "auth", "rwx") + COMMAND("fs authorize " + "name=filesystem,type=CephString " + "name=entity,type=CephString " +@@ -517,6 +531,11 @@ COMMAND("mon set-weight " + "name=weight,type=CephInt,range=0|65535", + "set the weight for the specified mon", + "mon", "rw") ++COMMAND("mon set " ++ "name=name,type=CephChoices,strings=auth_service_cipher|auth_allowed_ciphers|auth_preferred_cipher " ++ "name=value,type=CephString", ++ "set a CephX cipher policy in the monitor map", ++ "mon", "rw") + COMMAND("mon enable-msgr2", + "enable the msgr2 protocol on port 3300", + "mon", "rw") +diff --git a/src/mon/MonMap.cc b/src/mon/MonMap.cc +index 3dbc3106f..25bc65f59 100644 +--- a/src/mon/MonMap.cc ++++ b/src/mon/MonMap.cc +@@ -4,6 +4,7 @@ + #include "MonMap.h" + + #include ++#include + #include + #include + #include +@@ -17,6 +18,7 @@ + #endif + + #include "common/Formatter.h" ++#include "auth/Crypto.h" + + #include "include/ceph_features.h" + #include "include/addr_parsing.h" +@@ -217,7 +219,7 @@ void MonMap::encode(ceph::buffer::list& blist, uint64_t con_features) const + return; + } + +- ENCODE_START(9, 6, blist); ++ ENCODE_START(10, 6, blist); + ceph::encode_raw(fsid, blist); + encode(epoch, blist); + encode(last_changed, blist); +@@ -234,13 +236,19 @@ void MonMap::encode(ceph::buffer::list& blist, uint64_t con_features) const + encode(stretch_mode_enabled, blist); + encode(tiebreaker_mon, blist); + encode(stretch_marked_down_mons, blist); ++ encode(auth_epoch, blist); ++ encode(auth_service_cipher, blist); ++ auto allowed_ciphers = auth_allowed_ciphers; ++ std::sort(allowed_ciphers.begin(), allowed_ciphers.end()); ++ encode(allowed_ciphers, blist); ++ encode(auth_preferred_cipher, blist); + ENCODE_FINISH(blist); + } + + void MonMap::decode(ceph::buffer::list::const_iterator& p) + { + map mon_addr; +- DECODE_START_LEGACY_COMPAT_LEN_16(9, 3, 3, p); ++ DECODE_START_LEGACY_COMPAT_LEN_16(10, 3, 3, p); + ceph::decode_raw(fsid, p); + decode(epoch, p); + if (struct_v == 1) { +@@ -298,6 +306,18 @@ void MonMap::decode(ceph::buffer::list::const_iterator& p) + tiebreaker_mon = ""; + stretch_marked_down_mons.clear(); + } ++ if (struct_v >= 10) { ++ decode(auth_epoch, p); ++ decode(auth_service_cipher, p); ++ decode(auth_allowed_ciphers, p); ++ decode(auth_preferred_cipher, p); ++ std::sort(auth_allowed_ciphers.begin(), auth_allowed_ciphers.end()); ++ } else { ++ auth_epoch = 0; ++ auth_service_cipher = CEPH_CRYPTO_AES; ++ auth_allowed_ciphers = {CEPH_CRYPTO_AES, CEPH_CRYPTO_AES256KRB5}; ++ auth_preferred_cipher = CEPH_CRYPTO_AES; ++ } + calc_addr_mons(); + DECODE_FINISH(p); + } +@@ -419,6 +439,23 @@ void MonMap::dump(Formatter *f) const + f->dump_bool("stretch_mode", stretch_mode_enabled); + f->dump_string("tiebreaker_mon", tiebreaker_mon); + f->dump_stream("removed_ranks: ") << removed_ranks; ++ f->dump_unsigned("auth_epoch", auth_epoch); ++ f->open_object_section("auth_service_cipher"); ++ f->dump_string("name", CryptoManager::get_key_type_name(auth_service_cipher)); ++ f->dump_int("value", auth_service_cipher); ++ f->close_section(); ++ f->open_array_section("auth_allowed_ciphers"); ++ for (auto cipher : auth_allowed_ciphers) { ++ f->open_object_section("cipher"); ++ f->dump_string("name", CryptoManager::get_key_type_name(cipher)); ++ f->dump_int("value", cipher); ++ f->close_section(); ++ } ++ f->close_section(); ++ f->open_object_section("auth_preferred_cipher"); ++ f->dump_string("name", CryptoManager::get_key_type_name(auth_preferred_cipher)); ++ f->dump_int("value", auth_preferred_cipher); ++ f->close_section(); + f->open_object_section("features"); + persistent_features.dump(f, "persistent"); + optional_features.dump(f, "optional"); +@@ -859,6 +896,7 @@ seastar::future<> MonMap::build_monmap(const crimson::common::ConfigProxy& conf, + + seastar::future<> MonMap::build_initial(const crimson::common::ConfigProxy& conf, bool for_mkfs) + { ++ auth_epoch = for_mkfs ? 0 : std::numeric_limits::max(); + // mon_host_override? + if (maybe_init_with_mon_host(conf.get_val("mon_host_override"), + for_mkfs)) { +@@ -939,6 +977,7 @@ int MonMap::build_initial(CephContext *cct, bool for_mkfs, ostream& errout) + { + lgeneric_dout(cct, 1) << __func__ << " for_mkfs: " << for_mkfs << dendl; + const auto& conf = cct->_conf; ++ auth_epoch = for_mkfs ? 0 : std::numeric_limits::max(); + + // mon_host_override? + auto mon_host_override = conf.get_val("mon_host_override"); +diff --git a/src/mon/MonMap.h b/src/mon/MonMap.h +index 7bcec9073..57616119d 100644 +--- a/src/mon/MonMap.h ++++ b/src/mon/MonMap.h +@@ -23,6 +23,7 @@ + #include "common/ceph_releases.h" + + #include "include/err.h" ++#include "include/ceph_fs.h" + #include "include/types.h" + + #include "mon/mon_types.h" +@@ -104,6 +105,11 @@ class MonMap { + std::map addr_mons; + + std::vector ranks; ++ epoch_t auth_epoch = 0; ++ int auth_service_cipher = CEPH_CRYPTO_AES; ++ std::vector auth_allowed_ciphers{ ++ CEPH_CRYPTO_AES, CEPH_CRYPTO_AES256KRB5}; ++ int auth_preferred_cipher = CEPH_CRYPTO_AES; + /* ranks which were removed when this map took effect. + There should only be one at a time, but leave support + for arbitrary numbers just to be safe. */ +diff --git a/src/mon/Monitor.cc b/src/mon/Monitor.cc +index 8804c50a3..33d5a3141 100644 +--- a/src/mon/Monitor.cc ++++ b/src/mon/Monitor.cc +@@ -13,6 +13,7 @@ + */ + + ++#include + #include + #include + #include +@@ -204,6 +205,7 @@ Monitor::Monitor(CephContext* cct_, string nm, MonitorDBStore *s, + routed_request_tid(0), + op_tracker(cct, g_conf().get_val("mon_enable_op_tracker"), 1) + { ++ refresh_auth_crypto_policy(); + clog = log_client.create_channel(CLOG_CHANNEL_CLUSTER); + audit_clog = log_client.create_channel(CLOG_CHANNEL_AUDIT); + +@@ -535,6 +537,7 @@ CompatSet Monitor::get_supported_features() + compat.incompat.insert(CEPH_MON_FEATURE_INCOMPAT_PACIFIC); + compat.incompat.insert(CEPH_MON_FEATURE_INCOMPAT_QUINCY); + compat.incompat.insert(CEPH_MON_FEATURE_INCOMPAT_REEF); ++ compat.incompat.insert(CEPH_MON_FEATURE_INCOMPAT_CEPHX_AUTH_AES256K); + return compat; + } + +@@ -686,6 +689,22 @@ void Monitor::update_log_clients() + audit_clog->parse_client_options(g_ceph_context); + } + ++void Monitor::refresh_auth_crypto_policy() ++{ ++ auto allowed = monmap->auth_allowed_ciphers; ++ vector emergency; ++ get_str_vec(cct->_conf.get_val( ++ "mon_auth_emergency_allowed_ciphers"), ", ", emergency); ++ for (const auto& name : emergency) { ++ int cipher = CryptoManager::get_key_type(name); ++ if (cipher > CEPH_CRYPTO_NONE && ++ std::find(allowed.begin(), allowed.end(), cipher) == allowed.end()) { ++ allowed.push_back(cipher); ++ } ++ } ++ key_server.set_crypto_policy(monmap->auth_service_cipher, allowed); ++} ++ + int Monitor::sanitize_options() + { + int r = 0; +@@ -2529,6 +2548,11 @@ void Monitor::apply_monmap_to_compatset_features() + ceph_assert(HAVE_FEATURE(quorum_con_features, SERVER_REEF)); + new_features.incompat.insert(CEPH_MON_FEATURE_INCOMPAT_REEF); + } ++ if (monmap_features.contains_all( ++ ceph::features::mon::FEATURE_CEPHX_AUTH_AES256K)) { ++ new_features.incompat.insert( ++ CEPH_MON_FEATURE_INCOMPAT_CEPHX_AUTH_AES256K); ++ } + + dout(5) << __func__ << dendl; + _apply_compatset_features(new_features); +@@ -6293,8 +6317,9 @@ bool Monitor::get_authorizer(int service_id, AuthAuthorizer **authorizer) + // mon to mon authentication uses the private monitor shared key and not the + // rotating key + CryptoKey secret; +- if (!keyring.get_secret(name, secret) && +- !key_server.get_secret(name, secret)) { ++ bool found = use_mon_keyring ? keyring.get_secret(name, secret) : ++ key_server.get_secret(name, secret); ++ if (!found) { + dout(0) << " couldn't get secret for mon service from keyring or keyserver" + << dendl; + stringstream ss, ds; +@@ -6308,7 +6333,8 @@ bool Monitor::get_authorizer(int service_id, AuthAuthorizer **authorizer) + } + + ret = key_server.build_session_auth_info( +- service_id, auth_ticket_info.ticket, secret, (uint64_t)-1, info); ++ service_id, auth_ticket_info.ticket, secret, (uint64_t)-1, ++ secret.get_type(), info); + if (ret < 0) { + dout(0) << __func__ << " failed to build mon session_auth_info " + << cpp_strerror(ret) << dendl; +@@ -6317,7 +6343,7 @@ bool Monitor::get_authorizer(int service_id, AuthAuthorizer **authorizer) + } else if (service_id == CEPH_ENTITY_TYPE_MGR) { + // mgr + ret = key_server.build_session_auth_info( +- service_id, auth_ticket_info.ticket, info); ++ service_id, auth_ticket_info.ticket, std::nullopt, info); + if (ret < 0) { + derr << __func__ << " failed to build mgr service session_auth_info " + << cpp_strerror(ret) << dendl; +@@ -6389,7 +6415,8 @@ int Monitor::handle_auth_request( + bool was_challenge = (bool)auth_meta->authorizer_challenge; + bool isvalid = ah->verify_authorizer( + cct, +- keyring, ++ use_mon_keyring ? static_cast(keyring) : ++ static_cast(key_server), + payload, + auth_meta->get_connection_secret_length(), + reply, +@@ -6631,6 +6658,7 @@ void Monitor::set_mon_crush_location(const string& loc) + + void Monitor::notify_new_monmap(bool can_change_external_state, bool remove_rank_elector) + { ++ refresh_auth_crypto_policy(); + if (need_set_crush_loc) { + auto my_info_i = monmap->mon_info.find(name); + if (my_info_i != monmap->mon_info.end() && +diff --git a/src/mon/Monitor.h b/src/mon/Monitor.h +index fa98d3b20..fe8c4ad70 100644 +--- a/src/mon/Monitor.h ++++ b/src/mon/Monitor.h +@@ -1037,6 +1037,10 @@ private: + const std::set &changed) override; + + void update_log_clients(); ++ void refresh_auth_crypto_policy(); ++ void use_keyring_as_authoritative() { ++ use_mon_keyring = true; ++ } + int sanitize_options(); + int preinit(); + int init(); +@@ -1110,6 +1114,7 @@ public: + } + + private: ++ bool use_mon_keyring = false; + ceph::coarse_mono_time const starttime = coarse_mono_clock::now(); + }; + +@@ -1128,6 +1133,7 @@ private: + #define CEPH_MON_FEATURE_INCOMPAT_PACIFIC CompatSet::Feature(13, "pacific ondisk layout") + #define CEPH_MON_FEATURE_INCOMPAT_QUINCY CompatSet::Feature(14, "quincy ondisk layout") + #define CEPH_MON_FEATURE_INCOMPAT_REEF CompatSet::Feature(15, "reef ondisk layout") ++#define CEPH_MON_FEATURE_INCOMPAT_CEPHX_AUTH_AES256K CompatSet::Feature(31, "cephx auth aes256k") + // make sure you add your feature to Monitor::get_supported_features + + +diff --git a/src/mon/MonmapMonitor.cc b/src/mon/MonmapMonitor.cc +index 1226c8a82..0227fb684 100644 +--- a/src/mon/MonmapMonitor.cc ++++ b/src/mon/MonmapMonitor.cc +@@ -13,6 +13,9 @@ + */ + + #include "MonmapMonitor.h" ++#include "auth/Crypto.h" ++#include ++#include + #include "Monitor.h" + #include "OSDMonitor.h" + #include "messages/MMonCommand.h" +@@ -815,6 +818,79 @@ bool MonmapMonitor::prepare_command(MonOpRequestRef op) + // output optional nevertheless, for auditing purposes. + << ", optional = " << pending_map.optional_features << dendl; + ++ } else if (prefix == "mon set") { ++ string name; ++ string value; ++ if (!cmd_getval(cmdmap, "name", name) || ++ !cmd_getval(cmdmap, "value", value)) { ++ err = -EINVAL; ++ goto reply_no_propose; ++ } ++ ++ auto require_cipher = [&](int cipher) { ++ return cipher != CEPH_CRYPTO_AES256KRB5 || ++ mon.get_quorum_mon_features().contains_all( ++ ceph::features::mon::FEATURE_CEPHX_AUTH_AES256K); ++ }; ++ ++ if (name == "auth_service_cipher" || ++ name == "auth_preferred_cipher") { ++ int cipher = CryptoManager::get_key_type(value); ++ if (cipher < 0 || cipher == CEPH_CRYPTO_NONE) { ++ ss << "invalid cipher " << value; ++ err = -EINVAL; ++ goto reply_no_propose; ++ } ++ if (!require_cipher(cipher)) { ++ ss << "all monitors must support cephx_auth_aes256k"; ++ err = -ENOTSUP; ++ goto reply_no_propose; ++ } ++ int& setting = name == "auth_service_cipher" ? ++ pending_map.auth_service_cipher : pending_map.auth_preferred_cipher; ++ if (setting == cipher) { ++ ss << "already set"; ++ err = 0; ++ goto reply_no_propose; ++ } ++ setting = cipher; ++ } else if (name == "auth_allowed_ciphers") { ++ vector names; ++ get_str_vec(value, ", ", names); ++ vector ciphers; ++ for (const auto& cipher_name : names) { ++ int cipher = CryptoManager::get_key_type(cipher_name); ++ if (cipher < 0 || cipher == CEPH_CRYPTO_NONE) { ++ ss << "invalid cipher " << cipher_name; ++ err = -EINVAL; ++ goto reply_no_propose; ++ } ++ if (!require_cipher(cipher)) { ++ ss << "all monitors must support cephx_auth_aes256k"; ++ err = -ENOTSUP; ++ goto reply_no_propose; ++ } ++ ciphers.push_back(cipher); ++ } ++ std::sort(ciphers.begin(), ciphers.end()); ++ ciphers.erase(std::unique(ciphers.begin(), ciphers.end()), ciphers.end()); ++ if (ciphers.empty()) { ++ ss << "at least one cipher must be allowed"; ++ err = -EINVAL; ++ goto reply_no_propose; ++ } ++ if (pending_map.auth_allowed_ciphers == ciphers) { ++ ss << "already set"; ++ err = 0; ++ goto reply_no_propose; ++ } ++ pending_map.auth_allowed_ciphers = std::move(ciphers); ++ } else { ++ ss << "unknown setting " << name; ++ err = -EINVAL; ++ goto reply_no_propose; ++ } ++ pending_map.last_changed = ceph_clock_now(); + } else if (prefix == "mon set-rank") { + string name; + int64_t rank; +@@ -1378,6 +1454,17 @@ bool MonmapMonitor::should_propose(double& delay) + return true; + } + ++epoch_t MonmapMonitor::bump_auth_epoch(epoch_t epoch) ++{ ++ ceph_assert(is_writeable()); ++ if (pending_map.auth_epoch == std::numeric_limits::max()) { ++ pending_map.auth_epoch = 0; ++ } ++ pending_map.auth_epoch = std::max(pending_map.auth_epoch, epoch); ++ pending_map.last_changed = ceph_clock_now(); ++ return pending_map.auth_epoch; ++} ++ + int MonmapMonitor::get_monmap(bufferlist &bl) + { + version_t latest_ver = get_last_committed(); +diff --git a/src/mon/MonmapMonitor.h b/src/mon/MonmapMonitor.h +index dc75e9620..cfa6ebe58 100644 +--- a/src/mon/MonmapMonitor.h ++++ b/src/mon/MonmapMonitor.h +@@ -63,6 +63,7 @@ class MonmapMonitor : public PaxosService { + bool prepare_command(MonOpRequestRef op); + + int get_monmap(ceph::buffer::list &bl); ++ epoch_t bump_auth_epoch(epoch_t epoch); + + /* + * Since monitors are pretty +diff --git a/src/mon/mon_types.h b/src/mon/mon_types.h +index 02aad5a0b..a0eaf7fa0 100644 +--- a/src/mon/mon_types.h ++++ b/src/mon/mon_types.h +@@ -489,6 +489,7 @@ namespace ceph { + constexpr mon_feature_t FEATURE_PINGING( (1ULL << 7)); + constexpr mon_feature_t FEATURE_QUINCY( (1ULL << 8)); + constexpr mon_feature_t FEATURE_REEF( (1ULL << 9)); ++ constexpr mon_feature_t FEATURE_CEPHX_AUTH_AES256K(1ULL << 31); + + constexpr mon_feature_t FEATURE_RESERVED( (1ULL << 63)); + constexpr mon_feature_t FEATURE_NONE( (0ULL)); +@@ -510,6 +511,7 @@ namespace ceph { + FEATURE_PINGING | + FEATURE_QUINCY | + FEATURE_REEF | ++ FEATURE_CEPHX_AUTH_AES256K | + FEATURE_NONE + ); + } +@@ -535,6 +537,7 @@ namespace ceph { + FEATURE_PINGING | + FEATURE_QUINCY | + FEATURE_REEF | ++ FEATURE_CEPHX_AUTH_AES256K | + FEATURE_NONE + ); + } +@@ -603,6 +606,8 @@ static inline const char *ceph::features::mon::get_feature_name(uint64_t b) { + return "quincy"; + } else if (f == FEATURE_REEF) { + return "reef"; ++ } else if (f == FEATURE_CEPHX_AUTH_AES256K) { ++ return "cephx_auth_aes256k"; + } else if (f == FEATURE_RESERVED) { + return "reserved"; + } +@@ -631,6 +636,8 @@ inline mon_feature_t ceph::features::mon::get_feature_by_name(const std::string + return FEATURE_QUINCY; + } else if (n == "reef") { + return FEATURE_REEF; ++ } else if (n == "cephx_auth_aes256k") { ++ return FEATURE_CEPHX_AUTH_AES256K; + } else if (n == "reserved") { + return FEATURE_RESERVED; + } +diff --git a/src/mount/mount.ceph.c b/src/mount/mount.ceph.c +index 6efb154d3..aaa0408c6 100644 +--- a/src/mount/mount.ceph.c ++++ b/src/mount/mount.ceph.c +@@ -449,6 +449,8 @@ static int fetch_config_info(struct ceph_mount_info *cmi) + memcpy(cmi->cmi_secret, cci->cci_secret, len + 1); + } else { + mount_ceph_debug("secret is too long (len=%zu max=%zu)!\n", len, SECRET_BUFSIZE); ++ ret = EX_SYSERR; ++ goto out; + } + } + if (!cmi->cmi_mons && cci->cci_mons[0]) { +@@ -569,8 +571,12 @@ static int parse_options(const char *data, struct ceph_mount_info *cmi, + } + + len = strnlen(value, sizeof(cmi->cmi_secret)) + 1; +- if (len <= sizeof(cmi->cmi_secret)) ++ if (len <= sizeof(cmi->cmi_secret)) { + memcpy(cmi->cmi_secret, value, len); ++ } else { ++ fprintf(stderr, "mount option secret is too long.\n"); ++ return -EINVAL; ++ } + } else if (strcmp(data, "conf") == 0) { + if (!value || !*value) { + fprintf(stderr, "mount option conf requires a value.\n"); +diff --git a/src/mount/mount.ceph.h b/src/mount/mount.ceph.h +index 9bd6bbfcc..603ab3df1 100644 +--- a/src/mount/mount.ceph.h ++++ b/src/mount/mount.ceph.h +@@ -11,9 +11,9 @@ extern "C" { + * 2 (for the type of secret) + + * 8 (for the timestamp) + + * 2 (for the length of secret) + +- * 16 (for an AES-128 key) ++ * 256 (for current and future secret-key encodings) + */ +-#define MAX_RAW_SECRET_LEN (2 + 8 + 2 + 16) ++#define MAX_RAW_SECRET_LEN (2 + 8 + 2 + 256) + + /* Max length of base64 encoded secret. 4/3 original size (rounded up) */ + #define MAX_SECRET_LEN ((MAX_RAW_SECRET_LEN + (3 - 1)) * 4 / 3) +diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc +index 950a20ad1..0361e76c4 100644 +--- a/src/rgw/rgw_rest_s3.cc ++++ b/src/rgw/rgw_rest_s3.cc +@@ -6395,7 +6395,7 @@ rgw::auth::s3::STSEngine::get_session_token(const DoutPrefixProvider* dpp, const + buffer::list en_input, dec_output; + en_input = buffer::list::static_from_string(decodedSessionToken); + +- ret = keyhandler->decrypt(en_input, dec_output, &error); ++ ret = keyhandler->decrypt(cct, en_input, dec_output, &error); + if (ret < 0) { + ldpp_dout(dpp, 0) << "ERROR: Decryption failed: " << error << dendl; + return -EPERM; +diff --git a/src/rgw/rgw_sts.cc b/src/rgw/rgw_sts.cc +index 2b31d5c5a..865e239e0 100644 +--- a/src/rgw/rgw_sts.cc ++++ b/src/rgw/rgw_sts.cc +@@ -139,7 +139,7 @@ int Credentials::generateCredentials(const DoutPrefixProvider *dpp, + buffer::list input, enc_output; + encode(token, input); + +- if (ret = keyhandler->encrypt(input, enc_output, &error); ret < 0) { ++ if (ret = keyhandler->encrypt(cct, input, enc_output, &error); ret < 0) { + ldpp_dout(dpp, 0) << "ERROR: Encrypting session token returned an error !" << dendl; + return ret; + } +diff --git a/src/test/crypto.cc b/src/test/crypto.cc +index 819d41c72..efcc13448 100644 +--- a/src/test/crypto.cc ++++ b/src/test/crypto.cc +@@ -1,6 +1,9 @@ + #include + #include + ++#include ++#include ++ + #include + + #include "gtest/gtest.h" +@@ -340,3 +343,334 @@ TEST(AES, LoopKey_29) { + TEST(AES, LoopKey_32) { + aes_loopkey(32); + } ++ ++TEST(AES256KRB5, ValidateSecret) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ ASSERT_NE(nullptr, handler); ++ for (int length = 0; length < 32; ++length) { ++ bufferptr secret(length); ++ EXPECT_EQ(-EINVAL, handler->validate_secret(secret)); ++ } ++ for (int length = 32; length < 50; ++length) { ++ bufferptr secret(length); ++ EXPECT_EQ(0, handler->validate_secret(secret)); ++ } ++} ++ ++struct AES256KRB5Vector { ++ vector secret; ++ vector confounder; ++ vector plaintext; ++ vector ciphertext; ++ uint32_t usage; ++}; ++ ++static const vector aes256krb5_vectors = { ++ { ++ {0x6d, 0x40, 0x4d, 0x37, 0xfa, 0xf7, 0x9f, 0x9d, ++ 0xf0, 0xd3, 0x35, 0x68, 0xd3, 0x20, 0x66, 0x98, ++ 0x00, 0xeb, 0x48, 0x36, 0x47, 0x2e, 0xa8, 0xa0, ++ 0x26, 0xd1, 0x6b, 0x71, 0x82, 0x46, 0x0c, 0x52}, ++ {0xf7, 0x64, 0xe9, 0xfa, 0x15, 0xc2, 0x76, 0x47, ++ 0x8b, 0x2c, 0x7d, 0x0c, 0x4e, 0x5f, 0x58, 0xe4}, ++ {}, ++ {0x41, 0xf5, 0x3f, 0xa5, 0xbf, 0xe7, 0x02, 0x6d, ++ 0x91, 0xfa, 0xf9, 0xbe, 0x95, 0x91, 0x95, 0xa0, ++ 0x58, 0x70, 0x72, 0x73, 0xa9, 0x6a, 0x40, 0xf0, ++ 0xa0, 0x19, 0x60, 0x62, 0x1a, 0xc6, 0x12, 0x74, ++ 0x8b, 0x9b, 0xbf, 0xbe, 0x7e, 0xb4, 0xce, 0x3c}, ++ 2 ++ }, ++ { ++ {0x6d, 0x40, 0x4d, 0x37, 0xfa, 0xf7, 0x9f, 0x9d, ++ 0xf0, 0xd3, 0x35, 0x68, 0xd3, 0x20, 0x66, 0x98, ++ 0x00, 0xeb, 0x48, 0x36, 0x47, 0x2e, 0xa8, 0xa0, ++ 0x26, 0xd1, 0x6b, 0x71, 0x82, 0x46, 0x0c, 0x52}, ++ {0xb8, 0x0d, 0x32, 0x51, 0xc1, 0xf6, 0x47, 0x14, ++ 0x94, 0x25, 0x6f, 0xfe, 0x71, 0x2d, 0x0b, 0x9a}, ++ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}, ++ {0x4e, 0xd7, 0xb3, 0x7c, 0x2b, 0xca, 0xc8, 0xf7, ++ 0x4f, 0x23, 0xc1, 0xcf, 0x07, 0xe6, 0x2b, 0xc7, ++ 0xb7, 0x5f, 0xb3, 0xf6, 0x37, 0xb9, 0xf5, 0x59, ++ 0xc7, 0xf6, 0x64, 0xf6, 0x9e, 0xab, 0x7b, 0x60, ++ 0x92, 0x23, 0x75, 0x26, 0xea, 0x0d, 0x1f, 0x61, ++ 0xcb, 0x20, 0xd6, 0x9d, 0x10, 0xf2}, ++ 2 ++ }, ++ { ++ {0x6d, 0x40, 0x4d, 0x37, 0xfa, 0xf7, 0x9f, 0x9d, ++ 0xf0, 0xd3, 0x35, 0x68, 0xd3, 0x20, 0x66, 0x98, ++ 0x00, 0xeb, 0x48, 0x36, 0x47, 0x2e, 0xa8, 0xa0, ++ 0x26, 0xd1, 0x6b, 0x71, 0x82, 0x46, 0x0c, 0x52}, ++ {0x53, 0xbf, 0x8a, 0x0d, 0x10, 0x52, 0x65, 0xd4, ++ 0xe2, 0x76, 0x42, 0x86, 0x24, 0xce, 0x5e, 0x63}, ++ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, ++ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, ++ {0xbc, 0x47, 0xff, 0xec, 0x79, 0x98, 0xeb, 0x91, ++ 0xe8, 0x11, 0x5c, 0xf8, 0xd1, 0x9d, 0xac, 0x4b, ++ 0xbb, 0xe2, 0xe1, 0x63, 0xe8, 0x7d, 0xd3, 0x7f, ++ 0x49, 0xbe, 0xca, 0x92, 0x02, 0x77, 0x64, 0xf6, ++ 0x8c, 0xf5, 0x1f, 0x14, 0xd7, 0x98, 0xc2, 0x27, ++ 0x3f, 0x35, 0xdf, 0x57, 0x4d, 0x1f, 0x93, 0x2e, ++ 0x40, 0xc4, 0xff, 0x25, 0x5b, 0x36, 0xa2, 0x66}, ++ 2 ++ }, ++ { ++ {0x6d, 0x40, 0x4d, 0x37, 0xfa, 0xf7, 0x9f, 0x9d, ++ 0xf0, 0xd3, 0x35, 0x68, 0xd3, 0x20, 0x66, 0x98, ++ 0x00, 0xeb, 0x48, 0x36, 0x47, 0x2e, 0xa8, 0xa0, ++ 0x26, 0xd1, 0x6b, 0x71, 0x82, 0x46, 0x0c, 0x52}, ++ {0x76, 0x3e, 0x65, 0x36, 0x7e, 0x86, 0x4f, 0x02, ++ 0xf5, 0x51, 0x53, 0xc7, 0xe3, 0xb5, 0x8a, 0xf1}, ++ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, ++ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, ++ 0x10, 0x11, 0x12, 0x13, 0x14}, ++ {0x40, 0x01, 0x3e, 0x2d, 0xf5, 0x8e, 0x87, 0x51, ++ 0x95, 0x7d, 0x28, 0x78, 0xbc, 0xd2, 0xd6, 0xfe, ++ 0x10, 0x1c, 0xcf, 0xd5, 0x56, 0xcb, 0x1e, 0xae, ++ 0x79, 0xdb, 0x3c, 0x3e, 0xe8, 0x64, 0x29, 0xf2, ++ 0xb2, 0xa6, 0x02, 0xac, 0x86, 0xfe, 0xf6, 0xec, ++ 0xb6, 0x47, 0xd6, 0x29, 0x5f, 0xae, 0x07, 0x7a, ++ 0x1f, 0xeb, 0x51, 0x75, 0x08, 0xd2, 0xc1, 0x6b, ++ 0x41, 0x92, 0xe0, 0x1f, 0x62}, ++ 2 ++ } ++}; ++ ++static bufferlist to_bufferlist(const vector& bytes) { ++ bufferlist result; ++ if (!bytes.empty()) { ++ result.append(reinterpret_cast(bytes.data()), bytes.size()); ++ } ++ return result; ++} ++ ++static bufferptr vector_secret(const AES256KRB5Vector& vector) { ++ return bufferptr(reinterpret_cast(vector.secret.data()), ++ vector.secret.size()); ++} ++ ++TEST(AES256KRB5, Encrypt) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ auto confounder = to_bufferlist(vector.confounder); ++ auto plaintext = to_bufferlist(vector.plaintext); ++ auto expected = to_bufferlist(vector.ciphertext); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ bufferlist ciphertext; ++ ASSERT_EQ(0, key->encrypt_ext(g_ceph_context, vector.usage, plaintext, ++ &confounder, ciphertext, &error)); ++ ASSERT_TRUE(error.empty()); ++ ASSERT_TRUE(expected.contents_equal(ciphertext)); ++ } ++} ++ ++TEST(AES256KRB5, EncryptUsage) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ auto confounder = to_bufferlist(vector.confounder); ++ auto plaintext = to_bufferlist(vector.plaintext); ++ auto expected = to_bufferlist(vector.ciphertext); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ bufferlist ciphertext; ++ ASSERT_EQ(0, key->encrypt_ext(g_ceph_context, vector.usage + 1, ++ plaintext, &confounder, ++ ciphertext, &error)); ++ ASSERT_FALSE(expected.contents_equal(ciphertext)); ++ ++ ciphertext.clear(); ++ ASSERT_EQ(0, key->encrypt_ext(g_ceph_context, plaintext, &confounder, ++ ciphertext, &error)); ++ ASSERT_TRUE(expected.contents_equal(ciphertext)); ++ } ++} ++ ++TEST(AES256KRB5, EncryptNoBl) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ CryptoKey::in_slice_t plaintext{ ++ vector.plaintext.size(), vector.plaintext.data()}; ++ CryptoKey::in_slice_t confounder{ ++ vector.confounder.size(), vector.confounder.data()}; ++ CryptoKey::out_slice_t probe{0, nullptr}; ++ auto required = key->encrypt_ext(g_ceph_context, vector.usage, ++ plaintext, &confounder, probe); ++ vector ciphertext(required); ++ CryptoKey::out_slice_t output{ciphertext.size(), ciphertext.data()}; ++ auto length = key->encrypt_ext(g_ceph_context, vector.usage, ++ plaintext, &confounder, output); ++ ASSERT_EQ(vector.ciphertext.size(), length); ++ ASSERT_EQ(0, memcmp(vector.ciphertext.data(), ciphertext.data(), length)); ++ } ++} ++ ++TEST(AES256KRB5, Decrypt) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ auto ciphertext = to_bufferlist(vector.ciphertext); ++ auto expected = to_bufferlist(vector.plaintext); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ bufferlist plaintext; ++ ASSERT_EQ(0, key->decrypt(g_ceph_context, ciphertext, plaintext, &error)); ++ ASSERT_TRUE(error.empty()); ++ ASSERT_TRUE(expected.contents_equal(plaintext)); ++ } ++} ++ ++TEST(AES256KRB5, DecryptUsage) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ auto ciphertext = to_bufferlist(vector.ciphertext); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ bufferlist plaintext; ++ ASSERT_NE(0, key->decrypt_ext(g_ceph_context, vector.usage + 1, ++ ciphertext, plaintext, &error)); ++ plaintext.clear(); ++ ASSERT_EQ(0, key->decrypt_ext(g_ceph_context, vector.usage, ++ ciphertext, plaintext, &error)); ++ ASSERT_TRUE(to_bufferlist(vector.plaintext).contents_equal(plaintext)); ++ } ++} ++ ++TEST(AES256KRB5, DecryptNoBl) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ for (const auto& vector : aes256krb5_vectors) { ++ auto secret = vector_secret(vector); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, vector.usage, error)); ++ CryptoKey::in_slice_t ciphertext{ ++ vector.ciphertext.size(), vector.ciphertext.data()}; ++ vector plaintext(std::max(1, vector.plaintext.size())); ++ CryptoKey::out_slice_t output{vector.plaintext.size(), plaintext.data()}; ++ auto length = key->decrypt_ext(g_ceph_context, vector.usage, ++ ciphertext, output); ++ ASSERT_EQ(vector.plaintext.size(), length); ++ if (length) { ++ ASSERT_EQ(0, memcmp(vector.plaintext.data(), plaintext.data(), length)); ++ } ++ } ++} ++ ++TEST(AES256KRB5, HMAC_SHA256) { ++ const unsigned char secret_bytes[] = { ++ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, ++ 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; ++ const unsigned char expected[] = { ++ 0x42, 0xc7, 0x02, 0x7e, 0x8b, 0xe0, 0x6d, 0xca, ++ 0x2c, 0x0b, 0x44, 0x43, 0x73, 0xfe, 0xfd, 0xbe, ++ 0xac, 0x5b, 0x40, 0x34, 0xec, 0xa4, 0x4a, 0x69, ++ 0xde, 0x3a, 0x29, 0x16, 0x34, 0xed, 0x8d, 0xf9}; ++ bufferptr secret(reinterpret_cast(secret_bytes), ++ sizeof(secret_bytes)); ++ std::string error; ++ std::unique_ptr key( ++ g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5) ++ ->get_key_handler(secret, error)); ++ bufferlist plaintext; ++ plaintext.append("blablabla"); ++ auto hash = key->hmac_sha256(plaintext); ++ ASSERT_EQ(0, memcmp(expected, &hash, sizeof(hash))); ++} ++ ++TEST(AES256KRB5, HMAC_SHA256_NoBl) { ++ const unsigned char secret_bytes[] = { ++ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, ++ 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; ++ const unsigned char expected[] = { ++ 0x4b, 0xd3, 0xac, 0x39, 0x4a, 0xcc, 0x97, 0x06, ++ 0xdd, 0x09, 0xe6, 0x5c, 0x68, 0xad, 0xd4, 0xcf, ++ 0x09, 0x2c, 0xcd, 0xa1, 0xe7, 0x99, 0xe3, 0x5c, ++ 0x52, 0x73, 0x85, 0xbd, 0x79, 0x73, 0xc6, 0x98}; ++ bufferptr secret(reinterpret_cast(secret_bytes), ++ sizeof(secret_bytes)); ++ std::string error; ++ std::unique_ptr key( ++ g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5) ++ ->get_key_handler(secret, error)); ++ const string plaintext = "testing1234blablabla"; ++ CryptoKey::in_slice_t slice{ ++ plaintext.size(), reinterpret_cast(plaintext.data())}; ++ auto hash = key->hmac_sha256(slice); ++ ASSERT_EQ(0, memcmp(expected, &hash, sizeof(hash))); ++} ++ ++TEST(AES256KRB5, RejectsTampering) { ++ const auto& vector = aes256krb5_vectors.front(); ++ auto secret = vector_secret(vector); ++ auto ciphertext = to_bufferlist(vector.ciphertext); ++ ciphertext.c_str()[0] ^= 1; ++ std::string error; ++ std::unique_ptr key( ++ g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5) ++ ->get_key_handler_ext(secret, vector.usage, error)); ++ bufferlist plaintext; ++ ASSERT_NE(0, key->decrypt_ext(g_ceph_context, vector.usage, ++ ciphertext, plaintext, &error)); ++} ++ ++static void aes256krb5_loop(size_t plaintext_size) { ++ auto handler = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); ++ CryptoRandom random; ++ bufferptr secret(32); ++ random.get_bytes(secret.c_str(), secret.length()); ++ bufferptr original(plaintext_size); ++ random.get_bytes(original.c_str(), original.length()); ++ bufferlist plaintext; ++ plaintext.append(original); ++ std::string error; ++ std::unique_ptr key( ++ handler->get_key_handler_ext(secret, 2, error)); ++ for (int iteration = 0; iteration < 10000; ++iteration) { ++ bufferlist ciphertext; ++ ASSERT_EQ(0, key->encrypt(g_ceph_context, plaintext, ciphertext, &error)); ++ plaintext.clear(); ++ ASSERT_EQ(0, key->decrypt(g_ceph_context, ciphertext, plaintext, &error)); ++ } ++ bufferlist expected; ++ expected.append(original); ++ ASSERT_TRUE(expected.contents_equal(plaintext)); ++} ++ ++TEST(AES256KRB5, Loop) { ++ aes256krb5_loop(256); ++} ++ ++TEST(AES256KRB5, Loop_29) { ++ aes256krb5_loop(29); ++} ++ ++TEST(AES256KRB5, Loop_32) { ++ aes256krb5_loop(32); ++} ++ ++TEST(AES, UsageAPICompatibility) { ++ CryptoKey key; ++ ASSERT_EQ(0, key.create(g_ceph_context, CEPH_CRYPTO_AES)); ++ bufferlist plaintext; ++ plaintext.append("legacy cephx ticket"); ++ bufferlist ciphertext; ++ std::string error; ++ ASSERT_EQ(0, key.encrypt_ext(g_ceph_context, 0x30, plaintext, ++ ciphertext, &error)); ++ bufferlist decrypted; ++ ASSERT_EQ(0, key.decrypt_ext(g_ceph_context, 0x30, ciphertext, ++ decrypted, &error)); ++ ASSERT_TRUE(plaintext.contents_equal(decrypted)); ++} +diff --git a/src/tools/ceph_authtool.cc b/src/tools/ceph_authtool.cc +index bf0bd5523..4e5a3d472 100644 +--- a/src/tools/ceph_authtool.cc ++++ b/src/tools/ceph_authtool.cc +@@ -51,7 +51,10 @@ void usage() + << " --caps CAPSFILE will set all of capabilities associated with a\n" + << " given key, for all subsystems\n" + << " --mode MODE will set the desired file mode to the keyring\n" +- << " e.g: '0644', defaults to '0600'" ++ << " e.g: '0644', defaults to '0600'\n" ++ << " -t, --key-type TYPE cipher used for generated keys:\n" ++ << " 'aes256k' (default) or 'aes' (legacy,\n" ++ << " required for older kernel clients)" + << std::endl; + exit(1); + } +@@ -65,6 +68,8 @@ int main(int argc, const char **argv) + map caps; + std::string fn; + ++ int key_type = CEPH_CRYPTO_AES256KRB5; ++ + if (args.empty()) { + cerr << argv[0] << ": -h or --help for usage" << std::endl; + exit(1); +@@ -122,6 +127,12 @@ int main(int argc, const char **argv) + create_keyring = true; + } else if (ceph_argparse_witharg(args, i, &val, "--import-keyring", (char*)NULL)) { + import_keyring = val; ++ } else if (ceph_argparse_witharg(args, i, &val, "-t", "--key-type", (char*)NULL)) { ++ key_type = CryptoManager::get_key_type(val); ++ if (key_type < 0 || key_type == CEPH_CRYPTO_NONE) { ++ cerr << "Option --key-type requires 'aes' or 'aes256k'" << std::endl; ++ exit(1); ++ } + } else if (ceph_argparse_witharg(args, i, &val, "--mode", (char*)NULL)) { + std::string err; + mode = strict_strtoll(val.c_str(), 8, &err); +@@ -170,7 +181,7 @@ int main(int argc, const char **argv) + + if (gen_print_key) { + CryptoKey key; +- key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ key.create(g_ceph_context, key_type); + cout << key << std::endl; + return 0; + } +@@ -239,7 +250,7 @@ int main(int argc, const char **argv) + } + if (gen_key) { + EntityAuth eauth; +- eauth.key.create(g_ceph_context, CEPH_CRYPTO_AES); ++ eauth.key.create(g_ceph_context, key_type); + keyring.add(ename, eauth); + modified = true; + } +diff --git a/src/tools/monmaptool.cc b/src/tools/monmaptool.cc +index b858fe5a6..ea1b82ccd 100644 +--- a/src/tools/monmaptool.cc ++++ b/src/tools/monmaptool.cc +@@ -350,6 +350,9 @@ int main(int argc, const char **argv) + monmap.epoch = 0; + monmap.created = ceph_clock_now(); + monmap.last_changed = monmap.created; ++ monmap.auth_service_cipher = CEPH_CRYPTO_AES256KRB5; ++ monmap.auth_allowed_ciphers = {CEPH_CRYPTO_AES256KRB5}; ++ monmap.auth_preferred_cipher = CEPH_CRYPTO_AES256KRB5; + srand(getpid() + time(0)); + if (g_conf().get_val("fsid").is_zero()) { + monmap.generate_fsid(); +-- +2.53.0 + diff --git a/SPECS/ceph/ceph.spec b/SPECS/ceph/ceph.spec index 3d0f3b99d97..1be4efa7ca1 100644 --- a/SPECS/ceph/ceph.spec +++ b/SPECS/ceph/ceph.spec @@ -5,7 +5,7 @@ Summary: User space components of the Ceph file system Name: ceph Version: 18.2.7 -Release: 12%{?dist} +Release: 2%{?dist} License: LGPLv2 and LGPLv3 and CC-BY-SA and GPLv2 and Boost and BSD and MIT and Public Domain and GPLv3 and ASL-2.0 URL: https://ceph.io/ Vendor: Microsoft Corporation @@ -32,6 +32,7 @@ Patch16: CVE-2020-14378.patch #Patch18: CVE-2024-48916.patch Patch19: CVE-2025-9648.patch Patch20: CVE-2024-47866.patch +Patch21: 0001-test-CVE-2025-30156.patch # # Copyright (C) 2004-2019 The Ceph Project Developers. See COPYING file # at the top-level directory of this distribution and at @@ -2071,6 +2072,9 @@ exit 0 %config %{_sysconfdir}/prometheus/ceph/ceph_default_alerts.yml %changelog +* Wed Sep 09 2026 Akhila Guruju - 18.2.7-2 +- Test backported patch for CVE-2025-30156 + * Thu Nov 13 2025 Azure Linux Security Servicing Account - 18.2.2-12 - Patch for CVE-2024-47866 From cf2fa2596f37a118adcb58074a992c3a9b527cad Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 11:11:25 +0000 Subject: [PATCH 3/5] modify patch to fix build --- SPECS/ceph/0001-test-CVE-2025-30156.patch | 80 +++++++++++++++-------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/SPECS/ceph/0001-test-CVE-2025-30156.patch b/SPECS/ceph/0001-test-CVE-2025-30156.patch index 1306f03ced7..5c20e050230 100644 --- a/SPECS/ceph/0001-test-CVE-2025-30156.patch +++ b/SPECS/ceph/0001-test-CVE-2025-30156.patch @@ -1,12 +1,12 @@ -From 7830b1b0624fe552ed46bd515205356077b5af59 Mon Sep 17 00:00:00 2001 +From e7341b0d847c36c5c33329050a19ddc5524f6c7e Mon Sep 17 00:00:00 2001 From: akhila-guruju -Date: Wed, 9 Sep 2026 06:39:44 +0000 -Subject: [PATCH] CVE-2025-30156 minimal backport +Date: Wed, 9 Sep 2026 09:29:15 +0000 +Subject: [PATCH] CVE-2025-30156-v2 --- src/auth/Auth.h | 4 + src/auth/AuthClientHandler.h | 1 + - src/auth/Crypto.cc | 705 +++++++++++++++++++++++++- + src/auth/Crypto.cc | 710 +++++++++++++++++++++++++- src/auth/Crypto.h | 106 +++- src/auth/RotatingKeyRing.cc | 6 + src/auth/RotatingKeyRing.h | 1 + @@ -23,7 +23,7 @@ Subject: [PATCH] CVE-2025-30156 minimal backport src/ceph_osd.cc | 2 +- src/common/ceph_context.cc | 5 + src/common/ceph_context.h | 1 + - src/common/options/mon.yaml.in | 10 + + src/common/options/mon.yaml.in | 9 + src/crimson/mon/MonClient.cc | 16 + src/crimson/osd/main.cc | 2 +- src/include/ceph_fs.h | 5 +- @@ -43,10 +43,10 @@ Subject: [PATCH] CVE-2025-30156 minimal backport src/mount/mount.ceph.h | 4 +- src/rgw/rgw_rest_s3.cc | 2 +- src/rgw/rgw_sts.cc | 2 +- - src/test/crypto.cc | 334 ++++++++++++ + src/test/crypto.cc | 345 +++++++++++++ src/tools/ceph_authtool.cc | 17 +- src/tools/monmaptool.cc | 3 + - 42 files changed, 1916 insertions(+), 101 deletions(-) + 42 files changed, 1931 insertions(+), 101 deletions(-) diff --git a/src/auth/Auth.h b/src/auth/Auth.h index 5521c8d3f..2b3c7ebfb 100644 @@ -76,7 +76,7 @@ index aba21b415..c7eb07b4b 100644 virtual void set_global_id(uint64_t id) = 0; diff --git a/src/auth/Crypto.cc b/src/auth/Crypto.cc -index ce666e8bd..28cd1d2e0 100644 +index ce666e8bd..c9ffc6cc5 100644 --- a/src/auth/Crypto.cc +++ b/src/auth/Crypto.cc @@ -12,11 +12,15 @@ @@ -334,7 +334,17 @@ index ce666e8bd..28cd1d2e0 100644 }; // when we say AES, we mean AES-128 -@@ -302,9 +453,10 @@ public: +@@ -276,6 +427,9 @@ class CryptoAESKeyHandler : public CryptoKeyHandler { + AES_KEY dec_key; + + public: ++ using CryptoKeyHandler::encrypt; ++ using CryptoKeyHandler::decrypt; ++ + CryptoAESKeyHandler() + : CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()) { + } +@@ -302,9 +456,10 @@ public: return 0; } @@ -346,7 +356,7 @@ index ce666e8bd..28cd1d2e0 100644 // we need to take into account the PKCS#7 padding. There *always* will // be at least one byte of padding. This stays even to input aligned to // AES_BLOCK_LEN. Otherwise we would face ambiguities during decryption. -@@ -344,9 +496,10 @@ public: +@@ -344,9 +499,10 @@ public: return 0; } @@ -358,7 +368,7 @@ index ce666e8bd..28cd1d2e0 100644 // PKCS#7 padding enlarges even empty plain-text to take 16 bytes. if (in.length() < AES_BLOCK_LEN || in.length() % AES_BLOCK_LEN) { return -1; -@@ -376,7 +529,7 @@ public: +@@ -376,7 +532,7 @@ public: return 0; } @@ -367,7 +377,7 @@ index ce666e8bd..28cd1d2e0 100644 const out_slice_t& out) const override { if (out.buf == nullptr) { // 16 + p2align(10, 16) -> 16 -@@ -417,7 +570,7 @@ public: +@@ -417,7 +573,7 @@ public: return main_encrypt_size + tail_encrypt_size; } @@ -376,7 +386,7 @@ index ce666e8bd..28cd1d2e0 100644 const out_slice_t& out) const override { if (in.length % AES_BLOCK_LEN != 0 || in.length < AES_BLOCK_LEN) { throw std::runtime_error("input not aligned to AES_BLOCK_LEN"); -@@ -466,8 +619,9 @@ int CryptoAES::validate_secret(const bufferptr& secret) +@@ -466,8 +622,9 @@ int CryptoAES::validate_secret(const bufferptr& secret) return 0; } @@ -388,7 +398,7 @@ index ce666e8bd..28cd1d2e0 100644 { CryptoAESKeyHandler *ckh = new CryptoAESKeyHandler; ostringstream oss; -@@ -480,6 +634,489 @@ CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, +@@ -480,6 +637,491 @@ CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, } @@ -663,6 +673,8 @@ index ce666e8bd..28cd1d2e0 100644 + + using CryptoKeyHandler::encrypt; + using CryptoKeyHandler::decrypt; ++ using CryptoKeyHandler::encrypt_ext; ++ using CryptoKeyHandler::decrypt_ext; + + int init(const ceph::bufferptr& s, uint32_t usage, ostringstream& err) { + cipher = EVP_CIPHER_fetch(NULL, "AES-256-CBC-CTS", NULL); @@ -878,7 +890,7 @@ index ce666e8bd..28cd1d2e0 100644 // -- -@@ -606,10 +1243,56 @@ CryptoHandler *CryptoHandler::create(int type) +@@ -606,10 +1248,56 @@ CryptoHandler *CryptoHandler::create(int type) return new CryptoNone; case CEPH_CRYPTO_AES: return new CryptoAES; @@ -936,7 +948,7 @@ index ce666e8bd..28cd1d2e0 100644 #pragma clang diagnostic pop #pragma GCC diagnostic pop diff --git a/src/auth/Crypto.h b/src/auth/Crypto.h -index a29ac1abd..d07336433 100644 +index a29ac1abd..0f41bc3fd 100644 --- a/src/auth/Crypto.h +++ b/src/auth/Crypto.h @@ -20,7 +20,9 @@ @@ -1059,7 +1071,7 @@ index a29ac1abd..d07336433 100644 const out_slice_t& out) { ceph_assert(ckh); - return ckh->encrypt(in, out); -+ return ckh->encrypt(cct, in, out); ++ return ckh->decrypt(cct, in, out); + } + + sha256_digest_t hmac_sha256(CephContext*, const ceph::buffer::list& in) const { @@ -2012,10 +2024,10 @@ index f18776478..b3b3ac2f6 100644 // experimental diff --git a/src/common/options/mon.yaml.in b/src/common/options/mon.yaml.in -index 8300e31d0..e7d1ecfc0 100644 +index 8300e31d0..35c70e76d 100644 --- a/src/common/options/mon.yaml.in +++ b/src/common/options/mon.yaml.in -@@ -2,6 +2,16 @@ +@@ -2,6 +2,15 @@ --- options: @@ -2023,7 +2035,6 @@ index 8300e31d0..e7d1ecfc0 100644 + type: str + level: advanced + desc: Temporarily add CephX ciphers allowed by the monitor map -+ default: "" + services: + - mon + flags: @@ -2433,17 +2444,17 @@ index 76ae4be04..0b5c75d92 100644 { ceph_assert(ceph_mutex_is_locked(monc_lock)); diff --git a/src/mon/MonClient.h b/src/mon/MonClient.h -index b72bf1f65..5a971dd35 100644 +index b72bf1f65..268ebd72a 100644 --- a/src/mon/MonClient.h +++ b/src/mon/MonClient.h -@@ -106,6 +106,7 @@ public: - } +@@ -315,6 +315,7 @@ private: + void handle_monmap(MMonMap *m); + void handle_config(MConfig *m); - private: + void _wipe_secrets_and_tickets(); - int _negotiate(MAuthReply *m, - const EntityName& entity_name, - uint32_t want_keys, + void handle_auth(MAuthReply *m); + + int call( diff --git a/src/mon/MonCommands.h b/src/mon/MonCommands.h index 662527de1..8e37b419f 100644 --- a/src/mon/MonCommands.h @@ -3019,7 +3030,7 @@ index 2b31d5c5a..865e239e0 100644 return ret; } diff --git a/src/test/crypto.cc b/src/test/crypto.cc -index 819d41c72..efcc13448 100644 +index 819d41c72..7b0f7f900 100644 --- a/src/test/crypto.cc +++ b/src/test/crypto.cc @@ -1,6 +1,9 @@ @@ -3032,7 +3043,7 @@ index 819d41c72..efcc13448 100644 #include #include "gtest/gtest.h" -@@ -340,3 +343,334 @@ TEST(AES, LoopKey_29) { +@@ -340,3 +343,345 @@ TEST(AES, LoopKey_29) { TEST(AES, LoopKey_32) { aes_loopkey(32); } @@ -3366,6 +3377,17 @@ index 819d41c72..efcc13448 100644 + ASSERT_EQ(0, key.decrypt_ext(g_ceph_context, 0x30, ciphertext, + decrypted, &error)); + ASSERT_TRUE(plaintext.contents_equal(decrypted)); ++ ++ CryptoKey::in_slice_t ciphertext_slice{ ++ ciphertext.length(), ++ reinterpret_cast(ciphertext.c_str())}; ++ vector slice_plaintext(ciphertext.length()); ++ CryptoKey::out_slice_t plaintext_slice{ ++ slice_plaintext.size(), slice_plaintext.data()}; ++ ASSERT_EQ(plaintext.length(), ++ key.decrypt(g_ceph_context, ciphertext_slice, plaintext_slice)); ++ ASSERT_EQ(0, memcmp(plaintext.c_str(), slice_plaintext.data(), ++ plaintext.length())); +} diff --git a/src/tools/ceph_authtool.cc b/src/tools/ceph_authtool.cc index bf0bd5523..4e5a3d472 100644 From 0738364b3c54c09dfe4715c81a0f6426b46f685a Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 11:24:04 +0000 Subject: [PATCH 4/5] Revert "upgrade to 18.2.7 from existing PR" This reverts commit 0608ee709ee69e0bfcbe7368af34481b3ea3bbc5. --- SPECS/ceph/ceph.signatures.json | 8 ++--- SPECS/ceph/ceph.spec | 59 +++------------------------------ cgmanifest.json | 4 +-- 3 files changed, 11 insertions(+), 60 deletions(-) diff --git a/SPECS/ceph/ceph.signatures.json b/SPECS/ceph/ceph.signatures.json index 42c0fbb071c..9097ec9ee95 100644 --- a/SPECS/ceph/ceph.signatures.json +++ b/SPECS/ceph/ceph.signatures.json @@ -1,5 +1,5 @@ { - "Signatures": { - "ceph-18.2.7.tar.gz": "42e7da3fa7936bb541399c16f0bc07f13d28be889e2c1df119335f1985ac9a74" - } -} \ No newline at end of file + "Signatures": { + "ceph-18.2.2.tar.gz": "e70bb5246b4a5d7aa78eb548677a05cc21d0d47945ba2937fddc7511134ffb57" + } +} diff --git a/SPECS/ceph/ceph.spec b/SPECS/ceph/ceph.spec index 1be4efa7ca1..1ad67ccb560 100644 --- a/SPECS/ceph/ceph.spec +++ b/SPECS/ceph/ceph.spec @@ -4,8 +4,8 @@ Summary: User space components of the Ceph file system Name: ceph -Version: 18.2.7 -Release: 2%{?dist} +Version: 18.2.2 +Release: 12%{?dist} License: LGPLv2 and LGPLv3 and CC-BY-SA and GPLv2 and Boost and BSD and MIT and Public Domain and GPLv3 and ASL-2.0 URL: https://ceph.io/ Vendor: Microsoft Corporation @@ -28,8 +28,8 @@ Patch13: CVE-2020-10724.patch Patch14: CVE-2025-1744.patch Patch15: CVE-2021-28361.patch Patch16: CVE-2020-14378.patch -#Patch17: CVE-2025-52555.patch -#Patch18: CVE-2024-48916.patch +Patch17: CVE-2025-52555.patch +Patch18: CVE-2024-48916.patch Patch19: CVE-2025-9648.patch Patch20: CVE-2024-47866.patch Patch21: 0001-test-CVE-2025-30156.patch @@ -434,21 +434,6 @@ ceph-mgr-diskprediction-local is a ceph-mgr module that tries to predict disk failures using local algorithms and machine-learning databases. %endif -%package mgr-modules-core -Summary: Ceph Manager modules which are always enabled -BuildArch: noarch -Requires: python%{python3_pkgversion}-bcrypt -Requires: python%{python3_pkgversion}-pecan -Requires: python%{python3_pkgversion}-pyOpenSSL -Requires: python%{python3_pkgversion}-requests -Requires: python%{python3_pkgversion}-dateutil -Requires: python%{python3_pkgversion}-cherrypy -Requires: python%{python3_pkgversion}-PyYAML -Requires: python%{python3_pkgversion}-werkzeug -%description mgr-modules-core -ceph-mgr-modules-core provides a set of modules which are always -enabled by ceph-mgr. - %package mgr-k8sevents BuildArch: noarch Summary: Ceph Manager module to orchestrate ceph-events to kubernetes' events API @@ -1187,8 +1172,6 @@ install -m 644 -D monitoring/ceph-mixin/prometheus_alerts.yml %{buildroot}/etc/p %dir %{_libdir}/ceph %dir %{_libdir}/ceph/erasure-code %{_libdir}/ceph/erasure-code/libec_*.so* -%dir %{_libdir}/ceph/extblkdev -%{_libdir}/ceph/extblkdev/libceph_*.so* %dir %{_libdir}/ceph/compressor %{_libdir}/ceph/compressor/libceph_*.so* %{_unitdir}/ceph-crash.service @@ -1412,38 +1395,6 @@ if [ $1 -eq 1 ] ; then /usr/bin/systemctl try-restart ceph-mgr.target >/dev/null 2>&1 || : fi -%files mgr-modules-core -%dir %{_datadir}/ceph/mgr -%{_datadir}/ceph/mgr/alerts -%{_datadir}/ceph/mgr/balancer -%{_datadir}/ceph/mgr/crash -%{_datadir}/ceph/mgr/devicehealth -%{_datadir}/ceph/mgr/influx -%{_datadir}/ceph/mgr/insights -%{_datadir}/ceph/mgr/iostat -%{_datadir}/ceph/mgr/localpool -%{_datadir}/ceph/mgr/mds_autoscaler -%{_datadir}/ceph/mgr/mirroring -%{_datadir}/ceph/mgr/nfs -%{_datadir}/ceph/mgr/orchestrator -%{_datadir}/ceph/mgr/osd_perf_query -%{_datadir}/ceph/mgr/osd_support -%{_datadir}/ceph/mgr/pg_autoscaler -%{_datadir}/ceph/mgr/progress -%{_datadir}/ceph/mgr/prometheus -%{_datadir}/ceph/mgr/rbd_support -# a dependent module of restful is not available and -# hence not enabling it at this moment -%{_datadir}/ceph/mgr/selftest -%{_datadir}/ceph/mgr/snap_schedule -%{_datadir}/ceph/mgr/stats -%{_datadir}/ceph/mgr/status -%{_datadir}/ceph/mgr/telegraf -%{_datadir}/ceph/mgr/telemetry -%{_datadir}/ceph/mgr/test_orchestrator -%{_datadir}/ceph/mgr/volumes -%{_datadir}/ceph/mgr/zabbix - %files mgr-k8sevents %{_datadir}/ceph/mgr/k8sevents @@ -2180,4 +2131,4 @@ exit 0 * Fri Aug 21 2020 Thomas Crain 15.2.4-1 - Initial CBL-Mariner import from Ceph source (license: LGPLv2.1) -- License verified \ No newline at end of file +- License verified diff --git a/cgmanifest.json b/cgmanifest.json index 113e0c7be43..f4906091a8a 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -1587,8 +1587,8 @@ "type": "other", "other": { "name": "ceph", - "version": "18.2.7", - "downloadUrl": "https://download.ceph.com/tarballs/ceph-18.2.7.tar.gz" + "version": "18.2.2", + "downloadUrl": "https://download.ceph.com/tarballs/ceph-18.2.2.tar.gz" } } }, From 9df8baf154341a28bc75f4ade2c6be3c4fc249e1 Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 11:26:33 +0000 Subject: [PATCH 5/5] patch for v.18.2.2 --- ... => CVE-2025-30156-v18.2.2-backport.patch} | 22 +++++++++---------- SPECS/ceph/ceph.spec | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) rename SPECS/ceph/{0001-test-CVE-2025-30156.patch => CVE-2025-30156-v18.2.2-backport.patch} (99%) diff --git a/SPECS/ceph/0001-test-CVE-2025-30156.patch b/SPECS/ceph/CVE-2025-30156-v18.2.2-backport.patch similarity index 99% rename from SPECS/ceph/0001-test-CVE-2025-30156.patch rename to SPECS/ceph/CVE-2025-30156-v18.2.2-backport.patch index 5c20e050230..a2f9fe7c1b7 100644 --- a/SPECS/ceph/0001-test-CVE-2025-30156.patch +++ b/SPECS/ceph/CVE-2025-30156-v18.2.2-backport.patch @@ -1,7 +1,7 @@ From e7341b0d847c36c5c33329050a19ddc5524f6c7e Mon Sep 17 00:00:00 2001 From: akhila-guruju Date: Wed, 9 Sep 2026 09:29:15 +0000 -Subject: [PATCH] CVE-2025-30156-v2 +Subject: [PATCH] CVE-2025-30156 backport for Ceph 18.2.2 --- src/auth/Auth.h | 4 + @@ -2763,7 +2763,15 @@ diff --git a/src/mon/Monitor.h b/src/mon/Monitor.h index fa98d3b20..fe8c4ad70 100644 --- a/src/mon/Monitor.h +++ b/src/mon/Monitor.h -@@ -1037,6 +1037,10 @@ private: +@@ -165,6 +165,7 @@ public: + std::string gss_ktfile_client{}; + + private: ++ bool use_mon_keyring = false; + void new_tick(); + + // -- local storage -- +@@ -1037,6 +1038,10 @@ private: const std::set &changed) override; void update_log_clients(); @@ -2774,15 +2782,7 @@ index fa98d3b20..fe8c4ad70 100644 int sanitize_options(); int preinit(); int init(); -@@ -1110,6 +1114,7 @@ public: - } - - private: -+ bool use_mon_keyring = false; - ceph::coarse_mono_time const starttime = coarse_mono_clock::now(); - }; - -@@ -1128,6 +1133,7 @@ private: +@@ -1116,6 +1121,7 @@ public: #define CEPH_MON_FEATURE_INCOMPAT_PACIFIC CompatSet::Feature(13, "pacific ondisk layout") #define CEPH_MON_FEATURE_INCOMPAT_QUINCY CompatSet::Feature(14, "quincy ondisk layout") #define CEPH_MON_FEATURE_INCOMPAT_REEF CompatSet::Feature(15, "reef ondisk layout") diff --git a/SPECS/ceph/ceph.spec b/SPECS/ceph/ceph.spec index 1ad67ccb560..e79896f72c2 100644 --- a/SPECS/ceph/ceph.spec +++ b/SPECS/ceph/ceph.spec @@ -5,7 +5,7 @@ Summary: User space components of the Ceph file system Name: ceph Version: 18.2.2 -Release: 12%{?dist} +Release: 13%{?dist} License: LGPLv2 and LGPLv3 and CC-BY-SA and GPLv2 and Boost and BSD and MIT and Public Domain and GPLv3 and ASL-2.0 URL: https://ceph.io/ Vendor: Microsoft Corporation @@ -32,7 +32,7 @@ Patch17: CVE-2025-52555.patch Patch18: CVE-2024-48916.patch Patch19: CVE-2025-9648.patch Patch20: CVE-2024-47866.patch -Patch21: 0001-test-CVE-2025-30156.patch +Patch21: CVE-2025-30156-v18.2.2-backport.patch # # Copyright (C) 2004-2019 The Ceph Project Developers. See COPYING file # at the top-level directory of this distribution and at @@ -2023,7 +2023,7 @@ exit 0 %config %{_sysconfdir}/prometheus/ceph/ceph_default_alerts.yml %changelog -* Wed Sep 09 2026 Akhila Guruju - 18.2.7-2 +* Wed Sep 09 2026 Akhila Guruju - 18.2.2-13 - Test backported patch for CVE-2025-30156 * Thu Nov 13 2025 Azure Linux Security Servicing Account - 18.2.2-12