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()
--- /dev/null
+
+// -*- 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 <keyutils.h>
+}
+#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, std::error_code> 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, std::error_code> 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<size_t>(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
--- /dev/null
+// -*- 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 <ostream>
+#include <system_error>
+#include <utility>
+
+#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<LinuxKeyringSecret, std::error_code> 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<ceph::LinuxKeyringSecret> : fmt::ostream_formatter {};
+#endif
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)
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
--- /dev/null
+#include <common/keyring.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+extern "C" {
+#include <keyutils.h>
+}
+
+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<char, 1024> buf = {0};
+ auto ret = keyctl_describe(dest_serial, buf.data(), buf.size());
+
+ EXPECT_EQ(-1, ret) << buf.data();
+ ASSERT_EQ(ENOKEY, errno);
+}
+
+} // namespace ceph