mirror of
https://github.com/ceph/ceph
synced 2025-01-19 17:41:39 +00:00
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:
parent
aa62bbc143
commit
eae0127513
@ -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
1
debian/control
vendored
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
95
src/mount/conf.cc
Normal 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());
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user