// ---------------------------------------------------------------------- // File: Mapping.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "common/Namespace.hh" #include "common/Mapping.hh" #include "common/Macros.hh" #include "common/Logging.hh" #include "common/SecEntity.hh" #include "common/SymKeys.hh" #include "common/StringUtils.hh" #include "common/token/EosTok.hh" #include "XrdNet/XrdNetUtils.hh" #include "XrdNet/XrdNetAddr.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdSec/XrdSecEntityAttr.hh" #include "XrdAcc/XrdAccAuthorize.hh" #include #include #include EOSCOMMONNAMESPACE_BEGIN /*----------------------------------------------------------------------------*/ // global mapping objects /*----------------------------------------------------------------------------*/ RWMutex Mapping::gMapMutex; Mapping::UserRoleMap_t Mapping::gUserRoleVector; Mapping::GroupRoleMap_t Mapping::gGroupRoleVector; Mapping::VirtualUserMap_t Mapping::gVirtualUidMap; Mapping::VirtualGroupMap_t Mapping::gVirtualGidMap; Mapping::SudoerMap_t Mapping::gSudoerMap; std::atomic Mapping::gRootSquash = true; std::atomic Mapping::gSecondaryGroups = false; std::atomic Mapping::gTokenSudo = Mapping::kAlways; Mapping::GeoLocationMap_t Mapping::gGeoMap; int Mapping::gNobodyAccessTreeDeepness(1024); Mapping::AllowedTidentMatches_t Mapping::gAllowedTidentMatches; ShardedCache Mapping::gShardedPhysicalUidCache( 8); ShardedCache Mapping::gShardedPhysicalGidCache( 8); ShardedCache Mapping::gShardedNegativeUserNameCache(8); ShardedCache Mapping::gShardedNegativeGroupNameCache(8); ShardedCache Mapping::gShardedNegativePhysicalUidCache(8); ShardedCache Mapping::ActiveTidentsSharded(16); ShardedCache Mapping::ActiveUidsSharded(16); std::mutex Mapping::gPhysicalUserNameCacheMutex; std::mutex Mapping::gPhysicalGroupNameCacheMutex; std::map Mapping::gPhysicalUserNameCache; std::map Mapping::gPhysicalGroupNameCache; std::map Mapping::gPhysicalUserIdCache; std::map Mapping::gPhysicalGroupIdCache; Mapping::ip_cache Mapping::gIpCache(300); OAuth Mapping::gOAuth; static std::string g_pwd_key = "\"\""; static std::string g_pwd_uid_key = g_pwd_key + ":uid"; static std::string g_pwd_gid_key = g_pwd_key + ":gid"; static std::string g_https_uid_key = "https:" + g_pwd_uid_key; static std::string g_https_gid_key = "https:" + g_pwd_gid_key; static std::string g_sss_uid_key = "sss:" + g_pwd_uid_key; static std::string g_sss_gid_key = "sss:" + g_pwd_gid_key; static std::string g_unix_uid_key = "unix:" + g_pwd_uid_key; static std::string g_unix_gid_key = "unix:" + g_pwd_gid_key; static std::string g_gsi_uid_key = "gsi:" + g_pwd_uid_key; static std::string g_gsi_gid_key = "gsi:" + g_pwd_gid_key; static std::string g_krb_uid_key = "krb5:" + g_pwd_uid_key; static std::string g_krb_gid_key = "krb5:" + g_pwd_gid_key; static std::string g_oauth2_uid_key = "oauth2:" + g_pwd_uid_key; static std::string g_oauth2_gid_key = "oauth2:" + g_pwd_gid_key; // not used yet static std::string g_ztn_uid_key = "ztn:" + g_pwd_uid_key; static std::string g_ztn_gid_key = "ztn:" + g_pwd_gid_key; //Static vars for nobody ids which may change in the future static uid_t g_nobody_uid = 99; static gid_t g_nobody_gid = 99; // flag to indicate whether the mapping is initialized std::once_flag g_cache_map_init; //------------------------------------------------------------------------------ // Initialize static maps //------------------------------------------------------------------------------ void Mapping::Init() { // allow FUSE client access as root via env variable if (getenv("EOS_FUSE_NO_ROOT_SQUASH") && !strcmp("1", getenv("EOS_FUSE_NO_ROOT_SQUASH"))) { gRootSquash = false; } if (getenv("EOS_SECONDARY_GROUPS") && !strcmp("1", getenv("EOS_SECONDARY_GROUPS"))) { gSecondaryGroups = true; } gOAuth.Init(); try { std::call_once(g_cache_map_init, []() { gShardedPhysicalUidCache.reset_cleanup_thread(3600 * 1000, "UidCacheGC"); gShardedPhysicalGidCache.reset_cleanup_thread(3600 * 1000, "GidCacheGC"); gShardedNegativeUserNameCache.reset_cleanup_thread(3600 * 1000, "NegUserNameGC"); gShardedNegativeGroupNameCache.reset_cleanup_thread(3600 * 1000, "NegGroupNameGC"); gShardedNegativePhysicalUidCache.reset_cleanup_thread(3600 * 1000, "NegUidGC"); ActiveUidsSharded.reset_cleanup_thread(300 * 1000, "ActiveUidsSharded"); ActiveTidentsSharded.reset_cleanup_thread(300 * 1000, "ActiveTidentsGC"); }); } catch (...) { // we can't log here as the logging system is not initialized yet } } //------------------------------------------------------------------------------ // Reset //------------------------------------------------------------------------------ void Mapping::Reset() { { std::scoped_lock lock{gPhysicalUserNameCacheMutex, gPhysicalGroupNameCacheMutex}; gPhysicalGroupNameCache.clear(); gPhysicalUserNameCache.clear(); gPhysicalGroupIdCache.clear(); gPhysicalUserIdCache.clear(); } ActiveTidentsSharded.clear(); ActiveUidsSharded.clear(); } //------------------------------------------------------------------------------ // Map a client to its virtual identity //------------------------------------------------------------------------------ void Mapping::IdMap(const XrdSecEntity* client, const char* env, const char* tident, VirtualIdentity& vid, XrdAccAuthorize* authz_obj, Access_Operation acc_op, std::string path, bool log) { if (!client) { return; } eos_static_debug("msg=\"XrdSecEntity client\" name=\"%s\" role=\"%s\" " "group=\"%s\" tident=\"%s\" cred=\"%s\"", client->name, client->role, client->grps, client->tident, (client->creds ? client->creds : "none")); // We start as 'nobody' vid = VirtualIdentity::Nobody(); XrdOucEnv Env(env); std::string authz = (Env.Get("authz") ? Env.Get("authz") : ""); vid.name = (client->name ? client->name : ""); vid.tident = tident; vid.sudoer = false; vid.gateway = false; // first map by alias XrdOucString useralias = client->prot; useralias += ":"; useralias += "\""; useralias += (client->name ? client->name : ""); useralias += "\""; useralias += ":"; XrdOucString groupalias = useralias; useralias += "uid"; groupalias += "gid"; RWMutexReadLock lock(gMapMutex); vid.prot = client->prot; // @todo (esindril) this is just a workaround for the fact that XrdHttp // does not properly populate the prot field in the XrdSecEntity object. // See https://github.com/xrootd/xrootd/issues/1122 if ((strlen(client->tident) == 4) && (strcmp(client->tident, "http") == 0)) { vid.prot = "https"; } // SSS and GRPC might contain a key embedded in the endorsements field if ((vid.prot == "sss") || (vid.prot == "grpc") || (vid.prot == "https")) { vid.key = (client->endorsements ? client->endorsements : ""); eos_static_debug("msg=\"client endorsement\" key=\"%s\"", vid.key.c_str()); } // KRB5 mapping if ((vid.prot == "krb5")) { eos_static_debug("%s", "msg=\"krb5 mapping\""); // Use physical mapping for kerberos names if (gVirtualUidMap.count(g_krb_uid_key)) { Mapping::getPhysicalUids(client->name, vid); } if (gVirtualGidMap.count(g_krb_gid_key)) { Mapping::getPhysicalGids(client->name, vid); } } // GSI mapping if ((vid.prot == "gsi")) { eos_static_debug("%s", "msg=\"gsi mapping\""); // Use physical mapping for gsi names if (gVirtualUidMap.count(g_gsi_uid_key)) { Mapping::getPhysicalUids(client->name, vid); } if (gVirtualGidMap.count(g_gsi_gid_key)) { Mapping::getPhysicalGids(client->name, vid); } HandleVOMS(client, vid); } // HTTPS mapping if (vid.prot == "https") { eos_static_debug("%s", "msg=\"https mapping\""); // Handle bearer token authorization if (authz_obj && !authz.empty() && (authz.find("Bearer%20") == 0)) { if (authz_obj->Access(client, path.c_str(), acc_op, &Env) == XrdAccPriv_None) { eos_static_err("msg=\"failed token authz\" path=\"%s\" opaque=\"%s\"", path.c_str(), env); return; } } // Check if we have the request.name in the attributes of the XrdSecEntity // object which is the client username according to the authz mapping. std::string client_username; std::string user_value; static const std::string user_key = "request.name"; if (client->eaAPI->Get(user_key, user_value)) { client_username = user_value; } else { if (client->name) { client_username = client->name; } } HandleUidGidMapping(client_username.c_str(), vid, g_https_uid_key, g_https_gid_key); HandleVOMS(client, vid); HandleKEYS(client, vid); } // ZTN mapping if ((vid.prot == "ztn") && client->creds) { // Handle bearer token authorization eos_static_debug("msg=\"dumping client credentials/token\" creds=\"%s\"", client->creds); if (authz_obj) { authz = "&authz="; authz += client->creds; XrdOucEnv op_env(authz.c_str()); if (authz_obj->Access(client, path.c_str(), acc_op, &op_env) == XrdAccPriv_None) { eos_static_err("msg=\"failed token authz\" path=\"%s\" opaque=\"%s\" " "authz=\"%s\"", path.c_str(), env, authz.c_str()); return; } // Check if we have the request.name in the attributes of the XrdSecEntity // object which is the client username according to the authz mapping. std::string client_username; std::string user_value; static const std::string user_key = "request.name"; if (client->eaAPI->Get(user_key, user_value)) { client_username = user_value; } else { if (client->name) { client_username = client->name; } } HandleUidGidMapping(client_username.c_str(), vid, g_ztn_uid_key, g_ztn_gid_key); } else { // add the ZTN credential if there is not another one provided if (authz.empty()) { authz = client->creds; } } } // sss mapping if ((vid.prot == "sss")) { HandleUidGidMapping(client->name, vid, g_sss_uid_key, g_sss_gid_key); } // unix mapping if ((vid.prot == "unix")) { HandleUidGidMapping(client->name, vid, g_unix_uid_key, g_unix_gid_key); } // tident mapping XrdOucString mytident = ""; XrdOucString myrole = ""; XrdOucString wildcardtident = ""; XrdOucString host = ""; XrdOucString stident = "tident:"; stident += "\""; stident += ReduceTident(vid.tident, wildcardtident, mytident, host); if (host == "127.0.0.1") { host = "localhost"; } myrole = mytident; myrole.erase(mytident.find("@")); // FUSE select's now the role via [:connectionid] // the connection id is already removed by ReduceTident myrole.erase(myrole.find(".")); XrdOucString swctident = "tident:"; swctident += "\""; swctident += wildcardtident; XrdOucString suidtident = stident; suidtident += "\":uid"; XrdOucString sgidtident = stident; sgidtident += "\":gid"; XrdOucString swcuidtident = swctident; swcuidtident += "\":uid"; XrdOucString swcgidtident = swctident; swcgidtident += "\":gid"; XrdOucString sprotuidtident = swcuidtident; XrdOucString sprotgidtident = swcgidtident; // there can be a protocol specific rule like sss:@:uid... sprotuidtident.replace("*", vid.prot); // there can be a protocol specific rule like sss:@:gid... sprotgidtident.replace("*", vid.prot); eos_static_debug("swcuidtident=%s sprotuidtident=%s myrole=%s", swcuidtident.c_str(), sprotuidtident.c_str(), myrole.c_str()); if (auto kv = gVirtualUidMap.find(suidtident.c_str()); kv != gVirtualUidMap.end()) { // eos_static_debug("tident mapping"); vid.uid = kv->second; vid.allowed_uids.insert(vid.uid); vid.allowed_uids.insert(99); } if (auto kv = gVirtualGidMap.find(sgidtident.c_str()); kv != gVirtualGidMap.end()) { // eos_static_debug("tident mapping"); vid.gid = kv->second; vid.allowed_gids.insert(vid.gid); vid.allowed_gids.insert(99); } // Wildcard tidents/protocol tidents - one can define mapping entries like // '*@host:uid=>0' e.g. for fuse mounts or only for a certain protocol // like 'sss@host:uid=>0' XrdOucString tuid = ""; XrdOucString tgid = ""; if (gVirtualUidMap.count(swcuidtident.c_str())) { // there is an entry like "*@@:uid" tuid = sprotuidtident.c_str(); } else { if (gAllowedTidentMatches.size()) { std::string sprot = vid.prot.c_str(); for (auto it = gAllowedTidentMatches.begin(); it != gAllowedTidentMatches.end(); ++it) { if (sprot != it->first.c_str()) { continue; } if (host.matches(it->second.c_str())) { sprotuidtident.replace(host.c_str(), it->second.c_str()); if (gVirtualUidMap.count(sprotuidtident.c_str())) { tuid = sprotuidtident.c_str(); break; } } } } } } if (gVirtualGidMap.count(swcgidtident.c_str())) { // there is an entry like "*@:gid" matching all protocols tgid = swcgidtident.c_str(); } else { if (gVirtualGidMap.count(sprotgidtident.c_str())) { // there is a protocol specific entry "@:uid" tgid = sprotgidtident.c_str(); } else { if (gAllowedTidentMatches.size()) { std::string sprot = vid.prot.c_str(); for (auto it = gAllowedTidentMatches.begin(); it != gAllowedTidentMatches.end(); ++it) { if (sprot != it->first.c_str()) { continue; } if (host.matches(it->second.c_str())) { sprotuidtident.replace(host.c_str(), it->second.c_str()); if (gVirtualUidMap.count(sprotuidtident.c_str())) { tuid = sprotuidtident.c_str(); break; } } } } } } eos_static_debug("tuid=%s tgid=%s", tuid.c_str(), tgid.c_str()); if (gVirtualUidMap.count(tuid.c_str())) { if (!gVirtualUidMap[tuid.c_str()]) { if (gRootSquash && (host != "localhost") && (host != "localhost.localdomain") && (host != "localhost6.localdomain6") && (vid.name == "root") && (myrole == "root")) { eos_static_debug("tident root uid squash"); vid.allowed_uids.clear(); vid.allowed_uids.insert(DAEMONUID); vid.uid = DAEMONUID; vid.allowed_gids.clear(); vid.gid = DAEMONGID; vid.allowed_gids.insert(DAEMONGID); } else { eos_static_debug("tident uid mapping prot=%s name=%s", vid.prot.c_str(), vid.name.c_str()); vid.allowed_uids.clear(); // use physical mapping // unix protocol maps to the role if the client is the root account // otherwise it maps to the unix ID on the client host if (((vid.prot == "unix") && (vid.name == "root")) || ((vid.prot == "sss") && (vid.name == "daemon"))) { Mapping::getPhysicalIdShards(myrole.c_str(), vid); } else { if (client->name != nullptr) { Mapping::getPhysicalIdShards(client->name, vid); } } vid.gateway = true; } } else { eos_static_debug("%s", "msg=\"tident uid forced mapping\""); // map to the requested id vid.allowed_uids.clear(); vid.uid = gVirtualUidMap[tuid.c_str()]; vid.allowed_uids.insert(vid.uid); vid.allowed_uids.insert(99); vid.allowed_gids.clear(); vid.gid = 99; vid.allowed_gids.insert(vid.gid); } } if (gVirtualGidMap.count(tgid.c_str())) { if (!gVirtualGidMap[tgid.c_str()]) { if (gRootSquash && (host != "localhost") && (host != "localhost.localdomain") && (vid.name == "root") && (myrole == "root")) { eos_static_debug("%s", "msg=\"tident root gid squash\""); vid.allowed_gids.clear(); vid.allowed_gids.insert(DAEMONGID); vid.gid = DAEMONGID; } else { eos_static_debug("%s", "msg=\"tident gid mapping\""); uid_t uid = vid.uid; if (((vid.prot == "unix") && (vid.name == "root")) || ((vid.prot == "sss") && (vid.name == "daemon"))) { Mapping::getPhysicalIdShards(myrole.c_str(), vid); } else { if (client->name != nullptr) { Mapping::getPhysicalIdShards(client->name, vid); } } vid.uid = uid; vid.allowed_uids.clear(); vid.allowed_uids.insert(uid); vid.allowed_uids.insert(99); vid.gateway = true; } } else { eos_static_debug("%s", "msg=\"tident gid forced mapping\""); // map to the requested id vid.allowed_gids.clear(); vid.gid = gVirtualGidMap[tgid.c_str()]; vid.allowed_gids.insert(vid.gid); } } eos_static_debug("suidtident:%s sgidtident:%s", suidtident.c_str(), sgidtident.c_str()); // Configuration door for localhost clients adds always the adm/adm vid's if ((suidtident == "tident:\"root@localhost.localdomain\":uid") || (suidtident == "tident:\"root@localhost\":uid")) { vid.sudoer = true; vid.uid = 3; vid.gid = 4; vid.allowed_uids.insert(vid.uid); vid.allowed_gids.insert(vid.gid); } // GRPC key mapping if ((vid.prot == "grpc") && vid.key.length()) { std::string keyname = vid.key.c_str(); if (keyname.substr(0, 8) == "zteos64:") { // this is an eos token authz = vid.key; vid = VirtualIdentity::Nobody(); } else { std::string maptident = "tident:\"grpc@"; std::string wildcardmaptident = "tident:\"grpc@*\":uid"; std::vector vtident; eos::common::StringConversion::Tokenize(client->tident, vtident, "@"); if (vtident.size() == 2) { maptident += vtident[1]; } maptident += "\":uid"; eos_static_info("%d %s %s %s", vtident.size(), client->tident, maptident.c_str(), wildcardmaptident.c_str()); if (gVirtualUidMap.count(maptident.c_str()) || gVirtualUidMap.count(wildcardmaptident.c_str())) { // if this is an allowed gateway, map according to client name or authkey std::string uidkey = "grpc:\""; uidkey += "key:"; uidkey += keyname; uidkey += "\":uid"; vid.uid = 99; vid.allowed_uids.clear(); vid.allowed_uids.insert(99); vid.gateway = true; if (gVirtualUidMap.count(uidkey.c_str())) { vid.uid = gVirtualUidMap[uidkey.c_str()]; vid.allowed_uids.insert(vid.uid); } std::string gidkey = "grpc:\""; gidkey += "key:"; gidkey += keyname; gidkey += "\":gid"; vid.gid = 99; vid.allowed_gids.clear(); vid.allowed_gids.insert(99); if (gVirtualGidMap.count(gidkey.c_str())) { vid.gid = gVirtualGidMap[gidkey.c_str()]; vid.allowed_gids.insert(vid.gid); } } else { // we are nobody if we are not an authorized host vid = VirtualIdentity::Nobody(); } } } // Environment selected roles XrdOucString ruid = Env.Get("eos.ruid"); XrdOucString rgid = Env.Get("eos.rgid"); XrdOucString rapp = Env.Get("eos.app"); // SSS key mapping if ((vid.prot == "sss") && vid.key.length()) { std::string keyname = vid.key; std::string maptident = "tident:\"sss@"; std::string wildcardmaptident = "tident:\"sss@*\":uid"; std::vector vtident; eos::common::StringConversion::Tokenize(client->tident, vtident, "@"); // token provided as key if (keyname.substr(0, 8) == "zteos64:") { // this is an eos token authz = vid.key; } else { // try oauth2 std::string oauthname; bool oauth2_enabled = (gVirtualUidMap.find(g_oauth2_uid_key) != gVirtualUidMap.end()); if (oauth2_enabled) { // Release the map mutex to avoid any inteference with a queued up // write lock and an oauth callout being slow lock.Release(); oauthname = gOAuth.Handle(keyname, vid); lock.Grab(gMapMutex); } // Check for OAuth contents if (oauthname.empty() || !oauth2_enabled) { // Treat as mapping key if (vtident.size() == 2) { maptident += vtident[1]; } maptident += "\":uid"; eos_static_info("%d %s %s %s", vtident.size(), client->tident, maptident.c_str(), wildcardmaptident.c_str()); if (gVirtualUidMap.count(maptident) || gVirtualUidMap.count(wildcardmaptident)) { vid.gateway = true; // if this is an allowed gateway, map according to client name or authkey std::string uidkey = "sss:\""; uidkey += "key:"; uidkey += keyname; uidkey += "\":uid"; vid.uid = 99; vid.allowed_uids.clear(); vid.allowed_uids.insert(99); if (auto kv = gVirtualUidMap.find(uidkey); kv != gVirtualUidMap.end()) { vid.uid = kv->second; vid.allowed_uids.insert(vid.uid); } std::string gidkey = "sss:\""; gidkey += "key:"; gidkey += keyname; gidkey += "\":gid"; vid.gid = 99; vid.allowed_gids.clear(); vid.allowed_gids.insert(99); if (auto kv = gVirtualGidMap.find(gidkey); kv != gVirtualGidMap.end()) { vid.gid = kv->second; vid.allowed_gids.insert(vid.gid); } } else { // we are nobody if we are not an authorized host vid = VirtualIdentity::Nobody(); vid.prot = "sss"; } } else { int errc = 0; std::string uidkey = "oauth2:\""; uidkey += "sub:"; uidkey += oauthname; uidkey += "\":uid"; if (auto kv = gVirtualUidMap.find(uidkey); kv != gVirtualUidMap.end()) { // map oauthname from static sub mapping oauthname = UidToUserName(kv->second, errc); } if (errc) { // we have no mapping for this uid Mapping::getPhysicalIdShards("nobody", vid); } else { // map oauthname Mapping::getPhysicalIdShards(oauthname.c_str(), vid); } vid.prot = "oauth2"; } } } // Explicit virtual mapping overrules physical mappings - the second one // comes from the physical mapping before { auto userkey = gVirtualUidMap.find(useralias.c_str()); vid.uid = userkey != gVirtualUidMap.end() ? userkey->second : vid.uid; vid.allowed_uids.insert(vid.uid); } { auto groupkey = gVirtualGidMap.find(groupalias.c_str()); vid.gid = groupkey != gVirtualGidMap.end() ? groupkey->second : vid.gid; vid.allowed_gids.insert(vid.gid); } // Add virtual user and group roles - if any if (gUserRoleVector.count(vid.uid)) { for (auto it = gUserRoleVector[vid.uid].cbegin(); it != gUserRoleVector[vid.uid].cend(); ++it) { vid.allowed_uids.insert(*it); } } if (gGroupRoleVector.count(vid.uid)) { for (auto it = gGroupRoleVector[vid.uid].cbegin(); it != gGroupRoleVector[vid.uid].cend(); ++it) { vid.allowed_gids.insert(*it); } } bool token_sudo = false; // Handle token based mapping if (!authz.empty()) { static const std::string http_enc_tag = "Bearer%20"; static const std::string http_tag = "Bearer "; // Remove extra characters and decode when passed as a bearer // authorization HTTPS header if (authz.find(http_enc_tag) == 0) { authz.erase(0, http_enc_tag.size()); authz = StringConversion::curl_default_unescaped(authz); } else { if (authz.find(http_tag) == 0) { authz.erase(0, http_tag.size()); } } if (authz.substr(0, 8) == "zteos64:") { // This is an eos token eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey(); std::string key = symkey ? symkey->GetKey64() : "0123457890defaultkey"; bool skip_key = false; if (getenv("EOS_MGM_TOKEN_KEYFILE")) { struct stat buf; if (::stat(getenv("EOS_MGM_TOKEN_KEYFILE"), &buf)) { eos_static_err("token keyfile is not existing '%s'", getenv("EOS_MGM_TOKEN_KEYHFILE")); skip_key = true; } else { if ((buf.st_uid != DAEMONUID) || (buf.st_mode != 0100400)) { skip_key = true; eos_static_err("token keyfile mode bit is %o", buf.st_mode); } } if (!skip_key) { key = eos::common::StringConversion::LoadFileIntoString( getenv("EOS_MGM_TOKEN_KEYFILE"), key); } } int rc = 0; vid.token = std::make_shared(); if ((rc = vid.token->Read(authz, key, eos::common::EosTok::sTokenGeneration, false))) { vid.token->Reset(); eos_static_err("failed to decode token tident='%s' token='%s' errno=%d", tident, authz.c_str(), -rc); } else { // if owner or group is specified, adjust this if (!vid.token->Owner().empty()) { token_sudo = true; ruid = vid.token->Owner().c_str(); } if (!vid.token->Group().empty()) { token_sudo = true; rgid = vid.token->Group().c_str(); } if (EOS_LOGS_INFO) { std::string dump; vid.token->Dump(dump, true, true); eos_static_info("%s", dump.c_str()); } } } } // apply policy if a token can change the identity (authenticate) if (gTokenSudo != Mapping::kAlways) { if (gTokenSudo == kNever) { token_sudo = false; } else { if (gTokenSudo == Mapping::kEncrypted) { if ((vid.prot != "sss") && (vid.prot != "https") && (vid.prot != "ztn") && (vid.prot != "grpc")) { token_sudo = false; } } else { if (gTokenSudo == Mapping::kStrong) { if (vid.prot == "unix") { token_sudo = false; } } } } } uid_t sel_uid = vid.uid; uid_t sel_gid = vid.gid; if (ruid.length()) { if (!IsUid(ruid, sel_uid)) { int errc = 0; // try alias conversion std::string luid = ruid.c_str(); sel_uid = (gVirtualUidMap.count(ruid.c_str())) ? gVirtualUidMap[ruid.c_str() ] : 99; if (sel_uid == 99) { sel_uid = UserNameToUid(luid, errc); } if (errc) { sel_uid = 99; } } } if (rgid.length()) { if (!IsGid(rgid, sel_gid)) { int errc = 0; // try alias conversion std::string lgid = rgid.c_str(); sel_gid = (gVirtualGidMap.count(rgid.c_str())) ? gVirtualGidMap[rgid.c_str()] : 99; if (sel_gid == 99) { sel_gid = GroupNameToGid(lgid, errc); } if (errc) { sel_gid = 99; } } } // Sudoer flag setting if (gSudoerMap.count(vid.uid)) { vid.sudoer = true; } // Check if we are allowed to take sel_uid & sel_gid if (!vid.sudoer && !token_sudo) { // if we are not a sudore, scan the allowed ids if (vid.hasUid(sel_uid)) { vid.uid = sel_uid; } else { vid.uid = 99; } if (vid.hasGid(sel_gid)) { vid.gid = sel_gid; } else { vid.gid = 99; } } else { vid.uid = sel_uid; vid.gid = sel_gid; if (ruid.length() || rgid.length()) { vid.allowed_gids.insert(sel_gid); vid.allowed_uids.insert(sel_uid); } } if (client->host) { vid.host = client->host; } else { vid.host = host.c_str(); } size_t dotpos = vid.host.find("."); // remove hostname if (dotpos != std::string::npos) { vid.domain = vid.host.substr(dotpos + 1); } else { vid.domain = "localdomain"; } { int errc = 0; // add the uid/gid as strings if (vid.uid_string.empty()) { vid.uid_string = UidToUserName(vid.uid, errc); } if (vid.gid_string.empty()) { vid.gid_string = GidToGroupName(vid.gid, errc); } } // verify origin if (vid.token) { if (vid.token->Valid()) { if (vid.token->VerifyOrigin(vid.host, vid.uid_string, std::string(vid.prot.c_str()))) { // invalidate this token eos_static_debug("invalidating token - origin mismatch %s:%s:%s", vid.host.c_str(), vid.uid_string.c_str(), vid.prot.c_str()); vid.token->Reset(); // reset the vid to nobody if the origin does not match vid.toNobody(); } } } if (rapp.length()) { vid.app = rapp.c_str(); } // Check the Geo Location if ((!vid.geolocation.length()) && (gGeoMap.size())) { // if the geo location was not set externally and we have some recipe we try // to translate the host name and match a rule // if we have a default geo location we assume that a client in that one if (auto kv = gGeoMap.find("default"); kv != gGeoMap.end()) { vid.geolocation = kv->second; } std::string ipstring = gIpCache.GetIp(host.c_str()); if (ipstring.length()) { std::string sipstring = ipstring; GeoLocationMap_t::const_iterator it; GeoLocationMap_t::const_iterator longuestmatch = gGeoMap.end(); // we use the geo location with the longest name match for (it = gGeoMap.begin(); it != gGeoMap.end(); ++it) { // If we have a previously matched geoloc and if it's longer that the // current one, try the next one if (longuestmatch != gGeoMap.end() && it->first.length() <= longuestmatch->first.length()) { continue; } if (sipstring.compare(0, it->first.length(), it->first) == 0) { vid.geolocation = it->second; longuestmatch = it; } } } } char actident[1024]; snprintf(actident, sizeof(actident) - 1, "%d^%s^%s^%s^%s", vid.uid, mytident.c_str(), vid.prot.c_str(), vid.host.c_str(), vid.app.c_str()); std::string intident = actident; if (!ActiveTidentsSharded.contains(intident)) { ActiveUidsSharded.fetch_add(vid.uid, 1); } ActiveTidentsSharded.store(intident, std::make_unique(time(NULL))); eos_static_debug("selected %d %d [%s %s]", vid.uid, vid.gid, ruid.c_str(), rgid.c_str()); if (log) { eos_static_info("%s sec.tident=\"%s\" vid.uid=%d vid.gid=%d sudo=%d gateway=%d", eos::common::SecEntity::ToString(client, Env.Get("eos.app")).c_str(), tident, vid.uid, vid.gid, vid.sudoer, vid.gateway); } } //------------------------------------------------------------------------------ // Handle VOMS mapping //------------------------------------------------------------------------------ void Mapping::HandleVOMS(const XrdSecEntity* client, VirtualIdentity& vid) { // No VOMS info available if ((client->grps == nullptr) || (strlen(client->grps) == 0)) { return; } std::string group = client->grps; size_t g_pos = group.find(" "); if (g_pos != std::string::npos) { group.erase(g_pos); } // VOMS mapping std::string vomsstring = "voms:\""; vomsstring += group; vomsstring += ":"; vid.grps = group; if (client->role && strlen(client->role) && (strncmp(client->role, "NULL", 4) != 0)) { // the role might be NULL std::string role = client->role; size_t r_pos = role.find(" "); if (r_pos != std::string::npos) { role.erase(r_pos); } vomsstring += role; vid.role = role; } vomsstring += "\""; std::string vomsuidstring = vomsstring; std::string vomsgidstring = vomsstring; vomsuidstring += ":uid"; vomsgidstring += ":gid"; // Mapping to user if (gVirtualUidMap.count(vomsuidstring)) { vid.allowed_uids.clear(); vid.allowed_gids.clear(); // Use physical mapping for VOMS roles, convert mapped uid to user name int errc = 0; std::string cname = Mapping::UidToUserName(gVirtualUidMap[vomsuidstring], errc); if (!errc) { Mapping::getPhysicalIdShards(cname.c_str(), vid); } else { vid = VirtualIdentity::Nobody(); eos_static_err("voms-mapping: cannot translate uid=%d to user name with " "the password db", (int) gVirtualUidMap[vomsuidstring]); } } // Mapping to group if (gVirtualGidMap.count(vomsgidstring)) { // se group mapping for VOMS roles vid.allowed_gids.clear(); vid.gid = gVirtualGidMap[vomsgidstring]; vid.allowed_gids.insert(vid.gid); } } //------------------------------------------------------------------------------ // Handle HTTPS authz keys mapping //------------------------------------------------------------------------------ void Mapping::HandleKEYS(const XrdSecEntity* client, VirtualIdentity& vid) { // No VOMS info available if (vid.key.empty()) { return; } std::string uidkey = "https:\""; uidkey += "key:"; uidkey += vid.key; uidkey += "\":uid"; if (gVirtualUidMap.count(uidkey.c_str())) { vid.uid = 99; vid.allowed_uids.clear(); vid.allowed_uids.insert(99); vid.uid = gVirtualUidMap[uidkey.c_str()]; vid.allowed_uids.insert(vid.uid); vid.gateway = true; } std::string gidkey = "https:\""; gidkey += "key:"; gidkey += vid.key; gidkey += "\":gid"; if (gVirtualGidMap.count(gidkey.c_str())) { vid.gid = 99; vid.allowed_gids.clear(); vid.allowed_gids.insert(99); vid.gid = gVirtualGidMap[gidkey.c_str()]; vid.allowed_gids.insert(vid.gid); vid.gateway = true; } } //------------------------------------------------------------------------------ // Print the current mappings //------------------------------------------------------------------------------ void Mapping::Print(XrdOucString& stdOut, XrdOucString option) { bool translateids = true; if (option.find("n") != STR_NPOS) { translateids = false; option.replace("n", ""); } if ((!option.length()) || ((option.find("u")) != STR_NPOS)) { UserRoleMap_t::const_iterator it; for (it = gUserRoleVector.begin(); it != gUserRoleVector.end(); ++it) { char iuid[4096]; sprintf(iuid, "%d", it->first); char suid[4096]; sprintf(suid, "%-6s", iuid); if (translateids) { int errc = 0; std::string username = UidToUserName(it->first, errc); if (!errc) { sprintf(suid, "%-12s", username.c_str()); } } stdOut += "membership uid: "; stdOut += suid; stdOut += " => uids("; for (const auto& uid : it->second) { if (translateids) { int errc = 0; std::string username = UidToUserName(uid, errc); if (!errc) { stdOut += username.c_str(); } else { stdOut += (int)uid; } } else { stdOut += (int)uid; } stdOut += ","; } if (!it->second.empty()) { stdOut.erasefromend(1); } stdOut += ")\n"; } } if ((!option.length()) || ((option.find("g")) != STR_NPOS)) { UserRoleMap_t::const_iterator it; for (it = gGroupRoleVector.begin(); it != gGroupRoleVector.end(); ++it) { char iuid[4096]; sprintf(iuid, "%d", it->first); char suid[4096]; sprintf(suid, "%-6s", iuid); if (translateids) { int errc = 0; std::string username = UidToUserName(it->first, errc); if (!errc) { sprintf(suid, "%-12s", username.c_str()); } } stdOut += "membership uid: "; stdOut += suid; stdOut += " => gids("; for (const auto& gid : it->second) { if (translateids) { int errc = 0; std::string username = GidToGroupName(gid, errc); if (!errc) { stdOut += username.c_str(); } else { stdOut += (int)gid; } } else { stdOut += (int)gid; } stdOut += ","; } if (!it->second.empty()) { stdOut.erasefromend(1); } stdOut += ")\n"; } } if ((!option.length()) || ((option.find("s")) != STR_NPOS)) { SudoerMap_t::const_iterator it; // print sudoer line stdOut += "sudoer => uids("; for (it = gSudoerMap.begin(); it != gSudoerMap.end(); ++it) { if (it->second) { int errc = 0; std::string username = UidToUserName(it->first, errc); if (!errc && translateids) { stdOut += username.c_str(); } else { stdOut += (int)(it->first); } stdOut += ","; } } if (stdOut.endswith(",")) { stdOut.erase(stdOut.length() - 1); } stdOut += ")\n"; stdOut += "tokensudo => "; if (gTokenSudo == Mapping::kAlways) { stdOut += "always"; } else if (gTokenSudo == Mapping::kEncrypted) { stdOut += "encrypted"; } else if (gTokenSudo == Mapping::kStrong) { stdOut += "strong"; } else if (gTokenSudo == Mapping::kNever) { stdOut += "never"; } else { stdOut += "inval"; } stdOut += "\n"; } if ((!option.length()) || ((option.find("U")) != STR_NPOS)) { VirtualUserMap_t::const_iterator it; for (it = gVirtualUidMap.begin(); it != gVirtualUidMap.end(); ++it) { stdOut += it->first.c_str(); stdOut += " => "; int errc = 0; std::string username = UidToUserName(it->second, errc); if (!errc && translateids) { stdOut += username.c_str(); } else { stdOut += (int) it->second; } stdOut += "\n"; } } if ((!option.length()) || ((option.find("G")) != STR_NPOS)) { VirtualGroupMap_t::const_iterator it; for (it = gVirtualGidMap.begin(); it != gVirtualGidMap.end(); ++it) { stdOut += it->first.c_str(); stdOut += " => "; int errc = 0; std::string groupname = GidToGroupName(it->second, errc); if (!errc && translateids) { stdOut += groupname.c_str(); } else { stdOut += (int) it->second; } stdOut += "\n"; } } if (((option.find("y")) != STR_NPOS)) { VirtualUserMap_t::const_iterator it; for (it = gVirtualUidMap.begin(); it != gVirtualUidMap.end(); ++it) { if (!it->second) { XrdOucString authmethod = it->first.c_str(); if (!authmethod.beginswith("tident:")) { continue; } int dpos = authmethod.find("@"); authmethod.erase(0, dpos + 1); dpos = authmethod.find("\""); authmethod.erase(dpos); stdOut += "gateway="; stdOut += authmethod; stdOut += "\n"; } } } if (((option.find("a")) != STR_NPOS)) { VirtualUserMap_t::const_iterator it; for (it = gVirtualUidMap.begin(); it != gVirtualUidMap.end(); ++it) { if (!it->second) { XrdOucString authmethod = it->first.c_str(); if (authmethod.beginswith("tident:")) { continue; } int dpos = authmethod.find(":"); authmethod.erase(dpos); stdOut += "auth="; stdOut += authmethod; stdOut += "\n"; } } } if ((!option.length()) || ((option.find("N")) != STR_NPOS)) { char sline[1024]; snprintf(sline, sizeof(sline), "publicaccesslevel: => %d\n", gNobodyAccessTreeDeepness); stdOut += sline; } if ((!option.length()) || ((option.find("l")) != STR_NPOS)) { for (auto it = gGeoMap.begin(); it != gGeoMap.end(); ++it) { char sline[1024]; snprintf(sline, sizeof(sline) - 1, "geotag:\"%s\" => \"%s\"\n", it->first.c_str(), it->second.c_str()); stdOut += sline; } } if ((!option.length())) { for (auto it = gAllowedTidentMatches.begin(); it != gAllowedTidentMatches.end(); ++it) { char sline[1024]; snprintf(sline, sizeof(sline) - 1, "hostmatch:\"protocol=%s pattern=%s\n", it->first.c_str(), it->second.c_str()); stdOut += sline; } } } /*----------------------------------------------------------------------------*/ /** * Convert uid to user name * * @param uid unix user id * @param errc 0 if success, EINVAL if does not exist * * @return user name as string */ /*----------------------------------------------------------------------------*/ std::string Mapping::UidToUserName(uid_t uid, int& errc) { errc = 0; { std::scoped_lock lock(gPhysicalUserNameCacheMutex); auto kv = gPhysicalUserNameCache.find(uid); if (kv != gPhysicalUserNameCache.end()) { return kv->second; } } if (auto user_ptr = gShardedNegativeUserNameCache.retrieve(uid)) { return *user_ptr; } char buffer[131072]; int buflen = sizeof(buffer); std::string uid_string = ""; struct passwd pwbuf; struct passwd* pwbufp = 0; (void) getpwuid_r(uid, &pwbuf, buffer, buflen, &pwbufp); if (pwbufp == NULL) { char buffer[131072]; int buflen = sizeof(buffer); std::string uid_string = ""; struct passwd pwbuf; struct passwd* pwbufp = 0; { if (getpwuid_r(uid, &pwbuf, buffer, buflen, &pwbufp) || (!pwbufp)) { char suid[1024]; snprintf(suid, sizeof(suid) - 1, "%u", uid); uid_string = suid; errc = EINVAL; gShardedNegativeUserNameCache.store(uid, std::make_unique(uid_string)); return uid_string; } else { uid_string = pwbuf.pw_name; errc = 0; } } cacheUserIds(uid, uid_string); return uid_string; } else { uid_string = pwbuf.pw_name; errc = 0; } cacheUserIds(uid, uid_string); return uid_string; } /*----------------------------------------------------------------------------*/ /** * Convert gid to group name * * @param gid unix group id * @param errc 0 if success, EINVAL if does not exist * * @return user name as string */ /*----------------------------------------------------------------------------*/ std::string Mapping::GidToGroupName(gid_t gid, int& errc, size_t buffersize) { errc = 0; { std::scoped_lock lock(gPhysicalGroupNameCacheMutex); auto kv = gPhysicalGroupNameCache.find(gid); if (kv != gPhysicalGroupNameCache.end()) { return kv->second; } } if (auto group_ptr = gShardedNegativeGroupNameCache.retrieve(gid)) { return *group_ptr; } { char buffer[buffersize]; int buflen = sizeof(buffer); struct group grbuf; struct group* grbufp = 0; std::string gid_string = ""; if (getgrgid_r(gid, &grbuf, buffer, buflen, &grbufp) || (!grbufp)) { if (errno == ERANGE) { if (buffersize < (16 * 1024 * 1024)) { // try doubling the buffer return GidToGroupName(gid, errc, 2 * buffersize); } // just give up here } // cannot translate this name char sgid[1024]; snprintf(sgid, sizeof(sgid) - 1, "%u", gid); gid_string = sgid; errc = EINVAL; gShardedNegativeGroupNameCache.store(gid, std::make_unique(gid_string)); return gid_string; } else { gid_string = grbuf.gr_name; errc = 0; } cacheGroupIds(gid, gid_string); return gid_string; } } /*----------------------------------------------------------------------------*/ /** * Convert string name to uid * * @param username name as string * @param errc 0 if success, EINVAL if does not exist * * @return user id */ /*----------------------------------------------------------------------------*/ uid_t Mapping::UserNameToUid(const std::string& username, int& errc) { { std::scoped_lock lock(gPhysicalUserNameCacheMutex); if (auto kv = gPhysicalUserIdCache.find(username); kv != gPhysicalUserIdCache.end()) { return kv->second; } } char buffer[131072]; int buflen = sizeof(buffer); uid_t uid = 99; struct passwd pwbuf; struct passwd* pwbufp = 0; errc = 0; (void) getpwnam_r(username.c_str(), &pwbuf, buffer, buflen, &pwbufp); if (pwbufp == NULL) { bool is_number = true; for (size_t i = 0; i < username.length(); i++) { if (!isdigit(username[i])) { is_number = false; break; } } uid = atoi(username.c_str()); if ((uid != 0) && (is_number)) { errc = 0; return uid; } else { errc = EINVAL; uid = 99; return uid; } } else { uid = pwbuf.pw_uid; errc = 0; } if (!errc) { cacheUserIds(uid, username); } return uid; } /*----------------------------------------------------------------------------*/ /** * Convert string name to gid * * @param groupname name as string * @param errc 0 if success, EINVAL if does not exist * * @return group id */ /*----------------------------------------------------------------------------*/ gid_t Mapping::GroupNameToGid(const std::string& groupname, int& errc) { { std::scoped_lock lock(gPhysicalGroupNameCacheMutex); if (auto kv = gPhysicalGroupIdCache.find(groupname); kv != gPhysicalGroupIdCache.end()) { return kv->second; } } char buffer[131072]; int buflen = sizeof(buffer); struct group grbuf; struct group* grbufp = 0; gid_t gid = 99; errc = 0; (void) getgrnam_r(groupname.c_str(), &grbuf, buffer, buflen, &grbufp); if (!grbufp) { bool is_number = true; for (size_t i = 0; i < groupname.length(); i++) { if (!isdigit(groupname[i])) { is_number = false; break; } } gid = atoi(groupname.c_str()); if ((gid != 0) && (is_number)) { errc = 0; return gid; } else { errc = EINVAL; gid = 99; } } else { gid = grbuf.gr_gid; errc = 0; } if (!errc) { cacheGroupIds(gid, groupname); } return gid; } /*----------------------------------------------------------------------------*/ /** * Convert string name to gid * * @param groupname name as string * @param errc 0 if success, EINVAL if does not exist * * @return group id */ /*----------------------------------------------------------------------------*/ std::string Mapping::ip_cache::GetIp(const char* hostname) { time_t now = time(NULL); { // check for an existing translation RWMutexReadLock guard(mLocker); if (mIp2HostMap.count(hostname) && mIp2HostMap[hostname].first > now) { eos_static_debug("status=cached host=%s ip=%s", hostname, mIp2HostMap[hostname].second.c_str()); // give cached entry return mIp2HostMap[hostname].second; } } { // refresh an entry XrdNetAddr* addrs = 0; int nAddrs = 0; const char* err = XrdNetUtils::GetAddrs(hostname, &addrs, nAddrs, XrdNetUtils::allIPv64, XrdNetUtils::NoPortRaw); if (err || nAddrs == 0) { return ""; } char buffer[64]; int hostlen = addrs[0].Format(buffer, sizeof(buffer), XrdNetAddrInfo::fmtAddr, XrdNetAddrInfo::noPortRaw); delete [] addrs; if (hostlen > 0) { RWMutexWriteLock guard(mLocker); std::string sip(buffer, hostlen); mIp2HostMap[hostname] = std::make_pair(now + mLifeTime, sip); eos_static_debug("status=refresh host=%s ip=%s", hostname, mIp2HostMap[hostname].second.c_str()); return sip; } return ""; } } //------------------------------------------------------------------------------ // Convert a comma separated uid string to a vector uid list //------------------------------------------------------------------------------ void Mapping::CommaListToUidSet(const char* list, std::set& uids_set) { XrdOucString slist = list; XrdOucString number = ""; int kommapos; if (!slist.endswith(",")) { slist += ","; } do { kommapos = slist.find(","); if (kommapos != STR_NPOS) { number.assign(slist, 0, kommapos - 1); std::string username = number.c_str(); int errc = 0; uid_t uid ; if (std::find_if(username.begin(), username.end(), [](unsigned char c) { return std::isalpha(c); }) != username.end()) { uid = eos::common::Mapping::UserNameToUid(username, errc); } else { try { uid = std::stoul(username); } catch (const std::exception& e) { uid = 99; } } if (!errc) { uids_set.insert(uid); } slist.erase(0, kommapos + 1); } } while (kommapos != STR_NPOS); } //------------------------------------------------------------------------------ // Convert a komma separated gid string to a vector gid list //------------------------------------------------------------------------------ void Mapping::CommaListToGidSet(const char* list, std::set& gids_set) { XrdOucString slist = list; XrdOucString number = ""; int kommapos; if (!slist.endswith(",")) { slist += ","; } do { kommapos = slist.find(","); if (kommapos != STR_NPOS) { number.assign(slist, 0, kommapos - 1); int errc; std::string groupname = number.c_str(); gid_t gid = GroupNameToGid(groupname, errc); if (!errc) { gids_set.insert(gid); } slist.erase(0, kommapos + 1); } } while (kommapos != STR_NPOS); } // ----------------------------------------------------------------------------- //! Compare a uid with the string representation // ----------------------------------------------------------------------------- bool Mapping::IsUid(XrdOucString idstring, uid_t& id) { id = strtoul(idstring.c_str(), 0, 10); char revid[1024]; sprintf(revid, "%lu", (unsigned long) id); XrdOucString srevid = revid; if (idstring == srevid) { return true; } return false; } // ----------------------------------------------------------------------------- //! Compare a gid with the string representation // ----------------------------------------------------------------------------- bool Mapping::IsGid(XrdOucString idstring, gid_t& id) { id = strtoul(idstring.c_str(), 0, 10); char revid[1024]; sprintf(revid, "%lu", (unsigned long) id); XrdOucString srevid = revid; if (idstring == srevid) { return true; } return false; } // ----------------------------------------------------------------------------- //! Reduce the trace identifier information to user@host // ----------------------------------------------------------------------------- const char* Mapping::ReduceTident(XrdOucString& tident, XrdOucString& wildcardtident, XrdOucString& mytident, XrdOucString& myhost) { int dotpos = tident.find("."); int addpos = tident.find("@"); wildcardtident = tident; mytident = tident; mytident.erase(dotpos, addpos - dotpos); myhost = mytident; dotpos = mytident.find("@"); myhost.erase(0, dotpos + 1); wildcardtident = mytident; addpos = wildcardtident.find("@"); wildcardtident.erase(0, addpos); wildcardtident = "*" + wildcardtident; return mytident.c_str(); } std::string Mapping::ReduceTident(std::string_view tident, std::string& wildcardtident, std::string& myhost) { auto dotpos = tident.find("."); auto addpos = tident.find("@"); std::string mytident{tident}; mytident.erase(dotpos, addpos - dotpos); myhost = tident.substr(addpos + 1); wildcardtident = "*@" + myhost; return mytident; } // ----------------------------------------------------------------------------- //! Convert a uid into a string // ----------------------------------------------------------------------------- std::string Mapping::UidAsString(uid_t uid) { std::string uidstring = ""; char suid[1024]; snprintf(suid, sizeof(suid) - 1, "%u", uid); uidstring = suid; return uidstring; } // ----------------------------------------------------------------------------- //! Convert a gid into a string // ----------------------------------------------------------------------------- std::string Mapping::GidAsString(gid_t gid) { std::string gidstring = ""; char sgid[1024]; snprintf(sgid, sizeof(sgid) - 1, "%u", gid); gidstring = sgid; return gidstring; } //------------------------------------------------------------------------------ //! Function converting vid frin a string representation //------------------------------------------------------------------------------ bool Mapping::VidFromString(VirtualIdentity& vid, const char* vidstring) { std::string svid = vidstring; std::vector tokens; eos::common::StringConversion::EmptyTokenize( vidstring, tokens, ":"); if (tokens.size() != 7) { return false; } vid.uid = strtoul(tokens[0].c_str(), 0, 10); vid.gid = strtoul(tokens[1].c_str(), 0, 10); vid.uid_string = tokens[2].c_str(); vid.gid_string = tokens[3].c_str(); vid.name = tokens[4].c_str(); vid.prot = tokens[5].c_str(); vid.tident = tokens[6].c_str(); return true; } //---------------------------------------------------------------------------- //! Function converting vid to a string representation //---------------------------------------------------------------------------- std::string Mapping::VidToString(VirtualIdentity& vid) { char vids[4096]; snprintf(vids, sizeof(vids), "%u:%u:%s:%s:%s:%s:%s", vid.uid, vid.gid, vid.uid_string.c_str(), vid.gid_string.c_str(), vid.name.c_str(), vid.prot.c_str(), vid.tident.c_str()); return std::string(vids); } //------------------------------------------------------------------------------ //! Function returning a VID from a name //------------------------------------------------------------------------------ VirtualIdentity Mapping::Someone(const std::string& name) { VirtualIdentity vid; vid = VirtualIdentity::Nobody(); int errc = 0; uid_t uid = UserNameToUid(name, errc); if (!errc) { vid.uid = uid; vid.uid_string = name; vid.name = name.c_str(); vid.tident = std::string(name + "@grpc").c_str(); } return vid; } //------------------------------------------------------------------------------ //! Function returning a VID from a uid/gid pair //------------------------------------------------------------------------------ VirtualIdentity Mapping::Someone(uid_t uid, gid_t gid) { VirtualIdentity vid; vid = VirtualIdentity::Nobody(); int errc = 0; vid.uid = uid; vid.gid = gid; vid.allowed_uids = {uid, 99}; vid.allowed_gids = {uid, 99}; vid.sudoer = false; vid.gateway = false; vid.uid_string = UidToUserName(uid, errc); if (!errc) { vid.name = vid.uid_string.c_str(); } else { vid.name = UidAsString(uid).c_str(); } vid.gid_string = GidToGroupName(gid, errc); vid.tident = std::string(vid.uid_string + "@grpc").c_str(); return vid; } //------------------------------------------------------------------------------ //! Function testing if an OAUTH2 resource is allowed by the configuration //------------------------------------------------------------------------------ bool Mapping::IsOAuth2Resource(const std::string& resource) { eos::common::RWMutexReadLock lock(eos::common::Mapping::gMapMutex); std::string uidkey = "oauth2:\""; uidkey += "key:"; uidkey += resource; uidkey += "\":uid"; return gVirtualUidMap.count(uidkey); } //------------------------------------------------------------------------------ //! Decode the uid from a trace ID string //------------------------------------------------------------------------------ uid_t Mapping::UidFromTident(const std::string& tident) { std::vector tokens; std::string delimiter = "^"; eos::common::StringConversion::Tokenize(tident, tokens, delimiter); if (tokens.size()) { return atoi(tokens[0].c_str()); } return 0; } //------------------------------------------------------------------------------ //! Return number of active sessions for a given uid //------------------------------------------------------------------------------ size_t Mapping::ActiveSessions(uid_t uid) { //XrdSysMutexHelper mLock(ActiveLock); if (auto n = ActiveUidsSharded.retrieve(uid)) { return *n; } return 0; } size_t Mapping::ActiveSessions() { return ActiveTidentsSharded.num_entries(); } void Mapping::addSecondaryGroups(VirtualIdentity& vid, const std::string& name, gid_t gid) { if (gSecondaryGroups) { eos_static_debug("msg=\"group lookup\" name=\"%s\" gid=%d", name.c_str(), gid); struct group* gr; // Calls to setgrent/getgrent/endgrent are not thread-safe static std::mutex mutex; std::unique_lock lock(mutex); setgrent(); while ((gr = getgrent())) { int cnt; cnt = 0; if (gr->gr_gid == gid) { if (!vid.allowed_gids.size()) { vid.allowed_gids.insert(gid); vid.gid = gid; eos_static_debug("adding %d\n", gid); } } while (gr->gr_mem[cnt]) { if (!strcmp(gr->gr_mem[cnt], name.c_str())) { vid.allowed_gids.insert(gr->gr_gid); eos_static_debug("adding %d\n", gr->gr_gid); } cnt++; } } endgrent(); } } void Mapping::getPhysicalIdShards(const std::string& name, VirtualIdentity& vid) { if (name.empty()) { return; } struct passwd passwdinfo; char buffer[131072]; size_t buflen = sizeof(buffer); memset(&passwdinfo, 0, sizeof(passwdinfo)); eos_static_debug("find in uid cache %s cache shard=%d", name.c_str(), gShardedPhysicalUidCache.calculateShard(name)); std::unique_ptr idp {nullptr}; bool in_uid_cache {false}; if (auto id_ptr = gShardedPhysicalUidCache.retrieve(name)) { vid.uid = id_ptr->uid; vid.gid = id_ptr->gid; // FIXME use a value type! idp.reset(new id_pair(vid.uid, vid.gid)); in_uid_cache = true; eos_static_debug("msg=\"found in uid cache\" name=%s", name.c_str()); } else { eos_static_debug("msg=\"not found in uid cache\" name=%s", name.c_str()); bool use_pw = true; if (name.length() == 8) { bool known_tident = false; if (startsWith(name, "*") || startsWith(name, "~") || startsWith(name, "_")) { known_tident = true; vid.allowed_uids.clear(); vid.allowed_gids.clear(); // that is a new base-64 encoded id following the format '*1234567' // where 1234567 is the base64 encoded 42-bit value of 20-bit uid | // 16-bit gid | 6-bit session id. std::string b64name = name; b64name.erase(0, 1); // Decoden '_' -> '/', '-' -> '+' that was done to ensure the validity // of the XRootD URL. std::replace(b64name.begin(), b64name.end(), '_', '/'); std::replace(b64name.begin(), b64name.end(), '-', '+'); b64name += "="; unsigned long long bituser = 0; char* out = 0; ssize_t outlen; if (eos::common::SymKey::Base64Decode(b64name.c_str(), out, outlen)) { if (outlen <= 8) { memcpy((((char*) &bituser)) + 8 - outlen, out, outlen); eos_static_debug("msg=\"decoded base-64 uid/gid/sid\" val=%llx val=%llx", bituser, n_tohll(bituser)); } else { eos_static_err("msg=\"decoded base-64 uid/gid/sid too long\" len=%d", outlen); return; } bituser = n_tohll(bituser); if (out) { free(out); } if (startsWith(name, "*") || startsWith(name, "_")) { idp.reset(new id_pair((bituser >> 22) & 0xfffff, (bituser >> 6) & 0xffff)); struct passwd* pwbufp = 0; if (getpwuid_r(idp->uid, &passwdinfo, buffer, buflen, &pwbufp) || (!pwbufp)) { return; } cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name); vid.uid_string = passwdinfo.pw_name; if (idp->gid != passwdinfo.pw_gid) { // add the primary group if it is not the desired one vid.allowed_gids.insert(passwdinfo.pw_gid); } } else { // only user id got forwarded, we retrieve the corresponding group uid_t ruid = (bituser >> 6) & 0xfffffffff; struct passwd* pwbufp = 0; if (getpwuid_r(ruid, &passwdinfo, buffer, buflen, &pwbufp) || (!pwbufp)) { return; } idp.reset(new id_pair(passwdinfo.pw_uid, passwdinfo.pw_gid)); vid.uid_string = passwdinfo.pw_name; cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name); } eos_static_debug("using base64 mapping %s %d %d", name.c_str(), idp->uid, idp->gid); } else { eos_static_err("msg=\"failed to decoded base-64 uid/gid/sid\" id=%s", name.c_str()); return; } } if (known_tident) { // unlikely as all the code paths here should have populated idp, but // just defensive programming if (!idp) { eos_static_err("msg=\"failed to retrieve id for\" name=%s", name.c_str()); return; } if (gRootSquash && idp && (!idp->uid || !idp->gid)) { return; } vid.uid = idp->uid; vid.gid = idp->gid; vid.allowed_uids.insert(vid.uid); vid.allowed_gids.insert(vid.gid); vid.allowed_uids.insert(99); vid.allowed_gids.insert(99); addSecondaryGroups(vid, vid.uid_string, idp->gid); auto gs = std::make_unique(vid.allowed_gids); eos_static_debug("adding to cache uid=%u gid=%u", idp->uid, idp->gid); gShardedPhysicalUidCache.store(name, std::move(idp)); gShardedPhysicalGidCache.store(name, std::move(gs)); return; } } if (use_pw) { if (auto ptr = gShardedNegativePhysicalUidCache.retrieve(name)) { eos_static_debug("msg=\"found in negative user name cache\" name=%s", name.c_str()); return; } struct passwd* pwbufp = 0; { if (getpwnam_r(name.c_str(), &passwdinfo, buffer, buflen, &pwbufp) || (!pwbufp)) { gShardedNegativePhysicalUidCache.store(name, std::make_unique(true)); return; } } idp.reset(new id_pair(passwdinfo.pw_uid, passwdinfo.pw_gid)); vid.uid = idp->uid; vid.gid = idp->gid; vid.uid_string = passwdinfo.pw_name; cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name); } } if (auto gv = gShardedPhysicalGidCache.retrieve(name)) { vid.allowed_uids.insert(idp->uid); vid.allowed_gids = *gv; vid.uid = idp->uid; vid.gid = idp->gid; eos_static_debug("msg=\"returning\" uid=%u gid=%u", idp->uid, idp->gid); if (!in_uid_cache) { eos_static_debug("msg=\"adding to cache\" uid=%u gid=%u", idp->uid, idp->gid); gShardedPhysicalUidCache.store(name, std::move(idp)); } return; } addSecondaryGroups(vid, vid.uid_string, idp->gid); // add to the cache if (!in_uid_cache) { eos_static_debug("msg=\"adding to cache\" uid=%u gid=%u", idp->uid, idp->gid); gShardedPhysicalUidCache.store(name, std::move(idp)); } gShardedPhysicalGidCache.store(name, std::make_unique(vid.allowed_gids)); return; } void Mapping::getPhysicalUids(const char* name, VirtualIdentity& vid) { Mapping::getPhysicalIdShards(name, vid); vid.gid = g_nobody_uid; vid.allowed_gids.clear(); vid.allowed_gids.insert(vid.gid); } void Mapping::getPhysicalGids(const char* name, VirtualIdentity& vid) { uid_t uid = vid.uid; Mapping::getPhysicalIdShards(name, vid); vid.uid = uid; vid.allowed_uids.clear(); vid.allowed_uids.insert(uid); vid.allowed_uids.insert(g_nobody_uid); } void Mapping::getPhysicalUidGids(const char* name, VirtualIdentity& vid) { Mapping::getPhysicalIdShards(name, vid); vid.allowed_uids.clear(); vid.allowed_gids.clear(); vid.allowed_uids.insert(vid.uid); vid.allowed_gids.insert(vid.gid); vid.allowed_uids.insert(g_nobody_uid); vid.allowed_gids.insert(g_nobody_gid); } void Mapping::cacheUserIds(uid_t uid, const std::string& username) { std::scoped_lock lock(gPhysicalUserNameCacheMutex); gPhysicalUserIdCache[username] = uid; gPhysicalUserNameCache[uid] = username; } void Mapping::cacheGroupIds(gid_t gid, const std::string& groupname) { std::scoped_lock lock(gPhysicalGroupNameCacheMutex); gPhysicalGroupIdCache[groupname] = gid; gPhysicalGroupNameCache[gid] = groupname; } void Mapping::HandleUidGidMapping(const char* name, VirtualIdentity& vid, const std::string& uid_key_name, const std::string& gid_key_name) { eos_static_debug("msg=\"handle uid gid mapping\" name=%s prot=%s", name, vid.prot.c_str()); auto kv_uid = gVirtualUidMap.find(uid_key_name); auto kv_gid = gVirtualGidMap.find(gid_key_name); bool uid_mapped = kv_uid != gVirtualUidMap.end(); bool gid_mapped = kv_gid != gVirtualGidMap.end(); if (uid_mapped && gid_mapped && (kv_uid->second == 0) && (kv_gid->second == 0)) { eos_static_debug("msg=\"%s uid/gid mapping\"", vid.prot.c_str()); Mapping::getPhysicalUidGids(name, vid); return; } if (uid_mapped) { if (kv_uid->second == 0) { eos_static_debug("msg=\"%s uid mapping\"", vid.prot.c_str()); Mapping::getPhysicalUids(name, vid); } else { eos_static_debug("msg=\"%s uid forced mapping\"", vid.prot.c_str()); vid.uid = kv_uid->second; vid.allowed_uids.clear(); vid.allowed_uids.insert(vid.uid); vid.allowed_uids.insert(99); vid.gid = 99; vid.allowed_gids.clear(); vid.allowed_gids.insert(vid.gid); } } if (gid_mapped) { if (kv_gid->second == 0) { eos_static_debug("msg=\"%s gid mapping\"", vid.prot.c_str()); Mapping::getPhysicalGids(name, vid); } else { eos_static_debug("msg=\"%s forced gid mapping\"", vid.prot.c_str()); vid.allowed_gids.clear(); vid.gid = kv_gid->second; vid.allowed_gids.insert(vid.gid); } } } EOSCOMMONNAMESPACE_END