]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: Add `PrincipalIdentity` for use in testing and utilities
authorAdam C. Emerson <aemerson@redhat.com>
Wed, 15 Jul 2026 02:12:03 +0000 (22:12 -0400)
committerAdam C. Emerson <aemerson@redhat.com>
Tue, 21 Jul 2026 21:55:08 +0000 (17:55 -0400)
This is an identity backed by a `Principal`, for use when we don't
have any sort of RGW driver.

Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/rgw/rgw_auth.cc
src/rgw/rgw_auth.h
src/rgw/rgw_basic_types.h
src/test/rgw/test_rgw_iam_policy.cc

index bd44e1a8ea0b802ddb84936b794c2220fc080e50..53e433c58fabe55fdfcafcae0fc525fcd0aa93f3 100644 (file)
@@ -365,7 +365,6 @@ auto transform_old_authinfo(const DoutPrefixProvider* dpp,
   }
   return transform_old_authinfo(info, std::move(account), std::move(policies), driver);
 }
-
 } /* namespace auth */
 } /* namespace rgw */
 
@@ -1384,3 +1383,168 @@ rgw::auth::AnonymousEngine::authenticate(const DoutPrefixProvider* dpp, const re
     return result_t::grant(std::move(apl));
   }
 }
+
+namespace rgw::auth {
+ACLOwner
+PrincipalIdentity::get_aclowner() const
+{
+  ceph_abort();
+  return {};
+}
+
+uint32_t
+PrincipalIdentity::get_perms_from_aclspec(
+    const DoutPrefixProvider* dpp,
+    const aclspec_t& aclspec) const
+{
+  ceph_abort();
+  return 0;
+}
+
+bool
+PrincipalIdentity::is_admin() const
+{
+  ceph_abort();
+  return false;
+}
+
+bool
+PrincipalIdentity::is_owner_of(const rgw_owner& owner) const
+{
+  ceph_abort();
+  return false;
+}
+
+bool
+PrincipalIdentity::is_root() const
+{
+  ceph_abort();
+  return false;
+}
+
+uint32_t
+PrincipalIdentity::get_perm_mask() const
+{
+  ceph_abort();
+  return 0;
+}
+
+std::string
+PrincipalIdentity::get_acct_name() const
+{
+  ceph_abort();
+  return {};
+}
+
+std::string
+PrincipalIdentity::get_subuser() const
+{
+  ceph_abort();
+  return {};
+}
+
+const std::string&
+PrincipalIdentity::get_tenant() const
+{
+  ceph_abort();
+  static std::string empty;
+  return empty;
+}
+
+const std::optional<RGWAccountInfo>&
+PrincipalIdentity::get_account() const
+{
+  ceph_abort();
+  static std::optional<RGWAccountInfo> empty;
+  return empty;
+}
+
+void
+PrincipalIdentity::to_str(std::ostream& out) const
+{
+  out << id;
+}
+
+bool
+PrincipalIdentity::is_identity(const Principal& p) const
+{
+  // Wildcard on either side always matches.
+  if (p.is_wildcard() || id.is_wildcard()) {
+    return true;
+  } else if (p.is_account()) {
+    // Compare account to account, since we're matching principal to principal
+    return (id.get_account() == p.get_account());
+  } else if (p.is_user() || p.is_role()) {
+    // Parse out id/path/subuser to pass to match_principal
+    std::string_view name;
+    std::string path; // We need actual storage to prepend a `/`.
+    std::string_view subuser;
+    std::string_view res{id.get_id()};
+    if (id.is_user() || id.is_role()) {
+      auto pos = res.rfind('/');
+      if (pos != res.npos) {
+        path.push_back('/');
+        path.append(res, 0, pos + 1);
+        res = res.substr(pos + 1);
+      }
+    }
+    if (p.is_user()) {
+      if (!id.is_user()) {
+        return false;
+      }
+      auto pos = res.find(':');
+      if (pos != res.npos) {
+        subuser = res.substr(pos + 1, res.npos);
+        res = res.substr(0, pos);
+      }
+      name = res;
+    } else if (p.is_role()) {
+      if (!(id.is_role() || id.is_assumed_role())) {
+        return false;
+      }
+      if (id.is_assumed_role()) {
+        auto pos = res.rfind('/');
+        name = res.substr(0, pos);
+      } else {
+        name = res;
+      }
+    }
+    return ((id.get_account() == p.get_account()) &&
+            match_principal(path, name, subuser, p.get_id()));
+  } else if (p.is_assumed_role()) {
+    // Match role/session to role/session
+    auto role_session = id.get_role_session();
+    if (auto pos = role_session.rfind('/');
+        (pos != role_session.npos) && (pos != 0)) {
+      if (pos = role_session.rfind('/', pos - 1); pos != role_session.npos) {
+        role_session = role_session.substr(pos + 1);
+      }
+    }
+    return (id.is_assumed_role() && (id.get_account() == p.get_account()) &&
+            (role_session == p.get_role_session()));
+  } else if (p.is_oidc_provider()) {
+    return id.is_oidc_provider() && id.get_idp_url() == p.get_idp_url();
+  } else if (p.is_service()) {
+    return id.is_service() && id.get_service() == p.get_service();
+  }
+  return false;
+}
+
+uint32_t
+PrincipalIdentity::get_identity_type() const
+{
+  if (id.is_role() || id.is_assumed_role()) {
+    return TYPE_ROLE;
+  } else if (id.is_oidc_provider()) {
+    return TYPE_WEB;
+  } else {
+    return TYPE_RGW;
+  }
+}
+
+std::optional<rgw::ARN>
+PrincipalIdentity::get_caller_identity() const
+{
+  return std::nullopt;
+}
+} // namespace rgw::auth
index 20dbfa886d994a6192b48adb4c23941c87b9ac2a..e89e4d55199db8e94ef2939a8232482418fa01e8 100644 (file)
@@ -988,6 +988,43 @@ protected:
   }
 };
 
