rpc: implement whitelist for commands needed by Dash Platform (#3738)

* implement whitelist for commands needed by Dash Platform

Signed-off-by: pasta <pasta@dashboost.org>

* Add test for platform command filtering

Signed-off-by: pasta <pasta@dashboost.org>

* Use less if statements

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>

* make defaultPlatformUser const

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>

* test: Make rpc_platform_filter.py executable

* test: Refactor tests in rpc_platform_filter.py

* minor modifications to rpc_platform_filter.py

Signed-off-by: pasta <pasta@dashboost.org>

* test: Expand test cases in rpc_platform_filter.py

* rpc: Use std::map instead of std::vector for platformAllowedCommands

* rpc: Improve readability and be more specific about the reject reason

* rpc: Fix comment

* rpc|httprpc: Rename RPC_PROTECTED_COMMAND to RPC_PLATFORM_RESTRICTION

* minor modifications to server.cpp

Signed-off-by: pasta <pasta@dashboost.org>

* add help text

Signed-off-by: pasta <pasta@dashboost.org>

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
Co-authored-by: xdustinface <xdustinfacex@gmail.com>
This commit is contained in:
PastaPastaPasta 2020-11-24 01:39:50 +00:00 committed by GitHub
parent 70788f385c
commit e72164e719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 0 deletions

View File

@ -76,6 +76,9 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni
nStatus = HTTP_BAD_REQUEST;
else if (code == RPC_METHOD_NOT_FOUND)
nStatus = HTTP_NOT_FOUND;
else if (code == RPC_PLATFORM_RESTRICTION) {
nStatus = HTTP_FORBIDDEN;
}
std::string strReply = JSONRPCReply(NullUniValue, objError, id);

View File

@ -613,6 +613,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageGroup(_("Masternode options:"));
strUsage += HelpMessageOpt("-masternodeblsprivkey=<hex>", _("Set the masternode BLS private key and enable the client to act as a masternode"));
strUsage += HelpMessageOpt("-platform-user=<user>", _("Set the username for the \"platform user\", a restricted user intended to be used by Dash Platform, to the specified username."));
strUsage += HelpMessageGroup(_("InstantSend options:"));
strUsage += HelpMessageOpt("-instantsendnotify=<cmd>", _("Execute command when a wallet InstantSend transaction is successfully locked (%s in cmd is replaced by TxID)"));

View File

@ -57,6 +57,7 @@ enum RPCErrorCode
RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
RPC_IN_WARMUP = -28, //!< Client still warming up
RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated
RPC_PLATFORM_RESTRICTION = -33, //!< This RPC command cannot be run by platform-user
//! Aliases for backward compatibility
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,

View File

@ -34,6 +34,16 @@ static RPCTimerInterface* timerInterface = nullptr;
/* Map of name to timer. */
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers;
// Any commands submitted by this user will have their commands filtered based on the platformAllowedCommands
static const std::string defaultPlatformUser = "platform-user";
static const std::map<std::string, std::set<std::string>> platformAllowedCommands{
{"getbestblockhash", {}},
{"getblockhash", {}},
{"getblockcount", {}},
{"getbestchainlock", {}},
};
static struct CRPCSignals
{
boost::signals2::signal<void ()> Started;
@ -550,6 +560,22 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
if (!pcmd)
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
// Before executing the RPC Command, filter commands from platform rpc user
if (fMasternodeMode && request.authUser == gArgs.GetArg("-platform-user", defaultPlatformUser)) {
auto it = platformAllowedCommands.find(request.strMethod);
// If the requested method is not available in platformAllowedCommands
if (it == platformAllowedCommands.end()) {
throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Method \"%s\" prohibited", request.strMethod));
}
const std::string strFirstParam = !request.params.empty() ? request.params[0].getValStr() : "";
// If there are any parameter restrictions for the requested method make sure the first paramter is allowed
if (!it->second.empty() && it->second.count(strFirstParam) == 0) {
throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Parameter \"%s\" prohibited for method \"%s\"", strFirstParam, request.strMethod));
}
}
g_rpcSignals.PreCommand(*pcmd);
try

View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that commands submitted by the platform user are filtered."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import str_to_b64str, assert_equal
import http.client
import json
import os
import urllib.parse
class HTTPBasicsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
def setup_chain(self):
super().setup_chain()
# Append rpcauth to dash.conf before initialization
rpcauthplatform = "rpcauth=platform-user:dd88fd676186f48553775d6fb5a2d344$bc1f7898698ead19c6ec7ff47055622dd7101478f1ff6444103d3dc03cd77c13"
# rpcuser : platform-user
# rpcpassword : password123
rpcauthoperator = "rpcauth=operator:e9b45dd0b61a7be72155535435365a3a$8fb7470bc6f74d8ceaf9a23f49b06127723bd563b3ed5d9cea776ef01803d191"
# rpcuser : operator
# rpcpassword : otherpassword
masternodeblskey="masternodeblsprivkey=58af6e39bb4d86b22bda1a02b134c2f5b71caffa1377540b02f7f1ad122f59e0"
with open(os.path.join(self.options.tmpdir+"/node0", "dash.conf"), 'a', encoding='utf8') as f:
f.write(masternodeblskey+"\n")
f.write(rpcauthplatform+"\n")
f.write(rpcauthoperator+"\n")
def run_test(self):
url = urllib.parse.urlparse(self.nodes[0].url)
def test_command(method, params, auth, expexted_status):
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
body = {"method": method}
if len(params):
body["params"] = params
conn.request('POST', '/', json.dumps(body), {"Authorization": "Basic " + str_to_b64str(auth)})
resp = conn.getresponse()
assert_equal(resp.status, expexted_status)
conn.close()
rpcuser_authpair_platform = "platform-user:password123"
rpcuser_authpair_operator = "operator:otherpassword"
rpcuser_authpair_wrong = "platform-user:rpcpasswordwrong"
self.log.info('Try using a incorrect password for platform-user...')
test_command("getbestblockhash", [], rpcuser_authpair_wrong, 401)
self.log.info('Try using a correct password for platform-user and running all whitelisted commands...')
test_command("getbestblockhash", [], rpcuser_authpair_platform, 200)
test_command("getblockhash", [0], rpcuser_authpair_platform, 200)
test_command("getblockcount", [], rpcuser_authpair_platform, 200)
test_command("getbestchainlock", [], rpcuser_authpair_platform, 500)
self.log.info('Try running a not whitelisted command...')
test_command("stop", [], rpcuser_authpair_platform, 403)
self.log.info('Try running a not whitelisted command as the operator...')
test_command("stop", [], rpcuser_authpair_operator, 200)
if __name__ == '__main__':
HTTPBasicsTest().main()

View File

@ -153,6 +153,7 @@ BASE_SCRIPTS= [
'feature_shutdown.py',
'rpc_privatesend.py',
'p2p_fingerprint.py',
'rpc_platform_filter.py',
'feature_uacomment.py',
'p2p_unrequested_blocks.py',
'feature_logging.py',