mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 20:42:59 +01:00
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:
parent
70788f385c
commit
e72164e719
@ -76,6 +76,9 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni
|
|||||||
nStatus = HTTP_BAD_REQUEST;
|
nStatus = HTTP_BAD_REQUEST;
|
||||||
else if (code == RPC_METHOD_NOT_FOUND)
|
else if (code == RPC_METHOD_NOT_FOUND)
|
||||||
nStatus = HTTP_NOT_FOUND;
|
nStatus = HTTP_NOT_FOUND;
|
||||||
|
else if (code == RPC_PLATFORM_RESTRICTION) {
|
||||||
|
nStatus = HTTP_FORBIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
|
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
|
||||||
|
|
||||||
|
@ -613,6 +613,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||||||
|
|
||||||
strUsage += HelpMessageGroup(_("Masternode options:"));
|
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("-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 += HelpMessageGroup(_("InstantSend options:"));
|
||||||
strUsage += HelpMessageOpt("-instantsendnotify=<cmd>", _("Execute command when a wallet InstantSend transaction is successfully locked (%s in cmd is replaced by TxID)"));
|
strUsage += HelpMessageOpt("-instantsendnotify=<cmd>", _("Execute command when a wallet InstantSend transaction is successfully locked (%s in cmd is replaced by TxID)"));
|
||||||
|
@ -57,6 +57,7 @@ enum RPCErrorCode
|
|||||||
RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
|
RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
|
||||||
RPC_IN_WARMUP = -28, //!< Client still warming up
|
RPC_IN_WARMUP = -28, //!< Client still warming up
|
||||||
RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated
|
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
|
//! Aliases for backward compatibility
|
||||||
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
|
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
|
||||||
|
@ -34,6 +34,16 @@ static RPCTimerInterface* timerInterface = nullptr;
|
|||||||
/* Map of name to timer. */
|
/* Map of name to timer. */
|
||||||
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers;
|
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
|
static struct CRPCSignals
|
||||||
{
|
{
|
||||||
boost::signals2::signal<void ()> Started;
|
boost::signals2::signal<void ()> Started;
|
||||||
@ -550,6 +560,22 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
|
|||||||
if (!pcmd)
|
if (!pcmd)
|
||||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
|
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);
|
g_rpcSignals.PreCommand(*pcmd);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
73
test/functional/rpc_platform_filter.py
Executable file
73
test/functional/rpc_platform_filter.py
Executable 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()
|
@ -153,6 +153,7 @@ BASE_SCRIPTS= [
|
|||||||
'feature_shutdown.py',
|
'feature_shutdown.py',
|
||||||
'rpc_privatesend.py',
|
'rpc_privatesend.py',
|
||||||
'p2p_fingerprint.py',
|
'p2p_fingerprint.py',
|
||||||
|
'rpc_platform_filter.py',
|
||||||
'feature_uacomment.py',
|
'feature_uacomment.py',
|
||||||
'p2p_unrequested_blocks.py',
|
'p2p_unrequested_blocks.py',
|
||||||
'feature_logging.py',
|
'feature_logging.py',
|
||||||
|
Loading…
Reference in New Issue
Block a user