]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mount.ceph: fork a child to get info from local configuration
authorJeff Layton <jlayton@redhat.com>
Tue, 13 Aug 2019 11:32:00 +0000 (07:32 -0400)
committerJeff Layton <jlayton@redhat.com>
Fri, 13 Sep 2019 12:14:48 +0000 (08:14 -0400)
When a secret and/or the mon addrs are not specified by the admin,
then mmap a MAP_SHARED buffer and spawn a child process to get that
info. For safety reasons, the child drops all capabilities other than
CAP_DAC_READ_SEARCH (to ensure that it'll be able to read the keyring,
should one be found). To achieve this, we add a new dependency on
libcap-ng.

Add a new C++ file with a single routine that will create a CephContext,
get a list of monitor addresses and scrape the keyring for a secret for
the specified cephx user.

If that info is found, then it is copied to fixed-length buffers in the
MAP_SHARED area and the child exits successfully.

The parent will then vet the returned info and copy it into the
appropriate fields if they are currently blank.

Fixes: https://tracker.ceph.com/issues/16656
Signed-off-by: Jeff Layton <jlayton@redhat.com>
ceph.spec.in
debian/control
doc/man/8/mount.ceph.rst
src/CMakeLists.txt
src/mount/CMakeLists.txt
src/mount/conf.cc [new file with mode: 0644]
src/mount/mount.ceph.c
src/mount/mount.ceph.h

index 05b97fd941193039cd42cfc152a93372e6c6a68b..503eab6f2431546b7ca303932324b53e0f3282e1 100644 (file)
@@ -167,6 +167,7 @@ BuildRequires:      leveldb-devel > 1.2
 BuildRequires: libaio-devel
 BuildRequires: libblkid-devel >= 2.17
 BuildRequires: libcurl-devel
+BuildRequires: libcap-ng-devel
 BuildRequires: libudev-devel
 BuildRequires: libnl3-devel
 BuildRequires: liboath-devel
index aadadcfccea9575a781d9df5350283aaf2b69073..56a15bfa0dac74dbac44e99bd24e8180ea3dcecb 100644 (file)
@@ -28,6 +28,7 @@ Build-Depends: cmake (>= 3.5),
                libblkid-dev (>= 2.17),
 # Crimson      libc-ares-dev,
 # Crimson      libcrypto++-dev,
+               libcap-ng-dev,
                libcunit1-dev,
                libcurl4-openssl-dev,
                libexpat1-dev,
index 0627f5cca80eafcd8747e68ad5a5763dfea37460..4f22cd29f733ab4f047b2e98665bfe942be4e86e 100644 (file)
@@ -9,14 +9,14 @@
 Synopsis
 ========
 
