]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
cephfs: add auth caps based on fs names
authorDouglas Fuller <dfuller@redhat.com>
Thu, 7 Mar 2019 15:17:23 +0000 (10:17 -0500)
committerRishabh Dave <ridave@redhat.com>
Thu, 10 Sep 2020 11:40:51 +0000 (17:10 +0530)
Add new auth caps to restrict access to clients based on fsnames. To
specify this, for example:

mds 'allow rw fsname=cephfs1'

This will restrict client access to fs name "cephfs1" only. Messages to
active MDS assigned to any other FSMap will be dropped. Standby MDS not
associated with an FSMap will accept messages from clients. To allow
multiple file systems, create MDS cap as follows -

mds 'allow rw fsname=cephfs1, allow rw fsname=cephfs2'

Fixes: http://tracker.ceph.com/issues/15070
Signed-off-by: Douglas Fuller <dfuller@redhat.com>
Signed-off-by: Rishabh Dave <ridave@redhat.com>
doc/cephfs/client-auth.rst
src/mds/MDSAuthCaps.cc
src/mds/MDSAuthCaps.h
src/mds/Server.cc
src/mds/SessionMap.h

index 33ab7a7a53b774e17b37b1554391b311e364756a..dc2e9d33184742df697d795d26c068d049b67f31 100644 (file)
@@ -184,3 +184,45 @@ And the client can only see the FS that it has authorization for::
 Standby MDS daemons will always be displayed. Note that the information about
 restricted MDS daemons and file systems may become available by other means,
 such as ``ceph health detail``.
+
+MDS communication restriction
+=============================
+
+By default, user applications may communicate with any MDS, whether or not
+they are allowed to modify data on an associated file system (see
+`Path restriction` above). Client's communication can be restricted to MDS
+daemons associated with particular file system(s) by adding MDS caps for that
+particular file system. Consider the following example where the Ceph cluster
+has 2 FSs::
+
+    $ ceph fs ls
+    name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ]
+    name: cephfs2, metadata pool: cephfs2_metadata, data pools: [cephfs2_data ]
+
+Client ``someuser`` is authorized only for one FS::
+
+    $ ceph fs authorize cephfs client.someuser / rw
+    [client.someuser]
+        key = AQBPSARfg8hCJRAAEegIxjlm7VkHuiuntm6wsA==
+    $ ceph auth get client.someuser > ceph.client.someuser.keyring
+    exported keyring for client.someuser
+    $ cat ceph.client.someuser.keyring
+    [client.someuser]
+        key = AQBPSARfg8hCJRAAEegIxjlm7VkHuiuntm6wsA==
+        caps mds = "allow rw fsname=cephfs"
+        caps mon = "allow r"
+        caps osd = "allow rw tag cephfs data=cephfs"
+
+Mounting ``cephfs1`` with ``someuser`` works::
+
+    $ sudo ceph-fuse /mnt/cephfs1 -n client.someuser -k ceph.client.someuser.keyring --client-fs=cephfs
+    ceph-fuse[96634]: starting ceph client
+    ceph-fuse[96634]: starting fuse
+    $ mount | grep ceph-fuse
+    ceph-fuse on /mnt/cephfs1 type fuse.ceph-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other)
+
+But mounting ``cephfs2`` does not::
+
+    $ sudo ceph-fuse /mnt/cephfs2 -n client.someuser -k ceph.client.someuser.keyring --client-fs=cephfs2
+    ceph-fuse[96599]: starting ceph client
+    ceph-fuse[96599]: ceph mount failed with (1) Operation not permitted
index 3f0c04da9a9e98118489be05f60a9f47643ec425..40db4c1ec1ad51474947acf19a79c8163b8781ff 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "common/debug.h"
 #include "MDSAuthCaps.h"
+#include "mdstypes.h"
 #include "include/ipaddr.h"
 
 #define dout_subsys ceph_subsys_mds
@@ -61,16 +62,20 @@ struct MDSCapParser : qi::grammar<Iterator, MDSAuthCaps()>
       lexeme[lit("'") >> *(char_ - '\'') >> '\''];
     unquoted_path %= +char_("a-zA-Z0-9_./-");
     network_str %= +char_("/.:a-fA-F0-9][");
+    fs_name_str %= +char_("a-zA-Z0-9-_.");
 
     // match := [path=<path>] [uid=<uid> [gids=<gid>[,<gid>...]]
     path %= (spaces >> lit("path") >> lit('=') >> (quoted_path | unquoted_path));
     uid %= (spaces >> lit("uid") >> lit('=') >> uint_);
     uintlist %= (uint_ % lit(','));
     gidlist %= -(spaces >> lit("gids") >> lit('=') >> uintlist);
+    fs_name %= -(spaces >> lit("fsname") >> lit('=') >> fs_name_str);
     match = -(
             (uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2)] |
             (path >> uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2, _3)] |
-             (path)[_val = phoenix::construct<MDSCapMatch>(_1)]);
+             (path)[_val = phoenix::construct<MDSCapMatch>(_1)] |
+             (fs_name)[_val = phoenix::construct<MDSCapMatch>(std::string(),
+                                                             _1)]);
 
     // capspec = * | r[w][p][s]
     capspec = spaces >> (
@@ -97,8 +102,8 @@ struct MDSCapParser : qi::grammar<Iterator, MDSAuthCaps()>
   }
   qi::rule<Iterator> spaces;
   qi::rule<Iterator, string()> quoted_path, unquoted_path, network_str;
+  qi::rule<Iterator, string()> fs_name_str, fs_name, path;
   qi::rule<Iterator, MDSCapSpec()> capspec;
