//------------------------------------------------------------------------------
//! @file ProcInterface.cc
//! @author Elvin Sindrilaru - CERN
//------------------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2017 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 "ProcInterface.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include "mgm/proc/admin/AccessCmd.hh"
#include "mgm/proc/admin/ConfigCmd.hh"
#include "mgm/proc/admin/ConvertCmd.hh"
#include "mgm/proc/admin/DebugCmd.hh"
#include "mgm/proc/admin/DevicesCmd.hh"
#include "mgm/proc/admin/FsCmd.hh"
#include "mgm/proc/admin/FsckCmd.hh"
#include "mgm/proc/admin/GroupCmd.hh"
#include "mgm/proc/admin/IoCmd.hh"
#include "mgm/proc/admin/NodeCmd.hh"
#include "mgm/proc/admin/NsCmd.hh"
#include "mgm/proc/admin/QuotaCmd.hh"
#include "mgm/proc/admin/SchedCmd.hh"
#include "mgm/proc/admin/SpaceCmd.hh"
#include "mgm/proc/admin/EvictCmd.hh"
#include "mgm/proc/admin/FileRegisterCmd.hh"
#include "mgm/proc/user/AclCmd.hh"
#include "mgm/proc/user/DfCmd.hh"
#include "mgm/proc/user/NewfindCmd.hh"
#include "mgm/proc/user/QoSCmd.hh"
#include "mgm/proc/user/RecycleCmd.hh"
#include "mgm/proc/user/RmCmd.hh"
#include "mgm/proc/user/RouteCmd.hh"
#include "mgm/proc/user/TokenCmd.hh"
#include
EOSMGMNAMESPACE_BEGIN
thread_local eos::common::LogId ProcInterface::tlLogId;
std::mutex ProcInterface::mMutexCmds;
std::list> ProcInterface::mCmdToDel;
std::unordered_map>
ProcInterface::mMapCmds;
eos::common::ThreadPool ProcInterface::sProcThreads(
std::max(std::thread::hardware_concurrency() / 10, 64u),
std::max(std::thread::hardware_concurrency() / 4, 256u),
3, 2, 2, "proc_pool");
//------------------------------------------------------------------------------
// Factory method to get a ProcCommand object
//------------------------------------------------------------------------------
std::unique_ptr
ProcInterface::GetProcCommand(const char* tident,
eos::common::VirtualIdentity& vid,
const char* path, const char* opaque,
const char* log_id)
{
tlLogId.SetLogId((log_id ? log_id : ""), vid, tident);
// Check if this is an already submitted command
std::unique_ptr pcmd = GetSubmittedCmd(tident);
if (pcmd) {
return pcmd;
}
if (!path || !opaque) {
// Return old style proc command which is populated in the open
pcmd.reset(new ProcCommand());
} else {
XrdOucEnv env(opaque);
// New proc command implementation using ProtocolBuffer objects
if (env.Get("mgm.cmd.proto")) {
pcmd = HandleProtobufRequest(opaque, vid);
} else {
pcmd.reset(new ProcCommand());
}
}
return pcmd;
}
//------------------------------------------------------------------------------
// Get asynchronous executing command, submitted earlier by the same client
//------------------------------------------------------------------------------
std::unique_ptr
ProcInterface::GetSubmittedCmd(const char* tident)
{
std::unique_ptr pcmd;
std::lock_guard lock(mMutexCmds);
auto it = mMapCmds.find(tident);
if (it != mMapCmds.end()) {
pcmd.swap(it->second);
mMapCmds.erase(it);
}
return pcmd;
}
//------------------------------------------------------------------------------
// Save asynchronous executing command, so we can stall the client and
// return later on the result.
//------------------------------------------------------------------------------
bool
ProcInterface::SaveSubmittedCmd(const char* tident,
std::unique_ptr&& pcmd)
{
std::lock_guard lock(mMutexCmds);
if (mMapCmds.count(tident)) {
return false;
}
mMapCmds.insert(std::make_pair(std::string(tident), std::move(pcmd)));
return true;
}
//------------------------------------------------------------------------------
// Drop asynchronous executing command since the client disconnected
//------------------------------------------------------------------------------
void
ProcInterface::DropSubmittedCmd(const char* tident)
{
std::lock_guard lock(mMutexCmds);
// Drop any long running commands without connected clients
for (auto it = mCmdToDel.begin(); it != mCmdToDel.end(); /* empty */) {
if ((*it)->KillJob()) {
mCmdToDel.erase(it++);
} else {
++it;
}
}
// Check if this client has any executing command
auto it = mMapCmds.find(tident);
if (it != mMapCmds.end()) {
std::unique_ptr tmp_cmd;
tmp_cmd.swap(it->second);
mMapCmds.erase(it);
if (!tmp_cmd->KillJob()) {
mCmdToDel.push_back(std::move(tmp_cmd));
}
}
}
//----------------------------------------------------------------------------
// Handle protobuf request
//----------------------------------------------------------------------------
unique_ptr
ProcInterface::HandleProtobufRequest(const char* opaque,
eos::common::VirtualIdentity& vid)
{
using eos::console::RequestProto;
std::unique_ptr cmd;
std::ostringstream oss;
std::string raw_pb;
XrdOucEnv env(opaque);
const char* b64data = env.Get("mgm.cmd.proto");
if (!eos::common::SymKey::Base64Decode(b64data, raw_pb)) {
oss << "error: failed to base64decode request";
eos_thread_err("%s", oss.str().c_str());
return cmd;
}
eos::console::RequestProto req;
if (!req.ParseFromString(raw_pb)) {
oss << "error: failed to deserialize ProtocolBuffer object: "
<< raw_pb;
eos_thread_err("%s", oss.str().c_str());
return cmd;
}
return HandleProtobufRequest(req, vid);
}
std::unique_ptr
ProcInterface::HandleProtobufRequest(eos::console::RequestProto& req,
eos::common::VirtualIdentity& vid)
{
using eos::console::RequestProto;
std::unique_ptr cmd;
// Log the type of command that we received
std::string json_out;
(void) google::protobuf::util::MessageToJsonString(req, &json_out);
eos_thread_info("cmd_proto=%s", json_out.c_str());
switch (req.command_case()) {
case RequestProto::kAcl:
cmd.reset(new AclCmd(std::move(req), vid));
break;
case RequestProto::kNs:
cmd.reset(new NsCmd(std::move(req), vid));
break;
case RequestProto::kFind:
cmd.reset(new NewfindCmd(std::move(req), vid));
break;
case RequestProto::kFs:
cmd.reset(new FsCmd(std::move(req), vid));
break;
case RequestProto::kRm:
cmd.reset(new RmCmd(std::move(req), vid));
break;
case RequestProto::kDf:
cmd.reset(new DfCmd(std::move(req), vid));
break;
case RequestProto::kDevices:
cmd.reset(new DevicesCmd(std::move(req), vid));
break;
case RequestProto::kToken:
cmd.reset(new TokenCmd(std::move(req), vid));
break;
case RequestProto::kStagerRm:
cmd.reset(new EvictCmd(std::move(req), vid));
break;
case RequestProto::kEvict:
cmd.reset(new EvictCmd(std::move(req), vid));
break;
case RequestProto::kRoute:
cmd.reset(new RouteCmd(std::move(req), vid));
break;
case RequestProto::kRecycle:
cmd.reset(new RecycleCmd(std::move(req), vid));
break;
case RequestProto::kIo:
cmd.reset(new IoCmd(std::move(req), vid));
break;
case RequestProto::kGroup:
cmd.reset(new GroupCmd(std::move(req), vid));
break;
case RequestProto::kDebug:
cmd.reset(new DebugCmd(std::move(req), vid));
break;
case RequestProto::kNode:
cmd.reset(new NodeCmd(std::move(req), vid));
break;
case RequestProto::kFsck:
cmd.reset(new FsckCmd(std::move(req), vid));
break;
case RequestProto::kQuota:
cmd.reset(new QuotaCmd(std::move(req), vid));
break;
case RequestProto::kSched:
cmd.reset(new SchedCmd(std::move(req), vid));
break;
case RequestProto::kSpace:
cmd.reset(new SpaceCmd(std::move(req), vid));
break;
case RequestProto::kConfig:
cmd.reset(new ConfigCmd(std::move(req), vid));
break;
case RequestProto::kAccess:
cmd.reset(new AccessCmd(std::move(req), vid));
break;
case RequestProto::kQos:
cmd.reset(new QoSCmd(std::move(req), vid));
break;
case RequestProto::kConvert:
cmd.reset(new ConvertCmd(std::move(req), vid));
break;
case RequestProto::kRecord:
cmd.reset(new FileRegisterCmd(std::move(req), vid));
break;
default:
eos_static_err("error: unknown request type");
break;
}
return cmd;
}
//----------------------------------------------------------------------------
// Inspect protobuf request if this modifies the namespace
//----------------------------------------------------------------------------
bool
ProcInterface::ProtoIsWriteAccess(const char* opaque)
{
using eos::console::RequestProto;
std::unique_ptr cmd;
std::ostringstream oss;
std::string raw_pb;
XrdOucEnv env(opaque);
const char* b64data = env.Get("mgm.cmd.proto");
if (!eos::common::SymKey::Base64Decode(b64data, raw_pb)) {
oss << "error: failed to base64decode request";
eos_static_err("%s", oss.str().c_str());
return false;
}
eos::console::RequestProto req;
if (!req.ParseFromString(raw_pb)) {
oss << "error: failed to deserialize ProtocolBuffer object: " << raw_pb;
eos_static_err("%s", oss.str().c_str());
return false;
}
// Log the type of command that we received
std::string json_out;
(void)google::protobuf::util::MessageToJsonString(req, &json_out);
/* being conservative, true by default. Add false clauses explicitly */
switch (req.command_case()) {
case RequestProto::kNs:
switch (req.ns().subcmd_case()) {
case eos::console::NsProto::kQuota:
return true;
default:
return false;
}
case RequestProto::kFind: // @todo could perhaps, check --purge
case RequestProto::kIo:
case RequestProto::kDebug:
case RequestProto::kConfig:
case RequestProto::kToken:
return false;
// conditional on the subcommand
case RequestProto::kAcl:
switch (req.acl().op()) {
case eos::console::AclProto::NONE:
case eos::console::AclProto::LIST:
return false;
default:
return true;
}
case RequestProto::kRecycle:
switch (req.recycle().subcmd_case()) {
case eos::console::RecycleProto::kLs:
return false;
default:
return true;
}
case RequestProto::kFs:
switch (req.fs().subcmd_case()) {
case eos::console::FsProto::kClone:
case eos::console::FsProto::kCompare:
case eos::console::FsProto::kDumpmd:
case eos::console::FsProto::kLs:
case eos::console::FsProto::kStatus:
return false;
default:
return true;
}
case RequestProto::kRoute:
switch (req.route().subcmd_case()) {
case eos::console::RouteProto::kList:
return false;
default:
return true;
}
case RequestProto::kGroup:
switch (req.group().subcmd_case()) {
case eos::console::GroupProto::kLs:
return false;
default:
return true;
}
case RequestProto::kNode:
switch (req.node().subcmd_case()) {
case eos::console::NodeProto::kLs:
case eos::console::NodeProto::kStatus:
return false;
default:
return true;
}
case RequestProto::kQuota:
switch (req.quota().subcmd_case()) {
case eos::console::QuotaProto::kLs:
case eos::console::QuotaProto::kLsuser:
return false;
default:
return true;
}
case RequestProto::kSpace:
switch (req.space().subcmd_case()) {
case eos::console::SpaceProto::kLs:
case eos::console::SpaceProto::kStatus:
case eos::console::SpaceProto::kNodeGet:
return false;
default:
return true;
}
case RequestProto::kAccess:
switch (req.access().subcmd_case()) {
// we have always to allow to modify the access settings, otherwise we cannot remove write or global stalls
default:
return false;
}
// always true
case RequestProto::kRm:
case RequestProto::kStagerRm:
case RequestProto::kEvict:
default:
return true;
}
}
//------------------------------------------------------------------------------
// Check if a path indicates a proc command
//------------------------------------------------------------------------------
bool
ProcInterface::IsProcAccess(const char* path)
{
return (strstr(path, "/proc/") == path);
}
//------------------------------------------------------------------------------
// Check if a proc command is a 'write' command modifying state of an MGM
//------------------------------------------------------------------------------
bool
ProcInterface::IsWriteAccess(const char* path, const char* info)
{
XrdOucString inpath = (path ? path : "");
XrdOucString ininfo = (info ? info : "");
if (!inpath.beginswith("/proc/")) {
return false;
}
XrdOucEnv procEnv(ininfo.c_str());
// Filter protobuf requests
// @TODO: avoid parsing proto buf requests twice (here and later when running the request)
if (procEnv.Get("mgm.cmd.proto")) {
return ProtoIsWriteAccess(ininfo.c_str());
}
XrdOucString cmd = procEnv.Get("mgm.cmd");
XrdOucString subcmd = procEnv.Get("mgm.subcmd");
// Filter here all namespace modifying proc messages
if (((cmd == "file") &&
((subcmd == "adjustreplica") ||
(subcmd == "drop") ||
(subcmd == "layout") ||
(subcmd == "touch") ||
(subcmd == "verify") ||
(subcmd == "version") ||
(subcmd == "versions") ||
(subcmd == "move") ||
(subcmd == "rename"))) ||
((cmd == "attr") &&
((subcmd == "set") ||
(subcmd == "rm"))) ||
((cmd == "archive") &&
((subcmd == "create") ||
(subcmd == "get") ||
(subcmd == "purge") ||
(subcmd == "delete"))) ||
((cmd == "backup")) ||
((cmd == "mkdir")) ||
((cmd == "rmdir")) ||
((cmd == "rm")) ||
((cmd == "chown")) ||
((cmd == "chmod")) ||
((cmd == "fuseX")) ||
((cmd == "fusex")) ||
((cmd == "fs") &&
((subcmd == "config") ||
(subcmd == "boot") ||
(subcmd == "dropdeletion") ||
(subcmd == "add") ||
(subcmd == "mv") ||
(subcmd == "rm"))) ||
((cmd == "space") &&
((subcmd == "config") ||
(subcmd == "define") ||
(subcmd == "set") ||
(subcmd == "rm") ||
(subcmd == "quota"))) ||
((cmd == "node") &&
((subcmd == "rm") ||
(subcmd == "config") ||
(subcmd == "set") ||
(subcmd == "register") ||
(subcmd == "gw"))) ||
((cmd == "group") &&
((subcmd == "set") ||
(subcmd == "rm"))) ||
((cmd == "map") &&
((subcmd == "link") ||
(subcmd == "unlink"))) ||
((cmd == "quota") &&
((subcmd != "ls"))) ||
((cmd == "vid") &&
((subcmd != "ls"))) ||
((cmd == "transfer") &&
((subcmd != ""))) ||
((cmd == "recycle") &&
((subcmd != "ls")))) {
return true;
}
return false;
}
//------------------------------------------------------------------------------
// Authorize a proc command based on the client's VID
//------------------------------------------------------------------------------
bool
ProcInterface::Authorize(const char* path, const char* info,
eos::common::VirtualIdentity& vid,
const XrdSecEntity* entity)
{
XrdOucString inpath = path;
// Administrator access
if (inpath.beginswith("/proc/admin/")) {
// Hosts with 'sss' authentication can run 'admin' commands
std::string protocol = entity ? entity->prot : "";
// We allow sss only with the daemon login is admin
if ((protocol == "sss") &&
(vid.hasUid(DAEMONUID))) {
return true;
}
// Root can do it
if (!vid.uid) {
return true;
}
// One has to be part of the virtual users 2(daemon)/3(adm)/4(adm)
return ((vid.hasUid(DAEMONUID)) ||
(vid.hasUid(3)) ||
(vid.hasGid(4)));
}
// User access
if (inpath.beginswith("/proc/user/")) {
return true;
}
return false;
}
EOSMGMNAMESPACE_END