+// A Principal-only identity disconnected from any backends for use in
+// testing and utilities.
+class PrincipalIdentity : public Identity {
+  const Principal id;
+public:
+
+  explicit PrincipalIdentity(Principal&& id) : id(std::move(id)) {}
+
+  // These exist to meet the interface requirement but are
+  // unimplemented and will `abort()`.
+  ACLOwner get_aclowner() const override;
+  uint32_t get_perms_from_aclspec(const DoutPrefixProvider* dpp, const aclspec_t& aclspec) const override;
+  bool is_admin() const override;
+  bool is_owner_of(const rgw_owner& owner) const override;
+  bool is_root() const override;
+  uint32_t get_perm_mask() const override;
+  std::string get_acct_name() const override;
+  std::string get_subuser() const override;
+  const std::string& get_tenant() const override;
+  const std::optional<RGWAccountInfo>& get_account() const override;
+  void to_str(std::ostream& out) const override;
+
+  // Predict whether the server would consider this identity to match a
+  // policy principal `p`, mirroring the structural matching in
+  // rgw::auth::LocalApplier::is_identity (users) and
+  // RoleApplier::is_identity (roles and assumed-role sessions) as far as
+  // the tool's Principal-only identity model allows.
+  bool is_identity(const Principal& p) const override;
+
+  // A role or an assumed-role session evaluates trust policies
+  // through the TYPE_ROLE branch of Statement::eval_principal;
+  // reflect that so rather than always reporting TYPE_RGW.
+  uint32_t get_identity_type() const override;
+  std::optional<rgw::ARN> get_caller_identity() const override;
+};
+
+
 } /* namespace auth */
 } /* namespace rgw */
 
index 99ea71db34d76bbd0a91e8e809676e7cfcdc7db1..e72c61dd97ef0fe995e20c58c1d810d889b6c624 100644 (file)
@@ -220,27 +220,27 @@ public:
     return t == Service;
   }
 
