diff --git a/src/init.cpp b/src/init.cpp index 005cca71c5fa84..54899c36bd3973 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -745,7 +745,6 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-llmq-data-recovery=", strprintf("Enable automated quorum data recovery (default: %u)", llmq::DEFAULT_ENABLE_QUORUM_DATA_RECOVERY), ArgsManager::ALLOW_ANY, OptionsCategory::MASTERNODE); argsman.AddArg("-llmq-qvvec-sync=:", strprintf("Defines from which LLMQ type the masternode should sync quorum verification vectors. Can be used multiple times with different LLMQ types. : %d (sync always from all quorums of the type defined by ), %d (sync from all quorums of the type defined by if a member of any of the quorums)", (int32_t)llmq::QvvecSyncMode::Always, (int32_t)llmq::QvvecSyncMode::OnlyIfTypeMember), ArgsManager::ALLOW_ANY, OptionsCategory::MASTERNODE); argsman.AddArg("-masternodeblsprivkey=", "Set the masternode BLS private key and enable the client to act as a masternode", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::MASTERNODE); - argsman.AddArg("-platform-user=", "Set the username for the \"platform user\", a restricted user intended to be used by Dash Platform, to the specified username.", ArgsManager::ALLOW_ANY, OptionsCategory::MASTERNODE); argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-dustrelayfee=", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); @@ -1623,8 +1622,6 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler); - tableRPC.InitPlatformRestrictions(); - /* Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index a47772a6895573..efcf2bafff414c 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -35,10 +34,7 @@ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ static Mutex g_deadline_timers_mutex; static std::map > deadlineTimers GUARDED_BY(g_deadline_timers_mutex); -static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler, const std::multimap>& mapPlatformRestrictions); - -// Any commands submitted by this user will have their commands filtered based on the mapPlatformRestrictions -static const std::string defaultPlatformUser = "platform-user"; +static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler); struct RPCCommandExecutionInfo { @@ -146,21 +142,6 @@ std::string CRPCTable::help(const std::string& strCommand, const std::string& st return strRet; } -void CRPCTable::InitPlatformRestrictions() -{ - mapPlatformRestrictions = { - {"getassetunlockstatuses", {}}, - {"getbestblockhash", {}}, - {"getblockhash", {}}, - {"getblockcount", {}}, - {"getbestchainlock", {}}, - {"quorum sign", {static_cast(Params().GetConsensus().llmqTypePlatform)}}, - {"quorum verify", {}}, - {"submitchainlock", {}}, - {"verifyislock", {}}, - }; -} - static RPCHelpMan help() { return RPCHelpMan{"help", @@ -525,7 +506,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const UniValue result; for (const auto& command : it->second) { const JSONRPCRequest new_request{subcommand.empty() ? request : request.squashed() }; - if (ExecuteCommand(*command, new_request, result, &command == &it->second.back(), mapPlatformRestrictions)) { + if (ExecuteCommand(*command, new_request, result, &command == &it->second.back())) { return result; } } @@ -533,72 +514,8 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); } -static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler, const std::multimap>& mapPlatformRestrictions) -{ - const NodeContext& node = EnsureAnyNodeContext(request.context); - // Before executing the RPC Command, filter commands from platform rpc user - if (node.mn_activeman && request.authUser == gArgs.GetArg("-platform-user", defaultPlatformUser)) { - // replace this with structured binding in c++20 - std::string command_name = command.name; - if (!command.subname.empty()) command_name += " " + command.subname; - const auto& it = mapPlatformRestrictions.equal_range(command_name); - const auto& allowed_begin = it.first; - const auto& allowed_end = it.second; - /** - * allowed_begin and allowed_end are iterators that represent a range of [method_name, vec_params] - * For example, assume allowed = `quorum sign platformLlmqType`, `quorum verify` and `verifyislock` - * this range will look like: - * - * if request.strMethod == "quorum": - * [ - * "quorum sign", [platformLlmqType], - * "quorum verify", [] - * ] - * if request.strMethod == "verifyislock" - * [ - * "verifyislock", [] - * ] - */ - - // If the requested method is not available in mapPlatformRestrictions - if (allowed_begin == allowed_end) { - throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Method \"%s\" prohibited", request.strMethod)); - } - - auto isValidRequest = [&request, &allowed_begin, &allowed_end]() { - for (auto itRequest = allowed_begin; itRequest != allowed_end; ++itRequest) { - // This is an individual group of parameters that is valid - // This will look something like `["sign", platformLlmqType]` from above. - const auto& vecAllowedParam = itRequest->second; - // An empty vector of allowed parameters represents that any parameter is allowed. - if (vecAllowedParam.empty()) { - return true; - } - if (request.params.empty()) { - throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Method \"%s\" has parameter restrictions.", request.strMethod)); - } - - if (request.params.size() < vecAllowedParam.size()) { - continue; - } - - if (std::equal(vecAllowedParam.begin(), vecAllowedParam.end(), - request.params.getValues().begin(), - [](const UniValue& left, const UniValue& right) { - return left.type() == right.type() && left.getValStr() == right.getValStr(); - })) { - return true; - } - } - return false; - }; - - // Try if any of the mapPlatformRestrictions entries matches the current request - if (!isValidRequest()) { - throw JSONRPCError(RPC_PLATFORM_RESTRICTION, "Request doesn't comply with the parameter restrictions."); - } - } - +static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler) +{ try { RPCCommandExecution execution(request.strMethod); diff --git a/src/rpc/server.h b/src/rpc/server.h index e83cd9bdf081c2..51f3f8f789eb29 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -157,13 +157,10 @@ class CRPCTable { private: std::map, std::vector> mapCommands; - std::multimap> mapPlatformRestrictions; public: CRPCTable(); std::string help(const std::string& name, const std::string& strSubCommand, const JSONRPCRequest& helpreq) const; - void InitPlatformRestrictions(); - /** * Execute a method. * @param request The JSONRPCRequest to execute diff --git a/test/functional/rpc_external_queue.py b/test/functional/rpc_external_queue.py new file mode 100755 index 00000000000000..750084bf0d4b83 --- /dev/null +++ b/test/functional/rpc_external_queue.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 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 + self.supports_cli = False + + def setup_nodes(self): + self.add_nodes(self.num_nodes) + self.start_nodes() + + 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 send_command(method, params, auth, expected_status, should_not_match=False): + 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() + if should_not_match: + assert resp.status != expected_status + else: + assert_equal(resp.status, expected_status) + conn.close() + + rpcuser_authpair_platform = "platform-user:password123" + rpcuser_authpair_operator = "operator:otherpassword" + rpcuser_authpair_wrong = "platform-user:rpcpasswordwrong" + + external_log_str = "HTTP: Calling handler for external user" + always_expected_log_str = "ThreadRPCServer method=" + + self.log.info('Try using a incorrect password for platform-user...') + send_command("getbestblockhash", [], rpcuser_authpair_wrong, 401) + + self.log.info("Check that there's no external queue by default") + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str], unexpected_msgs = [external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_platform, 200) + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str], unexpected_msgs = [external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_operator, 200) + + self.log.info("Restart node with -rpcexternaluser") + self.restart_node(0, extra_args=["-rpcexternaluser=platform-user"]) + + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str, external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_platform, 200) + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str], unexpected_msgs = [external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_operator, 200) + + self.log.info("Restart node with multiple external users") + self.restart_node(0, extra_args=["-rpcexternaluser=platform-user,operator"]) + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str, external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_platform, 200) + with self.nodes[0].assert_debug_log(expected_msgs=[always_expected_log_str, external_log_str]): + send_command("getbestblockhash", [], rpcuser_authpair_operator, 200) + + +if __name__ == '__main__': + HTTPBasicsTest().main() diff --git a/test/functional/rpc_platform_filter.py b/test/functional/rpc_platform_filter.py deleted file mode 100755 index 200ed96f48d159..00000000000000 --- a/test/functional/rpc_platform_filter.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2020-2023 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 - self.supports_cli = False - - def setup_nodes(self): - self.add_nodes(self.num_nodes) - self.start_nodes() - - 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, expected_status, should_not_match=False): - 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() - if should_not_match: - assert resp.status != expected_status - else: - assert_equal(resp.status, expected_status) - conn.close() - - whitelisted = ["getassetunlockstatuses", - "getbestblockhash", - "getblockhash", - "getblockcount", - "getbestchainlock", - "quorum", - "submitchainlock", - "verifyislock"] - - help_output = self.nodes[0].help().split('\n') - nonwhitelisted = set() - - for line in help_output: - line = line.strip() - - # Ignore blanks, headers and whitelisted - if line and not line.startswith('='): - command = line.split()[0] - if not command in whitelisted: - nonwhitelisted.add(command) - - 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) - test_command("quorum", ["sign", 106], rpcuser_authpair_platform, 500) - test_command("quorum", ["sign", 106, "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000001"], - rpcuser_authpair_platform, 200) - test_command("quorum", ["verify"], rpcuser_authpair_platform, 500) - test_command("verifyislock", [], rpcuser_authpair_platform, 500) - - self.log.info('Try using some invalid combinations for platform-user') - test_command("quorum", [], rpcuser_authpair_platform, 403) - test_command("quorum", ["sign"], rpcuser_authpair_platform, 403) - test_command("quorum", ["sign", 102], rpcuser_authpair_platform, 403) - test_command("quorum", ["sign", "100"], rpcuser_authpair_platform, 403) - test_command("quorum", ["dkgsimerror"], rpcuser_authpair_platform, 403) - - self.log.info('Try running all non-whitelisted commands as each user...') - for command in nonwhitelisted: - test_command(command, [], rpcuser_authpair_platform, 403) - if command != "stop": # avoid stopping the node while testing - # we don't care about the exact status here, should simply be anything else but 403 - test_command(command, [], rpcuser_authpair_operator, 403, True) - - self.log.info('Try running a not whitelisted command as the operator...') - test_command("debug", ["1"], rpcuser_authpair_operator, 200) - - - self.log.info("Restart node with -rpcexternaluser") - self.restart_node(0, extra_args=["-rpcexternaluser=platform-user"]) - - external_log_str = "HTTP: Calling handler for external user" - expected_log_str = "ThreadRPCServer method=" - with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str, external_log_str]): - test_command("getbestblockhash", [], rpcuser_authpair_platform, 200) - with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str], unexpected_msgs = [external_log_str]): - test_command("getbestblockhash", [], rpcuser_authpair_operator, 200) - - self.log.info("Restart node with multiple external users") - self.restart_node(0, extra_args=["-rpcexternaluser=platform-user,operator"]) - with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str, external_log_str]): - test_command("getbestblockhash", [], rpcuser_authpair_platform, 200) - with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str, external_log_str]): - test_command("getbestblockhash", [], rpcuser_authpair_operator, 200) - - - -if __name__ == '__main__': - HTTPBasicsTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3af377ba30dde3..84633cac52b56b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -310,7 +310,7 @@ 'wallet_send.py --descriptors', 'wallet_create_tx.py --descriptors', 'p2p_fingerprint.py', - 'rpc_platform_filter.py', + 'rpc_external_queue.py', 'rpc_wipewallettxes.py', 'feature_dip0020_activation.py', 'feature_uacomment.py',