diff options
author | Pete Zaitcev <zaitcev@kotori.zaitcev.us> | 2022-06-10 00:31:16 +0200 |
---|---|---|
committer | Pete Zaitcev <zaitcev@kotori.zaitcev.us> | 2023-02-28 23:35:27 +0100 |
commit | 07f947684cce6742fc49528f3665e6a9d839aee5 (patch) | |
tree | a2bc487d52d3911738e7ffb7cd3249425ccfe83e | |
parent | Merge pull request #50308 from adk3798/mock-fqdn-secure-alertmanager (diff) | |
download | ceph-07f947684cce6742fc49528f3665e6a9d839aee5.tar.xz ceph-07f947684cce6742fc49528f3665e6a9d839aee5.zip |
RGW: Add a reader feature
This feature is prompted by a desire to audit contents of
a cluster safely. Currently, such task is done by a privileged
account, which can damage the data by accident. In this
situation, using an account that can only read is an application
of the least privilege.
Note that the reader is a role in RBAC. So, an account can have
both a reader role and a normal user role. The latter allows it to
write the audit report into the cluster, if the operator wants that.
In OpenStack terms, this reader is known as "system reader persona".
Signed-off-by: Pete Zaitcev <zaitcev@redhat.com>
-rw-r--r-- | src/common/options/rgw.yaml.in | 7 | ||||
-rw-r--r-- | src/rgw/rgw_auth_keystone.cc | 28 | ||||
-rw-r--r-- | src/rgw/rgw_auth_keystone.h | 4 | ||||
-rw-r--r-- | src/rgw/rgw_keystone.cc | 23 | ||||
-rw-r--r-- | src/rgw/rgw_keystone.h | 11 |
5 files changed, 63 insertions, 10 deletions
diff --git a/src/common/options/rgw.yaml.in b/src/common/options/rgw.yaml.in index 29dc8cdb93b..3008f00621b 100644 --- a/src/common/options/rgw.yaml.in +++ b/src/common/options/rgw.yaml.in @@ -797,6 +797,13 @@ options: services: - rgw with_legacy: true +- name: rgw_keystone_accepted_reader_roles + type: str + level: advanced + desc: List of roles that can only be used for reads (Keystone). + services: + - rgw + with_legacy: true - name: rgw_keystone_token_cache_size type: int level: advanced diff --git a/src/rgw/rgw_auth_keystone.cc b/src/rgw/rgw_auth_keystone.cc index b818325db9f..ef236bc30f7 100644 --- a/src/rgw/rgw_auth_keystone.cc +++ b/src/rgw/rgw_auth_keystone.cc @@ -115,16 +115,15 @@ TokenEngine::get_from_keystone(const DoutPrefixProvider* dpp, const std::string& } TokenEngine::auth_info_t -TokenEngine::get_creds_info(const TokenEngine::token_envelope_t& token, - const std::vector<std::string>& admin_roles +TokenEngine::get_creds_info(const TokenEngine::token_envelope_t& token ) const noexcept { using acct_privilege_t = rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t; /* Check whether the user has an admin status. */ acct_privilege_t level = acct_privilege_t::IS_PLAIN_ACCT; - for (const auto& admin_role : admin_roles) { - if (token.has_role(admin_role)) { + for (const auto& role : token.roles) { + if (role.is_admin && !role.is_reader) { level = acct_privilege_t::IS_ADMIN_ACCT; break; } @@ -175,7 +174,7 @@ TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t& token) const }; /* Lambda will obtain a copy of (not a reference to!) allowed_items. */ - return [allowed_items](const rgw::auth::Identity::aclspec_t& aclspec) { + return [allowed_items, token_roles=token.roles](const rgw::auth::Identity::aclspec_t& aclspec) { uint32_t perm = 0; for (const auto& allowed_item : allowed_items) { @@ -186,6 +185,18 @@ TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t& token) const } } + for (const auto& r : token_roles) { + if (r.is_reader) { + if (r.is_admin) { /* system scope reader persona */ + /* + * Because system reader defeats permissions, + * we don't even look at the aclspec. + */ + perm |= RGW_OP_TYPE_READ; + } + } + } + return perm; }; } @@ -205,6 +216,7 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp, explicit RolesCacher(CephContext* const cct) { get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain); get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin); + get_str_vec(cct->_conf->rgw_keystone_accepted_reader_roles, reader); /* Let's suppose that having an admin role implies also a regular one. */ plain.insert(std::end(plain), std::begin(admin), std::end(admin)); @@ -212,6 +224,7 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp, std::vector<std::string> plain; std::vector<std::string> admin; + std::vector<std::string> reader; } roles(cct); static const struct ServiceTokenRolesCacher { @@ -239,7 +252,7 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp, ldpp_dout(dpp, 20) << "cached token.project.id=" << t->get_project_id() << dendl; auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t), - get_creds_info(*t, roles.admin)); + get_creds_info(*t)); return result_t::grant(std::move(apl)); } @@ -314,6 +327,7 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp, if (! t) { return result_t::deny(-EACCES); } + t->update_roles(roles.admin, roles.reader); /* Verify expiration. */ if (t->expired()) { @@ -350,7 +364,7 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp, << " expires: " << t->get_expires() << dendl; token_cache.add(token_id, *t); auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t), - get_creds_info(*t, roles.admin)); + get_creds_info(*t)); return result_t::grant(std::move(apl)); } } diff --git a/src/rgw/rgw_auth_keystone.h b/src/rgw/rgw_auth_keystone.h index f3c9604370b..11c4f8af77f 100644 --- a/src/rgw/rgw_auth_keystone.h +++ b/src/rgw/rgw_auth_keystone.h @@ -41,9 +41,7 @@ class TokenEngine : public rgw::auth::Engine { get_from_keystone(const DoutPrefixProvider* dpp, const std::string& token, bool allow_expired) const; acl_strategy_t get_acl_strategy(const token_envelope_t& token) const; - auth_info_t get_creds_info(const token_envelope_t& token, - const std::vector<std::string>& admin_roles - ) const noexcept; + auth_info_t get_creds_info(const token_envelope_t& token) const noexcept; result_t authenticate(const DoutPrefixProvider* dpp, const std::string& token, const std::string& service_token, diff --git a/src/rgw/rgw_keystone.cc b/src/rgw/rgw_keystone.cc index 2df417bd0e7..c225474482a 100644 --- a/src/rgw/rgw_keystone.cc +++ b/src/rgw/rgw_keystone.cc @@ -375,6 +375,29 @@ int TokenEnvelope::parse(const DoutPrefixProvider *dpp, return 0; } +/* + * Maybe one day we'll have the parser find this in Keystone replies. + * But for now, we use the confguration to augment the list of roles. + */ +void TokenEnvelope::update_roles(const std::vector<std::string> & admin, + const std::vector<std::string> & reader) +{ + for (auto& iter: roles) { + for (const auto& r : admin) { + if (fnmatch(r.c_str(), iter.name.c_str(), 0) == 0) { + iter.is_admin = true; + break; + } + } + for (const auto& r : reader) { + if (fnmatch(r.c_str(), iter.name.c_str(), 0) == 0) { + iter.is_reader = true; + break; + } + } + } +} + bool TokenCache::find(const std::string& token_id, rgw::keystone::TokenEnvelope& token) { diff --git a/src/rgw/rgw_keystone.h b/src/rgw/rgw_keystone.h index 0ba88278268..a1728b25a04 100644 --- a/src/rgw/rgw_keystone.h +++ b/src/rgw/rgw_keystone.h @@ -161,8 +161,17 @@ public: class Role { public: + Role() : is_admin(false), is_reader(false) { } + Role(const Role &r) { + id = r.id; + name = r.name; + is_admin = r.is_admin; + is_reader = r.is_reader; + } std::string id; std::string name; + bool is_admin; + bool is_reader; void decode_json(JSONObj *obj); }; @@ -204,6 +213,8 @@ public: const std::string& token_str, ceph::buffer::list& bl /* in */, ApiVersion version); + void update_roles(const std::vector<std::string> & admin, + const std::vector<std::string> & reader); }; |