-| **mount.ceph** *monaddr1*\ [,\ *monaddr2*\ ,...]:/[*subdir*] *dir* [
+| **mount.ceph** [*monaddr1*\ ,\ *monaddr2*\ ,...]:/[*subdir*] *dir* [
   -o *options* ]
 
 
 Description
 ===========
 
-**mount.ceph** is a simple helper for mounting the Ceph file system on
+**mount.ceph** is a helper for mounting the Ceph file system on
 a Linux host. It serves to resolve monitor hostname(s) into IP
 addresses and read authentication keys from disk; the Linux kernel
 client component does most of the real work. In fact, it is possible
@@ -34,6 +34,10 @@ learn about all monitors from any responsive monitor. However, it is a
 good idea to specify more than one in case one happens to be down at
 the time of mount.
 
+If the host portion of the device is left blank, then **mount.ceph** will
+attempt to determine monitor addresses using local configuration files
+and/or DNS SRV records.
+
 A subdirectory subdir may be specified if a subset of the file system
 is to be mounted.
 
@@ -126,6 +130,16 @@ Options
 :command:`noasyncreaddir`
   no dcache readdir
 
+:command:`conf`
+  Path to a ceph.conf file. This is used to initialize the ceph context
+  for autodiscovery of monitor addresses and auth secrets. The default is
+  to use the standard search path for ceph.conf files.
+
+Mount Secrets
+=============
+If the `secret` and `secretfile` options are not specified on the command-line
+then the mount helper will spawn a child process that will use the standard
+ceph library routines to find a keyring and fetch the secret from it.
 
 Examples
 ========
@@ -143,6 +157,10 @@ port::
 
         mount.ceph monhost1:7000,monhost2:7000,monhost3:7000:/ /mnt/foo
 
+To automatically determine the monitor addresses from local configuration::
+
+        mount.ceph :/ /mnt/foo
+
 To mount only part of the namespace::
 
         mount.ceph monhost1:/some/small/thing /mnt/thing
index dc7f9c75da3734aad35d5ae12996f5686c61249d..60e261b10e609d3388986fc41cfe7bde0db4a70c 100644 (file)
@@ -640,6 +640,8 @@ add_subdirectory(bash_completion)
 add_subdirectory(client)
 
 if(WITH_LIBCEPHFS)
+  find_package(PkgConfig QUIET REQUIRED)
+  pkg_check_modules(CAPNG REQUIRED libcap-ng)
   set(libcephfs_srcs libcephfs.cc)
   add_library(cephfs ${CEPH_SHARED} ${libcephfs_srcs})
   target_link_libraries(cephfs PRIVATE client ceph-common
index 6ff2617446161c5d101e9dd46bc57a26f7d633ed..67e7130e9e06e8444124b636971e5ed91922bd0b 100644 (file)
@@ -1,9 +1,7 @@
 set(mount_ceph_srcs
-  mount.ceph.c)
+  mount.ceph.c conf.cc)
 add_executable(mount.ceph ${mount_ceph_srcs}
   $<TARGET_OBJECTS:parse_secret_objs>
   $<TARGET_OBJECTS:common_mountcephfs_objs>)
-set_target_properties(mount.ceph PROPERTIES
-  INSTALL_RPATH "")
-target_link_libraries(mount.ceph keyutils::keyutils)
+target_link_libraries(mount.ceph keyutils::keyutils ${CAPNG_LIBRARIES} global ceph-common)
 install(TARGETS mount.ceph DESTINATION ${CMAKE_INSTALL_SBINDIR})
diff --git a/src/mount/conf.cc b/src/mount/conf.cc
new file mode 100644 (file)
index 0000000..228f53e
--- /dev/null
@@ -0,0 +1,95 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <string>
+#include <vector>
+#include <cstring>
+#include <map>
+
+#include "common/ceph_context.h"
+#include "common/ceph_argparse.h"
+#include "global/global_init.h"
+#include "common/config.h"
+#include "auth/KeyRing.h"
+#include "mount.ceph.h"
+#include "mon/MonClient.h"
+
+extern "C" void mount_ceph_get_config_info(const char *config_file,
+                                          const char *name,
+                                          struct ceph_config_info *cci)
+{
+  int err;
+  KeyRing keyring;
+  CryptoKey secret;
+  std::string secret_str;
+  std::string monaddrs;
+  vector<const char *> args = { "--name", name };
+  bool first = true;
+
+  if (config_file) {
+    args.push_back("--conf");
+    args.push_back(config_file);
+  }
+
+  /* Create CephContext */
+  auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
+                        CODE_ENVIRONMENT_UTILITY,
+                        CINIT_FLAG_NO_DAEMON_ACTIONS|CINIT_FLAG_NO_MON_CONFIG);
+  auto& conf = cct->_conf;
+
+  conf.parse_env(cct->get_module_type()); // environment variables override
+  conf.apply_changes(nullptr);
+
+  MonClient monc = MonClient(cct.get());
+  err = monc.build_initial_monmap();
+  if (err)
+    goto scrape_keyring;
+
+  for (const auto& mon : monc.monmap.addr_mons) {
+    auto& eaddr = mon.first;
+
+    // For now, kernel client only accepts legacy addrs
+    if (!eaddr.is_legacy())
+      continue;
+
+    std::string addr;
+    addr += eaddr.ip_only_to_str();
+    addr += ":";
+    addr += std::to_string(eaddr.get_port());
+    /* If this will overrun cci_mons, stop here */
+    if (monaddrs.length() + 1 + addr.length() + 1 > sizeof(cci->cci_mons))
+      break;
+
+    if (first)
+      first = false;
+    else
+      monaddrs += ",";
+
+    monaddrs += addr;
+  }
+
+  if (monaddrs.length())
+    strcpy(cci->cci_mons, monaddrs.c_str());
+  else
+    mount_ceph_debug("Could not discover monitor addresses");
+
+scrape_keyring:
+  err = keyring.from_ceph_context(cct.get());
+  if (err) {
+    mount_ceph_debug("keyring.from_ceph_context failed: %d\n", err);
+    return;
+  }
+
+  if (!keyring.get_secret(conf->name, secret)) {
+    mount_ceph_debug("keyring.get_secret failed\n");
+    return;
+  }
+
+  secret.encode_base64(secret_str);
+
+  if (secret_str.length() + 1 > sizeof(cci->cci_secret)) {
+    mount_ceph_debug("secret is too long\n");
+    return;
+  }
+  strcpy(cci->cci_secret, secret_str.c_str());
+}
index cb72d65d9ed94a7124f46d0742d3351f12dae581..3a612f3d45d4f610dff7d654f2f634be542e54ad 100644 (file)
@@ -4,10 +4,14 @@
 #include <errno.h>
 #include <sys/mount.h>
 #include <stdbool.h>