-  const std::string& get_account() const {
+  std::string_view get_account() const {
     return u.tenant;
   }
 
-  const std::string& get_id() const {
+  std::string_view get_id() const {
     return u.id;
   }
 
-  const std::string& get_idp_url() const {
+  std::string_view get_idp_url() const {
     return idp_url;
   }
 
-  const std::string& get_role_session() const {
+  std::string_view get_role_session() const {
     return u.id;
   }
 
-  const std::string& get_role() const {
+  std::string_view get_role() const {
     return u.id;
   }
 
-  const std::string& get_service() const {
+  std::string_view get_service() const {
     return service_id;
   }
 
index af0d6aab8c91b81419fb76e7d32ce95eb8091f33..51d11d64bc3529413c2904557ae64b18ca5077ac 100644 (file)
@@ -40,6 +40,7 @@ using boost::none;
 
 using rgw::auth::Identity;
 using rgw::auth::Principal;
+using rgw::auth::PrincipalIdentity;
 
 using rgw::ARN;
 using rgw::IAM::Effect;
@@ -143,81 +144,6 @@ using rgw::IAM::allValue;
 
 using rgw::IAM::get_managed_policy;
 
-class FakeIdentity : public Identity {
-  const Principal id;
-public:
-
-  explicit FakeIdentity(Principal&& id) : id(std::move(id)) {}
-
-  ACLOwner get_aclowner() const override {
-    ceph_abort();
-    return {};
-  }
-
-  uint32_t get_perms_from_aclspec(const DoutPrefixProvider* dpp, const aclspec_t& aclspec) const override {
-    ceph_abort();
-    return 0;
-  };
-
-  bool is_admin() const override {
-    ceph_abort();
-    return false;
-  }
-
-  bool is_owner_of(const rgw_owner& owner) const override {
-    ceph_abort();
-    return false;
-  }
-
-  bool is_root() const override {
-    ceph_abort();
-    return false;
-  }
-
-  virtual uint32_t get_perm_mask() const override {
-    ceph_abort();
-    return 0;
-  }
-
-  string get_acct_name() const override {
-    abort();
-    return string{};
-  }
-
-  string get_subuser() const override {
-    abort();
-    return string{};
-  }
-
-  const std::string& get_tenant() const override {
-    ceph_abort();
-    static std::string empty;
-    return empty;
-  }
-
-  const std::optional<RGWAccountInfo>& get_account() const override {
-    ceph_abort();
-    static std::optional<RGWAccountInfo> empty;
-    return empty;
-  }
-
-  void to_str(std::ostream& out) const override {
-    out << id;
-  }
-
-  bool is_identity(const Principal& p) const override {
-    return id.is_wildcard() || p.is_wildcard() || p == id;
-  }
-
-  uint32_t get_identity_type() const override {
-    return TYPE_RGW;
-  }
-
-  std::optional<rgw::ARN> get_caller_identity() const override {
-    return std::nullopt;
-  }
-};
-
 class PolicyTest : public ::testing::Test {
 protected:
   intrusive_ptr<CephContext> cct;
@@ -330,10 +256,10 @@ TEST_F(PolicyTest, Eval2) {
   auto p  = Policy(cct.get(), &arbitrary_tenant, example2, true);
   Environment e;
 
-  auto trueacct = FakeIdentity(
+  auto trueacct = PrincipalIdentity(
     Principal::account("ACCOUNT-ID-WITHOUT-HYPHENS"));
 
-  auto notacct = FakeIdentity(
+  auto notacct = PrincipalIdentity(
     Principal::account("some-other-account"));
   for (auto i = 0ULL; i < s3All; ++i) {
     ARN arn1(Partition::aws, Service::s3,
@@ -750,11 +676,11 @@ TEST_F(PolicyTest, Eval7) {
   auto p  = Policy(cct.get(), &arbitrary_tenant, example7, true);
   Environment e;
 
-  auto subacct = FakeIdentity(
+  auto subacct = PrincipalIdentity(
     Principal::user(std::move(""), "A:subA"));
-  auto parentacct = FakeIdentity(
+  auto parentacct = PrincipalIdentity(
     Principal::user(std::move(""), "A"));
-  auto sub2acct = FakeIdentity(
+  auto sub2acct = PrincipalIdentity(
     Principal::user(std::move(""), "A:sub2A"));
 
   ARN arn1(Partition::aws, Service::s3,
@@ -1247,7 +1173,7 @@ TEST_F(IPPolicyTest, EvalIPAddress) {
   blocklistedIP.emplace("aws:SourceIp", "192.168.1.1");
   blocklistedIPv6.emplace("aws:SourceIp", "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
 
-  auto trueacct = FakeIdentity(
+  auto trueacct = PrincipalIdentity(
     Principal::account("ACCOUNT-ID-WITHOUT-HYPHENS"));
   // Without an IP address in the environment then evaluation will always pass
   ARN arn1(Partition::aws, Service::s3,