feat: rpc external users are comma separated list

This commit is contained in:
Konstantin Akimov 2024-06-12 19:45:55 +07:00
parent 68def970ad
commit 241f073932
No known key found for this signature in database
GPG Key ID: 2176C4A5D01EA524
3 changed files with 16 additions and 9 deletions

View File

@ -154,6 +154,8 @@ static struct evhttp* eventHTTP = nullptr;
static std::vector<CSubNet> rpc_allow_subnets; static std::vector<CSubNet> rpc_allow_subnets;
//! Work queue for handling longer requests off the event loop thread //! Work queue for handling longer requests off the event loop thread
static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr}; static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr};
//! List of 'external' RPC users
static std::vector<std::string> g_external_usernames;
//! Handlers for (sub)paths //! Handlers for (sub)paths
static std::vector<HTTPPathHandler> pathHandlers; static std::vector<HTTPPathHandler> pathHandlers;
//! Bound listening sockets //! Bound listening sockets
@ -270,8 +272,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg)
} }
} }
const bool is_external_request = [&hreq]() -> bool { const bool is_external_request = [&hreq]() -> bool {
const std::string external_username = gArgs.GetArg("-rpcexternaluser", ""); if (g_external_usernames.empty()) return false;
if (external_username.empty()) return false;
const std::string strAuth = hreq->GetHeader("authorization").second; const std::string strAuth = hreq->GetHeader("authorization").second;
if (strAuth.substr(0, 6) != "Basic ") if (strAuth.substr(0, 6) != "Basic ")
@ -283,8 +284,8 @@ static void http_request_cb(struct evhttp_request* req, void* arg)
if (invalid) return false; if (invalid) return false;
if (strUserPass.find(':') == std::string::npos) return false; if (strUserPass.find(':') == std::string::npos) return false;
const std::string username{strUserPass.substr(0, strUserPass.find(':'))};
return strUserPass.substr(0, strUserPass.find(':')) == external_username; return find(g_external_usernames.begin(), g_external_usernames.end(), username) != g_external_usernames.end();
}(); }();
// Dispatch to worker thread // Dispatch to worker thread
@ -432,12 +433,11 @@ bool InitHTTPServer()
LogPrint(BCLog::HTTP, "Initialized HTTP server\n"); LogPrint(BCLog::HTTP, "Initialized HTTP server\n");
int workQueueDepth = std::max((long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); int workQueueDepth = std::max((long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
int workQueueDepthExternal = 0; int workQueueDepthExternal = 0;
if (!gArgs.GetArg("-rpcexternaluser", "").empty()) { if (const std::string rpc_externaluser{gArgs.GetArg("-rpcexternaluser", "")}; !rpc_externaluser.empty()) {
LogPrintf("HTTP: creating external work queue of depth %d\n", workQueueDepthExternal);
workQueueDepthExternal = std::max((long)gArgs.GetArg("-rpcexternalworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); workQueueDepthExternal = std::max((long)gArgs.GetArg("-rpcexternalworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
g_external_usernames = SplitString(rpc_externaluser, ',');
} }
LogPrintf("HTTP: creating work queue of depth %d external_depth %d\n", workQueueDepth, workQueueDepthExternal); LogPrintf("HTTP: creating work queue of depth %d external_depth %d\n", workQueueDepth, workQueueDepthExternal);
g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth, workQueueDepthExternal); g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth, workQueueDepthExternal);
// transfer ownership to eventBase/HTTP via .release() // transfer ownership to eventBase/HTTP via .release()
eventBase = base_ctr.release(); eventBase = base_ctr.release();

View File

@ -764,7 +764,7 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcuser. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcuser. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost, or if -rpcallowip has been specified, 0.0.0.0 and :: i.e., all addresses)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost, or if -rpcallowip has been specified, 0.0.0.0 and :: i.e., all addresses)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcexternaluser=<user>", "Username for JSON-RPC external connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcexternaluser=<users>", "List of comma-separated usernames for JSON-RPC external connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcexternalworkqueue=<n>", strprintf("Set the depth of the work queue to service external RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcexternalworkqueue=<n>", strprintf("Set the depth of the work queue to service external RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);

View File

@ -114,7 +114,7 @@ class HTTPBasicsTest(BitcoinTestFramework):
test_command("debug", ["1"], rpcuser_authpair_operator, 200) test_command("debug", ["1"], rpcuser_authpair_operator, 200)
self.log.info("Restart node with -rpcexternaluser...") self.log.info("Restart node with -rpcexternaluser")
self.restart_node(0, extra_args=["-rpcexternaluser=platform-user"]) self.restart_node(0, extra_args=["-rpcexternaluser=platform-user"])
external_log_str = "HTTP: Calling handler for external user" external_log_str = "HTTP: Calling handler for external user"
@ -124,6 +124,13 @@ class HTTPBasicsTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str], unexpected_msgs = [external_log_str]): with self.nodes[0].assert_debug_log(expected_msgs=[expected_log_str], unexpected_msgs = [external_log_str]):
test_command("getbestblockhash", [], rpcuser_authpair_operator, 200) 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__': if __name__ == '__main__':