+#include <sys/mman.h>
+#include <wait.h>
+#include <cap-ng.h>
 
 #include "common/module.h"
 #include "common/secret.h"
 #include "include/addr_parsing.h"
+#include "mount.ceph.h"
 
 #ifndef MS_RELATIME
 # define MS_RELATIME (1<<21)
@@ -27,6 +31,7 @@ struct ceph_mount_info {
        char            *cmi_name;
        char            *cmi_path;
        char            *cmi_mons;
+       char            *cmi_conf;
        char            *cmi_opts;
        int             cmi_opts_len;
        char            cmi_secret[SECRET_BUFSIZE];
@@ -63,16 +68,14 @@ static int parse_src(const char *orig_str, struct ceph_mount_info *cmi)
                fprintf(stderr, "source mount path was not specified\n");
                return -EINVAL;
        }
+
        len = mount_path - orig_str;
-       if (len == 0) {
-               fprintf(stderr, "server address expected\n");
-               return -EINVAL;
+       if (len != 0) {
+               cmi->cmi_mons = strndup(orig_str, len);
+               if (!cmi->cmi_mons)
+                       return -ENOMEM;
        }
 
-       cmi->cmi_mons = strndup(orig_str, len);
-       if (!cmi->cmi_mons)
-               return -ENOMEM;
-
        mount_path++;
        cmi->cmi_path = strdup(mount_path);
        if (!cmi->cmi_path)
@@ -96,6 +99,104 @@ static char *finalize_src(struct ceph_mount_info *cmi)
        return src;
 }
 
