From eae01275134ea550367a09e6adb084ffc9f4bd46 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 13 Aug 2019 07:32:00 -0400 Subject: [PATCH] mount.ceph: fork a child to get info from local configuration 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 --- ceph.spec.in | 1 + debian/control | 1 + doc/man/8/mount.ceph.rst | 22 +++++- src/CMakeLists.txt | 2 + src/mount/CMakeLists.txt | 6 +- src/mount/conf.cc | 95 ++++++++++++++++++++++++++ src/mount/mount.ceph.c | 140 ++++++++++++++++++++++++++++++++++++--- src/mount/mount.ceph.h | 11 +++ 8 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 src/mount/conf.cc diff --git a/ceph.spec.in b/ceph.spec.in index 05b97fd9411..503eab6f243 100644 --- a/ceph.spec.in +++ b/ceph.spec.in @@ -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 diff --git a/debian/control b/debian/control index aadadcfccea..56a15bfa0da 100644 --- a/debian/control +++ b/debian/control @@ -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, diff --git a/doc/man/8/mount.ceph.rst b/doc/man/8/mount.ceph.rst index 0627f5cca80..4f22cd29f73 100644 --- a/doc/man/8/mount.ceph.rst +++ b/doc/man/8/mount.ceph.rst @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc7f9c75da3..60e261b10e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/mount/CMakeLists.txt b/src/mount/CMakeLists.txt index 6ff26174461..67e7130e9e0 100644 --- a/src/mount/CMakeLists.txt +++ b/src/mount/CMakeLists.txt @@ -1,9 +1,7 @@ set(mount_ceph_srcs - mount.ceph.c) + mount.ceph.c conf.cc) add_executable(mount.ceph ${mount_ceph_srcs} $ $) -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 index 00000000000..228f53e7218 --- /dev/null +++ b/src/mount/conf.cc @@ -0,0 +1,95 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include +#include +#include +#include + +#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 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()); +} diff --git a/src/mount/mount.ceph.c b/src/mount/mount.ceph.c index cb72d65d9ed..3a612f3d45d 100644 --- a/src/mount/mount.ceph.c +++ b/src/mount/mount.ceph.c @@ -4,10 +4,14 @@ #include #include #include +#include +#include +#include #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,15 +68,13 @@ 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; - } - cmi->cmi_mons = strndup(orig_str, len); - if (!cmi->cmi_mons) - return -ENOMEM; + len = mount_path - orig_str; + if (len != 0) { + cmi->cmi_mons = strndup(orig_str, len); + if (!cmi->cmi_mons) + return -ENOMEM; + } mount_path++; cmi->cmi_path = strdup(mount_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); diff --git a/src/mount/mount.ceph.h b/src/mount/mount.ceph.h index 2c07cb74435..c563597c438 100644 --- a/src/mount/mount.ceph.h +++ b/src/mount/mount.ceph.h @@ -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