1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
32 
33 #include "mongo/platform/basic.h"
34 
35 #include <string>
36 
37 #include "mongo/bson/bsonelement.h"
38 #include "mongo/bson/bsonobj.h"
39 #include "mongo/bson/bsonobjbuilder.h"
40 #include "mongo/bson/util/bson_extract.h"
41 #include "mongo/client/connpool.h"
42 #include "mongo/client/dbclientinterface.h"
43 #include "mongo/db/audit.h"
44 #include "mongo/db/auth/authorization_session.h"
45 #include "mongo/db/commands.h"
46 #include "mongo/rpc/metadata.h"
47 #include "mongo/s/client/shard.h"
48 #include "mongo/s/client/shard_registry.h"
49 #include "mongo/s/grid.h"
50 #include "mongo/util/log.h"
51 #include "mongo/util/mongoutils/str.h"
52 
53 namespace mongo {
54 namespace {
55 
56 class ClusterKillOpCommand : public BasicCommand {
57 public:
ClusterKillOpCommand()58     ClusterKillOpCommand() : BasicCommand("killOp") {}
59 
60 
supportsWriteConcern(const BSONObj & cmd) const61     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
62         return false;
63     }
64 
slaveOk() const65     bool slaveOk() const final {
66         return true;
67     }
68 
adminOnly() const69     bool adminOnly() const final {
70         return true;
71     }
72 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)73     Status checkAuthForCommand(Client* client,
74                                const std::string& dbname,
75                                const BSONObj& cmdObj) final {
76         bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
77             ResourcePattern::forClusterResource(), ActionType::killop);
78         return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized");
79     }
80 
run(OperationContext * opCtx,const std::string & db,const BSONObj & cmdObj,BSONObjBuilder & result)81     bool run(OperationContext* opCtx,
82              const std::string& db,
83              const BSONObj& cmdObj,
84              BSONObjBuilder& result) final {
85         // The format of op is shardid:opid
86         // This is different than the format passed to the mongod killOp command.
87         std::string opToKill;
88         uassertStatusOK(bsonExtractStringField(cmdObj, "op", &opToKill));
89 
90         const auto opSepPos = opToKill.find(':');
91 
92         uassert(28625,
93                 str::stream() << "The op argument to killOp must be of the format shardid:opid"
94                               << " but found \""
95                               << opToKill
96                               << '"',
97                 (opToKill.size() >= 3) &&                  // must have at least N:N
98                     (opSepPos != std::string::npos) &&     // must have ':' as separator
99                     (opSepPos != 0) &&                     // can't be :NN
100                     (opSepPos != (opToKill.size() - 1)));  // can't be NN:
101 
102         auto shardIdent = opToKill.substr(0, opSepPos);
103         log() << "want to kill op: " << redact(opToKill);
104 
105         // Will throw if shard id is not found
106         auto shardStatus = grid.shardRegistry()->getShard(opCtx, shardIdent);
107         if (!shardStatus.isOK()) {
108             return appendCommandStatus(result, shardStatus.getStatus());
109         }
110         auto shard = shardStatus.getValue();
111 
112         int opId;
113         uassertStatusOK(parseNumberFromStringWithBase(opToKill.substr(opSepPos + 1), 10, &opId));
114 
115         // shardid is actually the opid - keeping for backwards compatibility.
116         result.append("shard", shardIdent);
117         result.append("shardid", opId);
118 
119         ScopedDbConnection conn(shard->getConnString());
120         // intentionally ignore return value - that is how legacy killOp worked.
121         conn->runCommand(OpMsgRequest::fromDBAndBody("admin", BSON("killOp" << 1 << "op" << opId)));
122         conn.done();
123 
124         // The original behavior of killOp on mongos is to always return success, regardless of
125         // whether the shard reported success or not.
126         return true;
127     }
128 
129 } clusterKillOpCommand;
130 
131 }  // namespace
132 }  // namespace mongo
133