//------------------------------------------------------------------------------ // File: TokenCmd.cc // Author: Andreas-Joachim Peters - CERN //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2019 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 "TokenCmd.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/XrdMgmOfsDirectory.hh" #include "mgm/Quota.hh" #include "mgm/Recycle.hh" #include "mgm/Macros.hh" #include "mgm/Access.hh" #include "common/Path.hh" #include "common/token/EosTok.hh" #include "namespace/interface/IView.hh" #include "namespace/interface/IFileMD.hh" EOSMGMNAMESPACE_BEGIN int eos::mgm::TokenCmd::StoreToken(const std::string& token, const std::string& voucherid, std::string& tokenpath, uid_t uid, gid_t gid) { XrdOucErrInfo info; std::shared_ptr fmd; if (!GetTokenPrefix(info, uid, gid, tokenpath)) { tokenpath += voucherid; // create file with voucherid name try { fmd.reset(); fmd = gOFS->eosView->getFile(tokenpath.c_str(),0,0); return EEXIST; } catch (eos::MDException& e) { fmd = gOFS->eosView->createFile(tokenpath, 0, 0); fmd->setSize(0); fmd->setCUid(uid); fmd->setCGid(gid); // store token as extended attribute fmd->setAttribute("sys.token",token); gOFS->eosView->updateFileStore(fmd.get()); } return 0; } return EIO; } /*----------------------------------------------------------------------------*/ int eos::mgm::TokenCmd::GetTokenPrefix(XrdOucErrInfo& error, uid_t uid, gid_t gid, std::string& tokenpath) /*----------------------------------------------------------------------------*/ { const char* epname = "GetTokenPrefix"; eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root(); char stokenuser[4096]; time_t now = time(NULL); struct tm nowtm; localtime_r(&now, &nowtm); do { snprintf(stokenuser, sizeof(stokenuser) - 1, "%s/uid:%u/%04u/%02u/%02u/", gOFS->MgmProcTokenPath.c_str(), uid, 1900 + nowtm.tm_year, nowtm.tm_mon + 1, nowtm.tm_mday); struct stat buf; if (!gOFS->_stat(stokenuser, &buf, error, rootvid, "")) { tokenpath = stokenuser; return SFS_OK; } // Verify/create group/user directory if (gOFS->_mkdir(stokenuser, S_IRUSR | S_IXUSR | SFS_O_MKPTH, error, rootvid, "")) { return gOFS->Emsg(epname, error, EIO, "remove existing file - the " "token user directory couldn't be created"); } // Check the user token directory if (gOFS->_stat(stokenuser, &buf, error, rootvid, "")) { return gOFS->Emsg(epname, error, EIO, "remove existing file - could not " "determine ownership of the token user directory", stokenuser); } // Check the ownership of the user directory if ((buf.st_uid != uid) || (buf.st_gid != gid)) { // Set the correct ownership if (gOFS->_chown(stokenuser, uid, gid , error, rootvid, "")) { return gOFS->Emsg(epname, error, EIO, "remove existing file - could not " "change ownership of the token user directory", stokenuser); } } tokenpath = stokenuser; return SFS_OK; } while (1); } eos::console::ReplyProto eos::mgm::TokenCmd::ProcessRequest() noexcept { eos::console::ReplyProto reply; std::ostringstream outStream; std::ostringstream errStream; XrdOucString m_err {""}; int ret_c = 0; eos::console::TokenProto token = mReqProto.token(); // ---------------------------------------------------------------------------------------------- // security barrier for token issuing // ---------------------------------------------------------------------------------------------- // a regular user can only issue tokens for files/paths he owns // - if a token asks for a directory path, the user has to own that directory // - if a oktne assk for a file path, the user has to own that file or if there is no file he has // to own the parent directory // a sudoer or root can ask for any token // ---------------------------------------------------------------------------------------------- if (!eos::common::EosTok::sTokenGeneration) { reply.set_retc(EPERM); reply.set_std_err("error: change the generation value != 0 e.g. using eos space config default space.token.generation=1 to enable token creation"); return reply; } eos_static_info("root=%d sudoer=%d uid=%u gid=%u", mVid.hasUid(0), mVid.sudoer, mVid.uid, mVid.gid); int mode = R_OK; if (token.permission().find("x") != std::string::npos) { mode |= X_OK; } if (token.permission().find("w") != std::string::npos) { mode |= W_OK; } if (token.vtoken().empty()) { eos_static_info("%s\n", token.vtoken().c_str()); // check who asks for a token if ((mVid.hasUid(0))) { // we issue all the token in the world for them } else { // for user token, we only allow rwxd, nothing else; for (char const& c : token.permission()) { if ((c != 'r') && (c != 'x') && (c != 'w') && (c != 'd') && (c != '!') && (c != '+')) { reply.set_retc(EINVAL); reply.set_std_err("error: you can only use rwx[+1]d in your permission set!"); return reply; } } if (token.expires() > ((uint64_t)time(NULL) + (365*86400))) { reply.set_retc(EINVAL); reply.set_std_err("error: the maximum lifetime for a user token is one year!"); return reply; } struct stat buf; XrdOucErrInfo error; // we restrict only on-behalf of the requestor tokens token.set_owner(mVid.uid_string); token.set_group(mVid.gid_string); // we verify that mVid owns the path in the token if (token.path().back() == '/') { if (token.allowtree()) { // tree token only allowed if owner or empty tree if (gOFS->_stat(token.path().c_str(), &buf, error, mVid, "", 0, false, 0) || ((buf.st_uid != mVid.uid) && (buf.st_blksize))) { if (error.getErrInfo()) { // stat error reply.set_retc(error.getErrInfo()); reply.set_std_err(error.getErrText()); return reply; } else { // owner error reply.set_retc(EACCES); reply.set_std_err("error: you are not the owner of the path given in your request and you are not a sudoer or root!"); return reply; } } } else { // directory token if (gOFS->_access(token.path().c_str(), mode, error, mVid, "")) { if (errno) { // return errno reply.set_retc(errno); if (errno == ENOENT) { reply.set_std_err("error: path does not exist!"); } else { reply.set_std_err("error: no permission!"); } } } } } else { // file path mode |= F_OK; eos::common::Path cPath(token.path().c_str()); errno = 0; if (gOFS->_access(token.path().c_str(), R_OK, error, mVid, "")) { if (errno) { // return errno reply.set_retc(errno); if (errno == ENOENT) { reply.set_std_err("error: path does not exist!"); } else { reply.set_std_err("error: no permission!"); } return reply; } } } } } eos::common::EosTok eostoken; eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey(); std::string key = symkey ? symkey->GetKey64() : "0123456789defaultkey"; if (getenv("EOS_MGM_TOKEN_KEYFILE")) { struct stat buf; if (::stat(getenv("EOS_MGM_TOKEN_KEYFILE"), &buf)) { reply.set_retc(-ENOKEY); reply.set_std_err("error: unable to load token keyfile"); return reply; } else { if ((buf.st_uid != DAEMONUID) || (buf.st_mode != 0100400)) { reply.set_retc(-ENOKEY); eos_static_err("mode bit is %o", buf.st_mode); reply.set_std_err("error: unable to load token keyfile - wrong ownership (must be daemon:400)"); return reply; } } key = eos::common::StringConversion::LoadFileIntoString( getenv("EOS_MGM_TOKEN_KEYFILE"), key); } if (token.vtoken().empty()) { if (token.permission().find(":") != std::string::npos) { // someone could try to inject more acl entries here reply.set_retc(-EPERM); reply.set_std_err("error: illegal permission requested"); return reply; } // create a token eostoken.SetPath(token.path(), token.allowtree()); eostoken.SetPermission(token.permission()); eostoken.SetExpires(token.expires()); eostoken.SetOwner(token.owner()); eostoken.SetGroup(token.group()); eostoken.SetGeneration(eos::common::EosTok::sTokenGeneration); eostoken.SetRequester(mVid.getTrace()); for (int i = 0; i < token.origins_size(); ++i) { const eos::console::TokenAuth& auth = token.origins(i); eostoken.AddOrigin(auth.host(), auth.name(), auth.prot()); } if (eostoken.VerifyOrigin(vid.host, vid.uid_string, std::string(vid.prot.c_str())) == -EBADE) { errStream << "error: one or several origin regexp's are invalid" << std::endl; ret_c = -EBADE; } else { std::string token = eostoken.Write(key) ; outStream << token; std::string dump; eostoken.Dump(dump,true, true); std::string voucherid=eostoken.Voucher(); { eos::common::RWMutexReadLock lock(Access::gAccessMutex); if (Access::gAllowedTokens.size()) { outStream << std::endl; outStream << "warning: the token will not be usuable without approval of an administrator!" << std::endl; outStream << " ask for token approval of voucher:id=" << voucherid << std::endl; } } std::string token_path; if ( (ret_c = StoreToken(dump,voucherid, token_path, vid.uid, vid.gid)) ) { errStream << "error: could not store the token: " << ret_c << std::endl; } else { eos_warning("creating voucher=%s path=%s owner=%s group=%s perm=%s expires=%lu store=%s token:'%s'\n" , eostoken.Voucher().c_str(), eostoken.Path().c_str(), eostoken.Owner().c_str(), eostoken.Group().c_str(), eostoken.Permission().c_str(), eostoken.Expires(), token_path.c_str(), dump.c_str()); } } } else { if (!(ret_c = eostoken.Read(token.vtoken(), key, eos::common::EosTok::sTokenGeneration.load(), true))) { std::string dump; eostoken.Dump(dump); outStream << dump; } else { errStream << "error: cannot read token" << std::endl; } if (eostoken.VerifyOrigin(vid.host, vid.uid_string, std::string(vid.prot.c_str())) == -EBADE) { errStream << "error: one or several origin regexp's are invalid" << std::endl; ret_c = -EBADE; } } reply.set_retc(ret_c); reply.set_std_out(outStream.str()); reply.set_std_err(errStream.str()); return reply; } EOSMGMNAMESPACE_END