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 <jlayton@redhat.com>
This commit is contained in:
Jeff Layton 2019-08-13 07:32:00 -04:00
parent aa62bbc143
commit eae0127513
8 changed files with 262 additions and 16 deletions

View 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

1
debian/control vendored
View 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,

View 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

View 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

View 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})

95
src/mount/conf.cc Normal file
View File

@ -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());
}

View 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,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);

View 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