+static int
+drop_capabilities()
+{
+       capng_setpid(getpid());
+       capng_clear(CAPNG_SELECT_BOTH);
+       if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
+               fprintf(stderr, "Unable to update permitted capability set.\n");
+               return EX_SYSERR;
+       }
+       if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_DAC_READ_SEARCH)) {
+               fprintf(stderr, "Unable to update effective capability set.\n");
+               return EX_SYSERR;
+       }
+       if (capng_apply(CAPNG_SELECT_BOTH)) {
+               fprintf(stderr, "Unable to apply new capability set.\n");
+               return EX_SYSERR;
+       }
+       return 0;
+}
+
+/*
+ * Attempt to fetch info from the local config file, if one is present. Since
+ * this involves activity that may be dangerous for a privileged task, we
+ * fork(), have the child drop privileges and do the processing and then hand
+ * back the results via memory shared with the parent.
+ */
+static int fetch_config_info(struct ceph_mount_info *cmi)
+{
+       int ret = 0;
+       pid_t pid;
+       struct ceph_config_info *cci;
+
+       /* Don't do anything if we already have requisite info */
+       if (cmi->cmi_secret[0] && cmi->cmi_mons)
+               return 0;
+
+       cci = mmap((void *)0, sizeof(*cci), PROT_READ | PROT_WRITE,
+                  MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+       if (cci == MAP_FAILED) {
+               mount_ceph_debug("Unable to allocate memory: %s\n",
+                                strerror(errno));
+               return EX_SYSERR;
+       }
+
+       pid = fork();
+       if (pid < 0) {
+               mount_ceph_debug("fork() failure: %s\n", strerror(errno));
+               ret = EX_SYSERR;
+               goto out;
+       }
+
+       if (pid == 0) {
+               /* child */
+               ret = drop_capabilities();
+               if (ret)
+                       exit(1);
+               mount_ceph_get_config_info(cmi->cmi_conf, cmi->cmi_name, cci);
+               exit(0);
+       } else {
+               /* parent */
+               pid = wait(&ret);
+               if (!WIFEXITED(ret)) {
+                       mount_ceph_debug("Child process terminated abnormally.\n");
+                       ret = EX_SYSERR;
+                       goto out;
+               }
+               ret = WEXITSTATUS(ret);
+               if (ret) {
+                       mount_ceph_debug("Child exited with status %d\n", ret);
+                       ret = EX_SYSERR;
+                       goto out;
+               }
+
+               /*
+                * Copy values from MAP_SHARED buffer to cmi if we didn't
+                * already find anything and we got something from the child.
+                */
+               size_t len;
+               if (!cmi->cmi_secret[0] && cci->cci_secret[0]) {
+
+                       len = strnlen(cci->cci_secret, SECRET_BUFSIZE);
+                       if (len < SECRET_BUFSIZE) {
+                               memcpy(cmi->cmi_secret, cci->cci_secret, len + 1);
+                       } else {
+                               mount_ceph_debug("secret is too long (len=%zu max=%zu)!\n", len, SECRET_BUFSIZE);
+                       }
+               }
+               if (!cmi->cmi_mons && cci->cci_mons[0]) {
+                       len = strnlen(cci->cci_mons, MON_LIST_BUFSIZE);
+                       if (len < MON_LIST_BUFSIZE)
+                               cmi->cmi_mons = strndup(cci->cci_mons, len + 1);
+               }
+       }
+out:
+       munmap(cci, sizeof(*cci));
+       return ret;
+}
+
 /*
  * this one is partially based on parse_options() from cifs.mount.c
  */
@@ -190,6 +291,15 @@ static int parse_options(const char *data, struct ceph_mount_info *cmi)
                        len = strnlen(value, sizeof(cmi->cmi_secret)) + 1;
                        if (len <= sizeof(cmi->cmi_secret))
                                memcpy(cmi->cmi_secret, value, len);
+               } else if (strncmp(data, "conf", 4) == 0) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "mount option conf requires a value.\n");
+                               return -EINVAL;
+                       }
+                       /* keep pointer to value */
+                       cmi->cmi_conf = strdup(value);
+                       if (!cmi->cmi_conf)
+                               return -ENOMEM;
                } else if (strncmp(data, "name", 4) == 0) {
                        if (!value || !*value) {
                                fprintf(stderr, "mount option name requires a value.\n");
@@ -312,6 +422,7 @@ static void ceph_mount_info_free(struct ceph_mount_info *cmi)
        free(cmi->cmi_name);
        free(cmi->cmi_path);
        free(cmi->cmi_mons);
+       free(cmi->cmi_conf);
 }
 
 static int finalize_options(struct ceph_mount_info *cmi)
@@ -363,8 +474,14 @@ int main(int argc, char *argv[])
                goto out;
        }
 
-       /* Ensure the ceph key_type is available */
-       modprobe();
+       /* We don't care if this errors out, since this is best-effort */
+       fetch_config_info(&cmi);
+
+       if (!cmi.cmi_mons) {
+               fprintf(stderr, "unable to determine mon addresses\n");
+               retval = EX_USAGE;
+               goto out;
+       }
 
        rsrc = finalize_src(&cmi);
        if (!rsrc) {
@@ -373,6 +490,9 @@ int main(int argc, char *argv[])
                goto out;
        }
 
+       /* Ensure the ceph key_type is available */
+       modprobe();
+
        retval = finalize_options(&cmi);
        if (retval) {
                fprintf(stderr, "couldn't finalize options: %d\n", retval);
index 2c07cb7443530aca708f52e302923130e313e8bc..c563597c43832d64a2cd16926fef161a4e982e03 100644 (file)
@@ -24,8 +24,19 @@ extern "C" {
 /* Buffer size for secret= option */
 #define SECRET_OPTION_BUFSIZE (sizeof("secret=") + MAX_SECRET_LEN + 1)
 
+/* 2k should be enough for anyone? */
+#define MON_LIST_BUFSIZE       2048
+
 void mount_ceph_debug(const char *fmt, ...);
 
+struct ceph_config_info {
+       char            cci_secret[SECRET_BUFSIZE];     // auth secret
+       char            cci_mons[MON_LIST_BUFSIZE];     // monitor addrs
+};
+
+void mount_ceph_get_config_info(const char *config_file, const char *name,
+                               struct ceph_config_info *cci);
+
 #ifdef __cplusplus
 }
 #endif