From b06b7a82ed1a5c57dce8b9ce2fa0931fe0f55ff7 Mon Sep 17 00:00:00 2001 From: Supriti Singh Date: Thu, 19 Mar 2026 10:26:32 +0100 Subject: [PATCH] rgw: Inject keystone userid into IAM policy Expose the Keystone token user UUID as condition key "keystone:userid" so IAM and bucket policies can restrict access by user (e.g. per-user buckets or user-specific allow/deny). On-behalf-of: SAP Signed-off-by: Supriti Singh --- doc/radosgw/bucketpolicy.rst | 1 + doc/radosgw/keystone.rst | 15 +++++++++++---- src/rgw/rgw_auth.cc | 3 +++ src/rgw/rgw_auth.h | 8 ++++++-- src/rgw/rgw_auth_keystone.cc | 6 ++++-- src/test/rgw/test_rgw_iam_policy.cc | 11 +++++++++++ 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/doc/radosgw/bucketpolicy.rst b/doc/radosgw/bucketpolicy.rst index a92f836680f1..392fa192682c 100644 --- a/doc/radosgw/bucketpolicy.rst +++ b/doc/radosgw/bucketpolicy.rst @@ -126,6 +126,7 @@ For all requests, condition keys we support are: Request that authenticate with Keystone also include: - keystone:role +- keystone:userid We support certain S3 condition keys for bucket and object requests. diff --git a/doc/radosgw/keystone.rst b/doc/radosgw/keystone.rst index 577ef5fd2c4c..72e9236f08a2 100644 --- a/doc/radosgw/keystone.rst +++ b/doc/radosgw/keystone.rst @@ -181,10 +181,17 @@ S3 API (with AWS-like access and secret keys), if the ``rgw s3 auth use keystone`` option is set. For details, see :doc:`s3/authentication`. -Requests authenticated via Keystone expose the Keystone role names in the -IAM policy environment as the condition key ``keystone:role``. It can be -used in bucket policies and idenitity policies to allow or deny access by -role (e.g. ``StringEquals`` on ``keystone:role``). +Requests authenticated via Keystone (Swift tokens or S3 with Keystone-managed +credentials) expose the Keystone idenitity in the IAM policy environment +as the condition keys: ``keystone:role`` (role names) and +``keystone:userid`` (user UUID). These conditions can be used in bucket +policies and idenitity policies with ``StringEquals``, ``StringNotEquals`` etc. + +- **keystone:role** - Allow or deny by *role* (e.g only users with role + ``reader`` get read access). Use for RBAC. +- **keystone:userid** - Restrict to specific *user*. Use when policy + depends on a specific user, not just their role. + See :doc:`bucketpolicy` for list of supported condition keys. Service Token Support diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index 4de695839dd9..b152b65c5728 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -1052,6 +1052,9 @@ void rgw::auth::RemoteApplier::modify_request_state(const DoutPrefixProvider* dp s->env.emplace("keystone:role", std::move(role)); } + if (!info.keystone_user_id.empty()) { + s->env.emplace("keystone:userid", info.keystone_user_id); + } } std::optional rgw::auth::RemoteApplier::get_caller_identity() const diff --git a/src/rgw/rgw_auth.h b/src/rgw/rgw_auth.h index 3cb5f0c265a6..37487e63bea4 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -601,6 +601,7 @@ public: const std::string keystone_user; const std::optional keystone_scope; const std::vector keystone_roles; + const std::string keystone_user_id; public: enum class acct_privilege_t { @@ -621,7 +622,9 @@ public: const std::string keystone_user, const uint32_t acct_type=TYPE_NONE, std::optional keystone_scope=std::nullopt, - std::vector keystone_roles = {}) + std::vector keystone_roles = {}, + const std::string keystone_user_id = {} + ) : acct_user(acct_user), acct_name(acct_name), perm_mask(perm_mask), @@ -631,7 +634,8 @@ public: subuser(subuser), keystone_user(keystone_user), keystone_scope(std::move(keystone_scope)), - keystone_roles(std::move(keystone_roles)) { + keystone_roles(std::move(keystone_roles)), + keystone_user_id(keystone_user_id) { } }; diff --git a/src/rgw/rgw_auth_keystone.cc b/src/rgw/rgw_auth_keystone.cc index 31c65b991c11..d06d125b77f8 100644 --- a/src/rgw/rgw_auth_keystone.cc +++ b/src/rgw/rgw_auth_keystone.cc @@ -172,7 +172,8 @@ TokenEngine::get_creds_info(const TokenEngine::token_envelope_t& token token.get_user_name(), TYPE_KEYSTONE, std::move(keystone_scope), - std::move(role_names) + std::move(role_names), + token.get_user_id() }; } @@ -689,7 +690,8 @@ EC2Engine::get_creds_info(const EC2Engine::token_envelope_t& token, token.get_user_name(), TYPE_KEYSTONE, std::move(keystone_scope), - std::move(role_names) + std::move(role_names), + token.get_user_id() }; } diff --git a/src/test/rgw/test_rgw_iam_policy.cc b/src/test/rgw/test_rgw_iam_policy.cc index 266b78bb2c82..b0f83ac4ec70 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -1901,4 +1901,15 @@ TEST_F(ConditionTest, KeyStoneRolePolicyParsing) multi_env.emplace("keystone:role", "member"); multi_env.emplace("keystone:role", "testrole"); EXPECT_TRUE(p->statements[0].conditions[0].eval(multi_env)); +} + +TEST_F(ConditionTest, KeystoneUserIdStringEquals) +{ + const std::string key = "keystone:userid"; + Condition cond{TokenID::StringEquals, key.data(), key.size(), false}; + cond.vals.push_back("user-123"); + + EXPECT_FALSE(cond.eval({})); + EXPECT_TRUE(cond.eval({{key, "user-123"}})); + EXPECT_FALSE(cond.eval({{key, "user-456"}})); } \ No newline at end of file -- 2.47.3