-  qi::rule<Iterator, string()> path;
   qi::rule<Iterator, uint32_t()> uid;
   qi::rule<Iterator, std::vector<uint32_t>() > uintlist;
   qi::rule<Iterator, std::vector<uint32_t>() > gidlist;
index 940d474a8468a35a4983b46ab6f4edee32942fa0..06d6c7190f4be8ecfdd95490c0f739f805a2fd2a 100644 (file)
@@ -23,6 +23,8 @@
 #include "include/types.h"
 #include "common/debug.h"
 
+#include "mdstypes.h"
+
 // unix-style capabilities
 enum {
   MAY_READ     = (1 << 0),
@@ -89,14 +91,24 @@ private:
 struct MDSCapMatch {
   static const int64_t MDS_AUTH_UID_ANY = -1;
 
-  MDSCapMatch() : uid(MDS_AUTH_UID_ANY) {}
-  MDSCapMatch(int64_t uid_, std::vector<gid_t>& gids_) : uid(uid_), gids(gids_) {}
+  MDSCapMatch() : uid(MDS_AUTH_UID_ANY), fs_name(std::string()) {}
+
+  MDSCapMatch(int64_t uid_, std::vector<gid_t>& gids_) :
+    uid(uid_), gids(gids_), fs_name(std::string()) {}
+
   explicit MDSCapMatch(const std::string &path_)
-    : uid(MDS_AUTH_UID_ANY), path(path_) {
+    : uid(MDS_AUTH_UID_ANY), path(path_), fs_name(std::string()) {
+    normalize_path();
+  }
+
+  explicit MDSCapMatch(std::string path, std::string fs_name) :
+    uid(MDS_AUTH_UID_ANY), path(std::move(path)), fs_name(std::move(fs_name))
+  {
     normalize_path();
   }
+
   MDSCapMatch(const std::string& path_, int64_t uid_, std::vector<gid_t>& gids_)
-    : uid(uid_), gids(gids_), path(path_) {
+    : uid(uid_), gids(gids_), path(path_), fs_name(std::string()) {
     normalize_path();
   }
 
@@ -124,6 +136,7 @@ struct MDSCapMatch {
   int64_t uid;       // Require UID to be equal to this, if !=MDS_AUTH_UID_ANY
   std::vector<gid_t> gids;  // Use these GIDs
   std::string path;  // Require path to be child of this (may be "" or "/" for any)
+  std::string fs_name;
 };
 
 struct MDSCapGrant {
@@ -173,6 +186,27 @@ public:
                  const entity_addr_t& addr) const;
   bool path_capable(std::string_view inode_path) const;
 
+  bool fs_name_capable(std::string_view fs_name, unsigned mask) const {
+    if (allow_all()) {
+      return true;
+    }
+
+    for (const MDSCapGrant &g : grants) {
+      if (g.match.fs_name == fs_name || g.match.fs_name.empty() ||
+         g.match.fs_name == "*") {
+       if (mask & MAY_READ && g.spec.allow_read()) {
+         return true;
+       }
+
+       if (mask & MAY_WRITE && g.spec.allow_write()) {
+         return true;
+       }
+      }
+    }
+
+    return false;
+  }
+
   friend std::ostream &operator<<(std::ostream &out, const MDSAuthCaps &cap);
 private:
   CephContext *cct = nullptr;
index 515f96bbeba63a5aa504b08a50ce73774b65d136..6b534930b19d04ed6ce123f7a3ff42c57abce7aa 100644 (file)
@@ -494,6 +494,12 @@ void Server::handle_client_reclaim(const cref_t<MClientReclaim> &m)
     return;
   }
 
+  std::string_view fs_name = mds->get_fs_name();
+  if (!fs_name.empty() && !session->fs_name_capable(fs_name, MAY_READ)) {
+    dout(0) << " dropping message not allowed for this fs_name: " << *m << dendl;
+    return;
+  }
+
   if (mds->get_state() < MDSMap::STATE_CLIENTREPLAY) {
     mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
     return;
@@ -522,6 +528,16 @@ void Server::handle_client_session(const cref_t<MClientSession> &m)
     return;
   }
 
+  std::string_view fs_name = mds->get_fs_name();
+  if (!fs_name.empty() && !session->fs_name_capable(fs_name, MAY_READ)) {
+    dout(0) << " dropping message not allowed for this fs_name: " << *m << dendl;
+    auto reply = make_message<MClientSession>(CEPH_SESSION_REJECT);
+    reply->metadata["error_string"] = "client doesn't have caps for FS \"" +
+                                     std::string(fs_name) + "\"";
+    mds->send_message(std::move(reply), m->get_connection());
+    return;
+  }
+
   if (m->get_op() == CEPH_SESSION_REQUEST_RENEWCAPS) {
     // always handle renewcaps (state >= MDSMap::STATE_RECONNECT)
   } else if (m->get_op() == CEPH_SESSION_REQUEST_CLOSE) {
index 01e13266ea863c779b75d7ed6b284aceddc4b259..ffa7973a2415b78710a0af0187968d0a992185e5 100644 (file)
@@ -385,6 +385,10 @@ public:
   int check_access(CInode *in, unsigned mask, int caller_uid, int caller_gid,
                   const std::vector<uint64_t> *gid_list, int new_uid, int new_gid);
 
+  bool fs_name_capable(std::string_view fs_name, unsigned mask) const {
+    return auth_caps.fs_name_capable(fs_name, mask);
+  }
+
   void set_connection(ConnectionRef con) {
     connection = std::move(con);
     auto& c = connection;