From: Adam C. Emerson Date: Wed, 15 Jul 2026 02:12:03 +0000 (-0400) Subject: rgw: Add `PrincipalIdentity` for use in testing and utilities X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=3a1686cf291958aa297cbe535ef0fec8124fc6e8;p=ceph.git rgw: Add `PrincipalIdentity` for use in testing and utilities 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 --- diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index bd44e1a8ea0b..53e433c58fab 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -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& +PrincipalIdentity::get_account() const +{ + ceph_abort(); + static std::optional 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 +PrincipalIdentity::get_caller_identity() const +{ + return std::nullopt; +} +} // namespace rgw::auth diff --git a/src/rgw/rgw_auth.h b/src/rgw/rgw_auth.h index 20dbfa886d99..e89e4d55199d 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -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& 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 get_caller_identity() const override; +}; + + } /* namespace auth */ } /* namespace rgw */ diff --git a/src/rgw/rgw_basic_types.h b/src/rgw/rgw_basic_types.h index 99ea71db34d7..e72c61dd97ef 100644 --- a/src/rgw/rgw_basic_types.h +++ b/src/rgw/rgw_basic_types.h @@ -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; } diff --git a/src/test/rgw/test_rgw_iam_policy.cc b/src/test/rgw/test_rgw_iam_policy.cc index af0d6aab8c91..51d11d64bc35 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -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& get_account() const override { - ceph_abort(); - static std::optional 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 get_caller_identity() const override { - return std::nullopt; - } -}; - class PolicyTest : public ::testing::Test { protected: intrusive_ptr 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,