From: Marcel Lauhoff Date: Fri, 25 Apr 2025 14:27:57 +0000 (+0200) Subject: common: Add Linux Keyring Secret Store Wrapper X-Git-Tag: v21.0.1~14^2~14 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=0307119cdfbdbb09285ad82544be0c4be9b69882;p=ceph.git common: Add Linux Keyring Secret Store Wrapper Add RAII wrapper around the Linux Key Retention Service add_key(2), keyctl_read(3), keyctl_invalidate(3) Signed-off-by: Marcel Lauhoff On-behalf-of: SAP marcel.lauhoff@sap.com --- diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index fa6771c2357..8f8f1dd0ba0 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -289,4 +289,9 @@ if(HAVE_KEYUTILS) set(parse_secret_srcs secret.c) add_library(parse_secret_objs OBJECT ${parse_secret_srcs}) + + set(keyring_srcs + keyring.cc) + add_library(keyring STATIC ${keyring_srcs}) + target_link_libraries(keyring keyutils::keyutils) endif() diff --git a/src/common/keyring.cc b/src/common/keyring.cc new file mode 100644 index 00000000000..860ec91ae9f --- /dev/null +++ b/src/common/keyring.cc @@ -0,0 +1,143 @@ + +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2025 Clyso GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "keyring.h" + +#ifndef _WIN32 +extern "C" { +#include +} +#endif + +namespace ceph { + +#ifdef _WIN32 + +LinuxKeyringSecret::LinuxKeyringSecret(key_serial_t serial, size_t len) noexcept + : _len(len), _serial(serial) {} + +LinuxKeyringSecret::~LinuxKeyringSecret() noexcept {} + +tl::expected LinuxKeyringSecret::add( + const std::string& key, const std::string& secret) noexcept { + return tl::unexpected(std::error_code(-ENOSYS, std::system_category())); +} + +bool LinuxKeyringSecret::supported() noexcept { + return false; +} + +void LinuxKeyringSecret::initialize_process_keyring() noexcept { +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::read(std::string& out) const { + return {-ENOSYS, std::system_category()}; +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::remove() const { + return {-ENOSYS, std::system_category()}; +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::reset() { + return {-ENOSYS, std::system_category()}; +} + +#else + +LinuxKeyringSecret::LinuxKeyringSecret(key_serial_t serial, size_t len) noexcept + : _len(len), _serial(serial) {} + +LinuxKeyringSecret::~LinuxKeyringSecret() noexcept { + reset(); +} + +tl::expected LinuxKeyringSecret::add( + const std::string& key, const std::string& secret) noexcept { + const auto serial = add_key( + "user", key.c_str(), secret.c_str(), secret.size(), + KEY_SPEC_PROCESS_KEYRING); + if (serial == -1) { + return tl::unexpected(std::error_code(errno, std::system_category())); + } + return LinuxKeyringSecret(serial, secret.size()); +} + +bool LinuxKeyringSecret::supported(std::error_code* ec) noexcept { + auto secret = LinuxKeyringSecret::add("ceph_test_keyring_support", "ceph"); + if (!secret) { + if (ec != nullptr) { + *ec = secret.error(); + } + return false; + } + std::string out; + if (auto err = secret.value().read(out); err) { + if (ec != nullptr) { + *ec = err; + } + return false; + } + if (auto err = secret.value().reset(); err) { + if (ec != nullptr) { + *ec = err; + } + return false; + } + return true; +} + +void LinuxKeyringSecret::initialize_process_keyring() noexcept { + keyctl_get_keyring_ID(KEY_SPEC_PROCESS_KEYRING, 1); +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::read(std::string& out) const { + out.clear(); + out.resize(_len); + const auto ret = keyctl_read(_serial, out.data(), _len); + if (ret == -1) { + return {errno, std::system_category()}; + } else if (static_cast(ret) != _len) { + return {-EINVAL, std::generic_category()}; + } + return {}; +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::remove() const { + const auto ret = keyctl_invalidate(_serial); + if (ret != 0) { + return {errno, std::system_category()}; + } + return {}; +} + +[[nodiscard]] std::error_code LinuxKeyringSecret::reset() { + if (_serial == -1) { + return {-EINVAL, std::generic_category()}; + } + if (const auto ret = remove(); !ret) { + return ret; + } + _serial = -1; + _len = 0; + return {}; +} + +[[nodiscard]] bool LinuxKeyringSecret::initialized() const { + return _len > 0 && _serial != -1; +} + +#endif + +} // namespace ceph diff --git a/src/common/keyring.h b/src/common/keyring.h new file mode 100644 index 00000000000..63fe29d16f5 --- /dev/null +++ b/src/common/keyring.h @@ -0,0 +1,84 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2025 Clyso GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once + +#include +#include +#include + +#include "fmt/ostream.h" +#include "include/expected.hpp" + +namespace ceph { + +/// RAII style wrapper around a secret stored in the Linux Key +/// Retention Service. +/// See: add_key(2), keyctl_read(3), keyctl_invalidate(3) +class LinuxKeyringSecret { + size_t _len; + int _serial; + + explicit LinuxKeyringSecret(int serial, size_t len) noexcept; + public: + LinuxKeyringSecret() = delete; + LinuxKeyringSecret(const LinuxKeyringSecret&) = delete; + LinuxKeyringSecret& operator=(const LinuxKeyringSecret&) = delete; + LinuxKeyringSecret(LinuxKeyringSecret&& other) noexcept + : _len(std::exchange(other._len, 0)), + _serial(std::exchange(other._serial, -1)) {}; + LinuxKeyringSecret& operator=(LinuxKeyringSecret&& other) noexcept { + if (this != &other) { + if (this->initialized()) { + this->reset(); + } + _len = std::exchange(other._len, 0); + _serial = std::exchange(other._serial, -1); + } + return *this; + }; + + ~LinuxKeyringSecret() noexcept; + + // Add a secret to the kernel process keyring. Beware: calling this + // with with an existing key _updates_ the value and causes multiple + // instances to refer to the same key. + static tl::expected add( + const std::string& key, const std::string& secret) noexcept; + static bool supported(std::error_code* ec = nullptr) noexcept; + // Initialize the process keyring. Do this before starting any + // threads that want to share possession of keys in the process + // keyring + static void initialize_process_keyring() noexcept; + [[nodiscard]] std::error_code read(std::string& out) const; + [[nodiscard]] std::error_code remove() const; + [[nodiscard]] bool initialized() const; + private: + std::error_code reset(); + + friend std::ostream& operator<<( + std::ostream& os, const LinuxKeyringSecret& secret) { + if (secret._serial == -1) { + return os << "LinuxKeyringSecret{}"; + } + return os << "LinuxKeyringSecret{" << secret._serial << "}"; + } + friend class LinuxKeyringTest_LifecycleMoveAssignResetsDestination_Test; +}; +} // namespace ceph + +#if FMT_VERSION >= 90000 +template <> +struct fmt::formatter : fmt::ostream_formatter {}; +#endif diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 91b488adea6..85d462c70c6 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1095,7 +1095,7 @@ if (benchmark_FOUND) add_executable(bench_secmem bench_secmem.cc ) - target_link_libraries(bench_secmem ceph-common global-static benchmark::benchmark keyutils) + target_link_libraries(bench_secmem ceph-common global-static benchmark::benchmark keyutils::keyutils) else() message(STATUS "The google/benchmark library was not found. Skipping micro benchmark tests") endif(benchmark_FOUND) diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt index 655bbe9a6de..0765f0c2efd 100644 --- a/src/test/common/CMakeLists.txt +++ b/src/test/common/CMakeLists.txt @@ -121,6 +121,15 @@ add_executable(unittest_web_cache add_ceph_unittest(unittest_web_cache) target_link_libraries(unittest_web_cache ceph-common) +if(HAVE_KEYUTILS) + # unittest_web_cache + add_executable(unittest_keyring + test_keyring.cc + ) + add_ceph_unittest(unittest_keyring) + target_link_libraries(unittest_keyring global keyring) +endif() + # unittest_sloppy_crc_map add_executable(unittest_sloppy_crc_map test_sloppy_crc_map.cc diff --git a/src/test/common/test_keyring.cc b/src/test/common/test_keyring.cc new file mode 100644 index 00000000000..ced857e9777 --- /dev/null +++ b/src/test/common/test_keyring.cc @@ -0,0 +1,65 @@ +#include +#include +#include +extern "C" { +#include +} + +namespace ceph { + +class LinuxKeyringTest : public ::testing::Test { + protected: + void SetUp() override { + std::error_code ec; + if (!LinuxKeyringSecret::supported(&ec)) { + GTEST_SKIP() << "Linux Keyring is unsupported. " << ec + << ". Skipping test"; + } + } +}; + +TEST_F(LinuxKeyringTest, Basics) { + std::string secret("secret"); + auto maybe_keyring_secret = LinuxKeyringSecret::add("testkey", secret); + + ASSERT_TRUE(maybe_keyring_secret.has_value()) << maybe_keyring_secret.error(); + auto keyring_secret = std::move(maybe_keyring_secret.value()); + + std::string out; + ASSERT_FALSE(keyring_secret.read(out)); + ASSERT_EQ(secret, out); + + ASSERT_FALSE(keyring_secret.remove()); + ASSERT_TRUE(keyring_secret.read(out)); +} + +TEST_F(LinuxKeyringTest, Lifecycle) { + std::string secret("secret"); + auto uut = LinuxKeyringSecret::add("testkey", secret); + ASSERT_TRUE(uut.has_value()); + ASSERT_TRUE(uut->initialized()); + auto next = std::move(uut); + ASSERT_FALSE(uut->initialized()); + ASSERT_TRUE(next->initialized()); +} + +TEST_F(LinuxKeyringTest, LifecycleMoveAssignResetsDestination) { + std::string secret("secret"); + auto dest = LinuxKeyringSecret::add("testkey", secret); + auto source = LinuxKeyringSecret::add("testkey2", secret); + ASSERT_TRUE(dest.has_value()); + ASSERT_TRUE(source.has_value()); + + auto dest_serial = dest->_serial; + ASSERT_NE(-1, dest_serial); + + dest = std::move(source); + + std::array buf = {0}; + auto ret = keyctl_describe(dest_serial, buf.data(), buf.size()); + + EXPECT_EQ(-1, ret) << buf.data(); + ASSERT_EQ(ENOKEY, errno); +} + +} // namespace ceph