mirror of
https://github.com/monero-project/monero.git
synced 2026-04-29 12:19:02 -07:00
zmq: add restricted rpc mode
This commit is contained in:
@@ -133,6 +133,18 @@ namespace daemon_args
|
||||
, "Address for ZMQ pub - tcp://ip:port or ipc://path"
|
||||
};
|
||||
|
||||
const command_line::arg_descriptor<bool> arg_restricted_zmq_rpc = {
|
||||
"restricted-zmq-rpc"
|
||||
, "Restrict ZMQ RPC to view-only / non-sensitive methods"
|
||||
, false
|
||||
};
|
||||
|
||||
const command_line::arg_descriptor<bool> arg_confirm_zmq_rpc_external_bind = {
|
||||
"confirm-zmq-rpc-external-bind"
|
||||
, "Confirm zmq-rpc-bind-ip value is NOT a loopback (local) IP"
|
||||
, false
|
||||
};
|
||||
|
||||
const command_line::arg_descriptor<bool> arg_zmq_rpc_disabled = {
|
||||
"no-zmq"
|
||||
, "Disable ZMQ RPC server"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include "misc_log_ex.h"
|
||||
#include "daemon/daemon.h"
|
||||
#include "rpc/daemon_handler.h"
|
||||
@@ -58,10 +59,39 @@ using namespace epee;
|
||||
|
||||
namespace daemonize {
|
||||
|
||||
namespace
|
||||
{
|
||||
void verify_zmq_rpc_bind(const boost::program_options::variables_map& vm)
|
||||
{
|
||||
std::string bind_ip = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip);
|
||||
if (bind_ip.empty())
|
||||
return;
|
||||
|
||||
// ZMQ bind input already accepts bracketed IPv6 literals, but
|
||||
// boost::asio::ip::make_address does not.
|
||||
if (bind_ip.size() >= 2 && bind_ip.front() == '[' && bind_ip.back() == ']')
|
||||
bind_ip = bind_ip.substr(1, bind_ip.size() - 2);
|
||||
|
||||
boost::system::error_code ec{};
|
||||
const auto parsed_ip = boost::asio::ip::make_address(bind_ip, ec);
|
||||
if (ec)
|
||||
throw std::runtime_error{"Invalid IP address given for --" + std::string(daemon_args::arg_zmq_rpc_bind_ip.name)};
|
||||
|
||||
if (!parsed_ip.is_loopback() && !command_line::get_arg(vm, daemon_args::arg_confirm_zmq_rpc_external_bind))
|
||||
{
|
||||
throw std::runtime_error{
|
||||
std::string{"--"} + daemon_args::arg_zmq_rpc_bind_ip.name +
|
||||
" permits inbound unencrypted external connections. Consider SSH tunnel or SSL proxy instead. Override with --" +
|
||||
daemon_args::arg_confirm_zmq_rpc_external_bind.name
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct zmq_internals
|
||||
{
|
||||
explicit zmq_internals(t_core& core, t_p2p& p2p)
|
||||
: rpc_handler{core.get(), p2p.get()}
|
||||
explicit zmq_internals(t_core& core, t_p2p& p2p, const bool restricted)
|
||||
: rpc_handler{core.get(), p2p.get(), restricted}
|
||||
, server{rpc_handler}
|
||||
{}
|
||||
|
||||
@@ -104,7 +134,10 @@ public:
|
||||
|
||||
if (!command_line::get_arg(vm, daemon_args::arg_zmq_rpc_disabled))
|
||||
{
|
||||
zmq.reset(new zmq_internals{core, p2p});
|
||||
verify_zmq_rpc_bind(vm);
|
||||
|
||||
const bool restricted = command_line::get_arg(vm, daemon_args::arg_restricted_zmq_rpc);
|
||||
zmq.reset(new zmq_internals{core, p2p, restricted});
|
||||
|
||||
const std::string zmq_port = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_port);
|
||||
const std::string zmq_address = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip);
|
||||
@@ -133,12 +166,20 @@ public:
|
||||
{
|
||||
MWARNING("WARN: --zmq-rpc-bind-port has no effect because --no-zmq was specified");
|
||||
}
|
||||
else if (command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip) !=
|
||||
if (command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip) !=
|
||||
daemon_args::arg_zmq_rpc_bind_ip.default_value)
|
||||
{
|
||||
MWARNING("WARN: --zmq-rpc-bind-ip has no effect because --no-zmq was specified");
|
||||
}
|
||||
else if (!command_line::get_arg(vm, daemon_args::arg_zmq_pub).empty())
|
||||
if (command_line::get_arg(vm, daemon_args::arg_confirm_zmq_rpc_external_bind))
|
||||
{
|
||||
MWARNING("WARN: --confirm-zmq-rpc-external-bind has no effect because --no-zmq was specified");
|
||||
}
|
||||
if (command_line::get_arg(vm, daemon_args::arg_restricted_zmq_rpc))
|
||||
{
|
||||
MWARNING("WARN: --restricted-zmq-rpc has no effect because --no-zmq was specified");
|
||||
}
|
||||
if (!command_line::get_arg(vm, daemon_args::arg_zmq_pub).empty())
|
||||
{
|
||||
MWARNING("WARN: --zmq-pub has no effect because --no-zmq was specified");
|
||||
}
|
||||
|
||||
@@ -157,6 +157,8 @@ int main(int argc, char const * argv[])
|
||||
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_ip);
|
||||
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port);
|
||||
command_line::add_arg(core_settings, daemon_args::arg_zmq_pub);
|
||||
command_line::add_arg(core_settings, daemon_args::arg_confirm_zmq_rpc_external_bind);
|
||||
command_line::add_arg(core_settings, daemon_args::arg_restricted_zmq_rpc);
|
||||
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_disabled);
|
||||
command_line::add_arg(core_settings, daemonizer::arg_non_interactive);
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ set(rpc_pub_sources zmq_pub.cpp)
|
||||
|
||||
set(daemon_rpc_server_sources
|
||||
daemon_handler.cpp
|
||||
zmq_restricted_methods.cpp
|
||||
zmq_pub.cpp
|
||||
zmq_server.cpp)
|
||||
|
||||
@@ -79,6 +80,7 @@ set(daemon_rpc_server_private_headers
|
||||
message.h
|
||||
daemon_messages.h
|
||||
daemon_handler.h
|
||||
zmq_restricted_methods.h
|
||||
zmq_server.h)
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "daemon_handler.h"
|
||||
#include "rpc/zmq_restricted_methods.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -109,12 +110,14 @@ namespace rpc
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
DaemonHandler::DaemonHandler(cryptonote::core& c, t_p2p& p2p)
|
||||
: m_core(c), m_p2p(p2p)
|
||||
DaemonHandler::DaemonHandler(cryptonote::core& c, t_p2p& p2p, bool restricted)
|
||||
: m_core(c), m_p2p(p2p), m_restricted(restricted)
|
||||
{
|
||||
const auto last_sorted = std::is_sorted_until(std::begin(handlers), std::end(handlers));
|
||||
if (last_sorted != std::end(handlers))
|
||||
throw std::logic_error{std::string{"ZMQ JSON-RPC handlers map is not properly sorted, see "} + last_sorted->method_name};
|
||||
|
||||
check_blocked_methods_sorted();
|
||||
}
|
||||
|
||||
void DaemonHandler::handle(const GetHeight::Request& req, GetHeight::Response& res)
|
||||
@@ -921,13 +924,24 @@ namespace rpc
|
||||
|
||||
epee::byte_slice DaemonHandler::handle(std::string&& request)
|
||||
{
|
||||
MDEBUG("Handling RPC request: " << request);
|
||||
if (m_restricted)
|
||||
MDEBUG("Handling RPC request");
|
||||
else
|
||||
MDEBUG("Handling RPC request: " << request);
|
||||
|
||||
try
|
||||
{
|
||||
FullMessage req_full(std::move(request), true);
|
||||
|
||||
const std::string request_type = req_full.getRequestType();
|
||||
if (m_restricted && is_blocked_in_restricted_mode(request_type))
|
||||
{
|
||||
Message fail;
|
||||
fail.status = Message::STATUS_FAILED;
|
||||
fail.error_details = "\"" + request_type + "\" is not available in restricted mode.";
|
||||
return FullMessage::getResponse(fail, req_full.getID());
|
||||
}
|
||||
|
||||
const auto matched_handler = std::lower_bound(std::begin(handlers), std::end(handlers), request_type);
|
||||
if (matched_handler == std::end(handlers) || matched_handler->method_name != request_type)
|
||||
return BAD_REQUEST(request_type, req_full.getID());
|
||||
|
||||
@@ -51,7 +51,7 @@ class DaemonHandler : public RpcHandler
|
||||
{
|
||||
public:
|
||||
|
||||
DaemonHandler(cryptonote::core& c, t_p2p& p2p);
|
||||
DaemonHandler(cryptonote::core& c, t_p2p& p2p, bool restricted = false);
|
||||
|
||||
~DaemonHandler() { }
|
||||
|
||||
@@ -143,6 +143,7 @@ class DaemonHandler : public RpcHandler
|
||||
|
||||
cryptonote::core& m_core;
|
||||
t_p2p& m_p2p;
|
||||
bool m_restricted;
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
75
src/rpc/zmq_restricted_methods.cpp
Normal file
75
src/rpc/zmq_restricted_methods.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2016-2026, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "rpc/zmq_restricted_methods.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::array<std::string_view, 9> blocked_in_restricted_mode{{
|
||||
"flush_txpool",
|
||||
"get_peer_list",
|
||||
"mining_status",
|
||||
"relay_tx",
|
||||
"save_bc",
|
||||
"set_log_categories",
|
||||
"set_log_level",
|
||||
"start_mining",
|
||||
"stop_mining"
|
||||
}};
|
||||
}
|
||||
|
||||
bool is_blocked_in_restricted_mode(const std::string_view method) noexcept
|
||||
{
|
||||
return std::binary_search(
|
||||
blocked_in_restricted_mode.begin(),
|
||||
blocked_in_restricted_mode.end(),
|
||||
method
|
||||
);
|
||||
}
|
||||
|
||||
void check_blocked_methods_sorted()
|
||||
{
|
||||
const auto last =
|
||||
std::is_sorted_until(blocked_in_restricted_mode.begin(), blocked_in_restricted_mode.end());
|
||||
|
||||
if (last != blocked_in_restricted_mode.end())
|
||||
throw std::logic_error{
|
||||
std::string{"ZMQ restricted-method map is not properly sorted, see "} + std::string{*last}
|
||||
};
|
||||
}
|
||||
} // rpc
|
||||
} // cryptonote
|
||||
45
src/rpc/zmq_restricted_methods.h
Normal file
45
src/rpc/zmq_restricted_methods.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2016-2026, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
//! Returns true when `method` must be rejected while ZMQ RPC runs in
|
||||
//! restricted mode. Keep this list in sync with daemon RPC method
|
||||
bool is_blocked_in_restricted_mode(std::string_view method) noexcept;
|
||||
|
||||
//! Throws std::logic_error if the internal method table is not sorted.
|
||||
void check_blocked_methods_sorted();
|
||||
} // rpc
|
||||
} // cryptonote
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "net/zmq.h"
|
||||
#include "rpc/message.h"
|
||||
#include "rpc/zmq_pub.h"
|
||||
#include "rpc/zmq_restricted_methods.h"
|
||||
#include "rpc/zmq_server.h"
|
||||
#include "serialization/json_object.h"
|
||||
|
||||
@@ -69,6 +70,23 @@ TEST(ZmqFullMessage, Request)
|
||||
EXPECT_STREQ("foo", parsed.getRequestType().c_str());
|
||||
}
|
||||
|
||||
TEST(ZmqRestrictedMethods, BasicCoverage)
|
||||
{
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("flush_txpool"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("get_peer_list"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("mining_status"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("relay_tx"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("save_bc"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("set_log_categories"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("set_log_level"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("start_mining"));
|
||||
EXPECT_TRUE(cryptonote::rpc::is_blocked_in_restricted_mode("stop_mining"));
|
||||
|
||||
EXPECT_FALSE(cryptonote::rpc::is_blocked_in_restricted_mode("get_height"));
|
||||
EXPECT_FALSE(cryptonote::rpc::is_blocked_in_restricted_mode("get_info"));
|
||||
EXPECT_FALSE(cryptonote::rpc::is_blocked_in_restricted_mode("send_raw_tx"));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using published_json = std::pair<std::string, rapidjson::Document>;
|
||||
|
||||
Reference in New Issue
Block a user