diff --git a/README.md b/README.md index 42f897e..9a471ca 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,23 @@ To enable the checksum (only on XRootD 5.2+): ofs.ckslib * libXrdMultiuser.so ``` +The following optional directives can also be set in the Xrootd configuration file: + +| Directive | Default | Description | +| --- | --- | --- | +| `multiuser.umask ` | (unset) | Apply this umask to files and directories created through the plugin. | +| `multiuser.checksumonwrite ` | `off` | Compute checksums while a file is being written. | +| `multiuser.minuid ` | `500` | Minimum UID a mapped username may resolve to; usernames mapping to a lower UID are treated as system accounts and denied. | +| `multiuser.mingid ` | `500` | Minimum GID a mapped username may resolve to; usernames mapping to a lower GID are treated as system accounts and denied. | + +For example, to allow users and groups with IDs as low as 100 (e.g., groups +imported from a Lustre file system): + +``` +multiuser.minuid 100 +multiuser.mingid 100 +``` + Startup ------- diff --git a/configs/60-osg-multiuser.cfg b/configs/60-osg-multiuser.cfg index 050c646..69ea8b9 100644 --- a/configs/60-osg-multiuser.cfg +++ b/configs/60-osg-multiuser.cfg @@ -17,4 +17,11 @@ if defined ?~XC_ENABLE_MULTIUSER # checksum while it is writing a file. To turn this on, uncomment the # following line: # multiuser.checksumonwrite on + + # Usernames that map to a UID or GID below these thresholds are treated as + # system accounts and are denied access. Both default to 500. Lower them + # if your site has legitimate users/groups with smaller IDs (for example, + # groups imported from a Lustre file system): + # multiuser.minuid 500 + # multiuser.mingid 500 fi diff --git a/src/MultiuserFileSystem.cc b/src/MultiuserFileSystem.cc index 714a99f..9f99df6 100644 --- a/src/MultiuserFileSystem.cc +++ b/src/MultiuserFileSystem.cc @@ -18,11 +18,13 @@ #include "MultiuserFile.hh" #include +#include #include #include #include #include #include +#include #include #include @@ -68,6 +70,31 @@ MultiuserFileSystem::Config(XrdSysLogger *lp, const char *configfn) } Config.Attach(cfgFD); const char *val; + + // Parse a single non-negative integer argument for the given directive. + // On success stores the value in `out` and returns true; on any error it + // logs an appropriate message and returns false. + auto parse_min_id = [&](const char *directive, long int &out) -> bool { + val = Config.GetWord(); + if (!val || !val[0]) { + m_log.Emsg("Config", directive, "must specify a value"); + return false; + } + char *endptr = NULL; + errno = 0; + long int id_val = strtol(val, &endptr, 10); + if (errno || (endptr && *endptr != '\0')) { + m_log.Emsg("Config", directive, "must specify a valid integer value"); + return false; + } + if (id_val < 0) { + m_log.Emsg("Config", directive, "must not be negative"); + return false; + } + out = id_val; + return true; + }; + while ((val = Config.GetMyFirstWord())) { if (!strcmp("multiuser.umask", val)) { val = Config.GetWord(); @@ -92,6 +119,36 @@ MultiuserFileSystem::Config(XrdSysLogger *lp, const char *configfn) m_umask_mode = umask_val; } + // Minimum UID a mapped username may resolve to. + if (!strcmp("multiuser.minuid", val)) { + long int min_uid = 0; + if (!parse_min_id("multiuser.minuid", min_uid)) { + Config.Close(); + return false; + } + if (static_cast(min_uid) > static_cast(std::numeric_limits::max())) { + m_log.Emsg("Config", "multiuser.minuid exceeds the maximum supported UID value"); + Config.Close(); + return false; + } + UserSentry::SetMinimumUid(static_cast(min_uid)); + } + + // Minimum GID a mapped username may resolve to. + if (!strcmp("multiuser.mingid", val)) { + long int min_gid = 0; + if (!parse_min_id("multiuser.mingid", min_gid)) { + Config.Close(); + return false; + } + if (static_cast(min_gid) > static_cast(std::numeric_limits::max())) { + m_log.Emsg("Config", "multiuser.mingid exceeds the maximum supported GID value"); + Config.Close(); + return false; + } + UserSentry::SetMinimumGid(static_cast(min_gid)); + } + // Checksum on write if (!strcmp("multiuser.checksumonwrite", val)) { val = Config.GetWord(); @@ -159,6 +216,13 @@ MultiuserFileSystem::Config(XrdSysLogger *lp, const char *configfn) umask(m_umask_mode); } + { + std::stringstream ss; + ss << "Requiring mapped users to have a minimum UID of " << UserSentry::GetMinimumUid() + << " and a minimum GID of " << UserSentry::GetMinimumGid(); + m_log.Emsg("Config", ss.str().c_str()); + } + return true; } diff --git a/src/UserSentry.hh b/src/UserSentry.hh index fb2c9f5..c83c605 100644 --- a/src/UserSentry.hh +++ b/src/UserSentry.hh @@ -23,10 +23,6 @@ #include #include -// TODO: set this via library parameters. -static const int g_minimum_uid = 500; -static const int g_minimum_gid = 500; - /** * Note: originally, we tried to use CAP_DAC_READ_SEARCH as that provides exactly what we need - @@ -110,6 +106,14 @@ public: static bool IsCmsd() {return m_is_cmsd;} + // Configure the minimum UID/GID a mapped username may resolve to. Any + // username resolving to an ID below these thresholds is treated as a + // system account and denied access. Defaults to 500 (see multiuser.cpp). + static void SetMinimumUid(uid_t uid) {m_min_uid = uid;} + static void SetMinimumGid(gid_t gid) {m_min_gid = gid;} + static uid_t GetMinimumUid() {return m_min_uid;} + static gid_t GetMinimumGid() {return m_min_gid;} + static bool IsGsiUserMapped(const XrdSecEntity *client) { // If VOMS was used to map client, return true if (client->vorg) { return true; } @@ -164,11 +168,11 @@ public: } return; } - if (pwd.pw_uid < g_minimum_uid) { + if (pwd.pw_uid < m_min_uid) { m_log.Emsg("UserSentry", "Multiuser denying access: Username", username.c_str(), "maps to a system UID; rejecting lookup"); return; } - if (pwd.pw_gid < g_minimum_gid) { + if (pwd.pw_gid < m_min_gid) { m_log.Emsg("UserSentry", "Multiuser denying access: Username", username.c_str(), "maps to a system GID; rejecting lookup"); return; } @@ -229,6 +233,11 @@ private: static bool m_is_cmsd; + // Minimum UID/GID thresholds; configurable via multiuser.minuid / + // multiuser.mingid. Definitions (and defaults) live in multiuser.cpp. + static uid_t m_min_uid; + static gid_t m_min_gid; + XrdSysError &m_log; }; diff --git a/src/multiuser.cpp b/src/multiuser.cpp index 522cff3..6dc0437 100644 --- a/src/multiuser.cpp +++ b/src/multiuser.cpp @@ -26,6 +26,11 @@ ChecksumManager* g_checksum_manager = nullptr; bool UserSentry::m_is_cmsd = false; +// Default minimum UID/GID. Usernames mapping to an ID below these values are +// rejected as system accounts. Override via multiuser.minuid / multiuser.mingid. +uid_t UserSentry::m_min_uid = 500; +gid_t UserSentry::m_min_gid = 500; + XrdVERSIONINFO(XrdOssGetFileSystem, Multiuser); // The status-quo to retrieve the default object is to copy/paste the