Skip to content

Commit

Permalink
Add RPC Whitelist Feature from bitcoin#12248
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyRubin authored and Stackout committed Feb 17, 2022
1 parent 0bd603c commit d3ed485
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
62 changes: 59 additions & 3 deletions src/httprpc.cpp
Expand Up @@ -15,8 +15,13 @@
#include <util/translation.h>
#include <walletinitinterface.h>

#include <algorithm>
#include <iterator>
#include <map>
#include <memory>
#include <stdio.h>
#include <set>
#include <string>

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

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

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

std::string strReply;
bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);
if (!user_has_whitelist && g_rpc_whitelist_default) {
LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser);
req->WriteReply(HTTP_FORBIDDEN);
return false;

// singleton request
if (valRequest.isObject()) {
} else if (valRequest.isObject()) {
jreq.parse(valRequest);

if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) {
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod);
req->WriteReply(HTTP_FORBIDDEN);
return false;
}
UniValue result = tableRPC.execute(jreq);

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

// array of requests
} else if (valRequest.isArray())
} else if (valRequest.isArray()) {
if (user_has_whitelist) {
for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) {
if (!valRequest[reqIdx].isObject()) {
throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
} else {
const UniValue& request = valRequest[reqIdx].get_obj();
// Parse method
std::string strMethod = find_value(request, "method").get_str();
if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) {
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod);
req->WriteReply(HTTP_FORBIDDEN);
return false;
}
}
}
}
strReply = JSONRPCExecBatch(jreq, valRequest.get_array());
}
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");

Expand Down Expand Up @@ -229,6 +264,27 @@ static bool InitRPCAuthentication()
{
LogPrintf("Using rpcauth authentication.\n");
}

g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));
for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {
auto pos = strRPCWhitelist.find(':');
std::string strUser = strRPCWhitelist.substr(0, pos);
bool intersect = g_rpc_whitelist.count(strUser);
std::set<std::string>& whitelist = g_rpc_whitelist[strUser];
if (pos != std::string::npos) {
std::string strWhitelist = strRPCWhitelist.substr(pos + 1);
std::set<std::string> new_whitelist;
boost::split(new_whitelist, strWhitelist, boost::is_any_of(", "));
if (intersect) {
std::set<std::string> tmp_whitelist;
std::set_intersection(new_whitelist.begin(), new_whitelist.end(),
whitelist.begin(), whitelist.end(), std::inserter(tmp_whitelist, tmp_whitelist.end()));
new_whitelist = std::move(tmp_whitelist);
}
whitelist = std::move(new_whitelist);
}
}

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/init.cpp
Expand Up @@ -535,6 +535,8 @@ void SetupServerArgs()
gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
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);
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);
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);
gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);

Expand Down

0 comments on commit d3ed485

Please sign in to comment.