]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
RGW: Add a reader feature 45469/head
authorPete Zaitcev <zaitcev@kotori.zaitcev.us>
Thu, 9 Jun 2022 22:31:16 +0000 (17:31 -0500)
committerPete Zaitcev <zaitcev@kotori.zaitcev.us>
Tue, 28 Feb 2023 22:35:27 +0000 (16:35 -0600)
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>
src/common/options/rgw.yaml.in
src/rgw/rgw_auth_keystone.cc
src/rgw/rgw_auth_keystone.h
src/rgw/rgw_keystone.cc
src/rgw/rgw_keystone.h

index 29dc8cdb93b8efce875f47033e0d4f1a2ae9cf86..3008f00621bcfd619a6a5ea1b3b69423e8d94fd0 100644 (file)
@@ -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
index b818325db9f8ccd829655723f646c44be87ed18c..ef236bc30f7f41ef68a0586c83241fb03eb402c2 100644 (file)
@@ -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));
     }
   }
index f3c9604370b9ac27d66a8aef4f0ebada8158a5a2..11c4f8af77f17b46dcc445fdaad22a3433fbfa9c 100644 (file)
@@ -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,
index 2df417bd0e7a3c1c4ce4780dc64846e649af873c..c225474482a9bc1e665312ac89e7dfc63b1dae0b 100644 (file)
@@ -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)
 {
index 0ba88278268dd5edc0ed77379a47058d2086e615..a1728b25a0484f8a05403795a1de08a073b32036 100644 (file)
@@ -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);
 };