Skip to content

Commit 7414d38

Browse files
committed
Add RPC Whitelist Feature from #12248
1 parent 3052130 commit 7414d38

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/httprpc.cpp

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
#include <util/translation.h>
1616
#include <walletinitinterface.h>
1717

18+
#include <algorithm>
19+
#include <iterator>
20+
#include <map>
1821
#include <memory>
1922
#include <stdio.h>
23+
#include <set>
24+
#include <string>
2025

2126
#include <boost/algorithm/string.hpp> // boost::trim
2227

@@ -64,6 +69,9 @@ class HTTPRPCTimerInterface : public RPCTimerInterface
6469
static std::string strRPCUserColonPass;
6570
/* Stored RPC timer interface (for unregistration) */
6671
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
72+
/* RPC Auth Whitelist */
73+
static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
74+
static bool g_rpc_whitelist_default = false;
6775

6876
static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
6977
{
@@ -183,18 +191,45 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
183191
jreq.URI = req->GetURI();
184192

185193
std::string strReply;
194+
bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);
195+
if (!user_has_whitelist && g_rpc_whitelist_default) {
196+
LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser);
197+
req->WriteReply(HTTP_FORBIDDEN);
198+
return false;
199+
186200
// singleton request
187-
if (valRequest.isObject()) {
201+
} else if (valRequest.isObject()) {
188202
jreq.parse(valRequest);
189-
203+
if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) {
204+
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod);
205+
req->WriteReply(HTTP_FORBIDDEN);
206+
return false;
207+
}
190208
UniValue result = tableRPC.execute(jreq);
191209

192210
// Send reply
193211
strReply = JSONRPCReply(result, NullUniValue, jreq.id);
194212

195213
// array of requests
196-
} else if (valRequest.isArray())
214+
} else if (valRequest.isArray()) {
215+
if (user_has_whitelist) {
216+
for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) {
217+
if (!valRequest[reqIdx].isObject()) {
218+
throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
219+
} else {
220+
const UniValue& request = valRequest[reqIdx].get_obj();
221+
// Parse method
222+
std::string strMethod = find_value(request, "method").get_str();
223+
if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) {
224+
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod);
225+
req->WriteReply(HTTP_FORBIDDEN);
226+
return false;
227+
}
228+
}
229+
}
230+
}
197231
strReply = JSONRPCExecBatch(jreq, valRequest.get_array());
232+
}
198233
else
199234
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
200235

@@ -229,6 +264,27 @@ static bool InitRPCAuthentication()
229264
{
230265
LogPrintf("Using rpcauth authentication.\n");
231266
}
267+
268+
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));
269+
for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {
270+
auto pos = strRPCWhitelist.find(':');
271+
std::string strUser = strRPCWhitelist.substr(0, pos);
272+
bool intersect = g_rpc_whitelist.count(strUser);
273+
std::set<std::string>& whitelist = g_rpc_whitelist[strUser];
274+
if (pos != std::string::npos) {
275+
std::string strWhitelist = strRPCWhitelist.substr(pos + 1);
276+
std::set<std::string> new_whitelist;
277+
boost::split(new_whitelist, strWhitelist, boost::is_any_of(", "));
278+
if (intersect) {
279+
std::set<std::string> tmp_whitelist;
280+
std::set_intersection(new_whitelist.begin(), new_whitelist.end(),
281+
whitelist.begin(), whitelist.end(), std::inserter(tmp_whitelist, tmp_whitelist.end()));
282+
new_whitelist = std::move(tmp_whitelist);
283+
}
284+
whitelist = std::move(new_whitelist);
285+
}
286+
}
287+
232288
return true;
233289
}
234290

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,8 @@ void SetupServerArgs()
534534
gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
535535
gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
536536
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
537+
gArgs.AddArg("-rpcwhitelist=<whitelist>", "Set a whitelist to filter incoming RPC calls for a specific user. The field <whitelist> comes in the format: <USERNAME>:<rpc 1>,<rpc 2>,...,<rpc n>. If multiple whitelists are set for a given user, they are set-intersected. See -rpcwhitelistdefault documentation for information on default whitelist behavior.", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
538+
gArgs.AddArg("-rpcwhitelistdefault", "Sets default behavior for rpc whitelisting. Unless rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc server acts as if all rpc users are subject to empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault is set to 1 and no -rpcwhitelist is set, rpc server acts as if all rpc users are subject to empty whitelists.", ArgsManager::ALLOW_BOOL, OptionsCategory::RPC);
537539
gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
538540
gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
539541

0 commit comments

Comments
 (0)