diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index 5e4107c902..64d1fb0343 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -187,13 +187,21 @@ # using HTTP's Basic Authentication headers when making outbound HTTP/S # requests. # -# admin = no | allow +# admin = [ IP, IP, IP, ... ] # -# Controls whether or not administrative commands are allowed. These -# commands may be issued over http, https, ws, or wss if configured -# on the port. If unspecified, the default is to not allow +# A comma-separated list of IP addresses. +# +# When set, grants administrative command access to the specified IP +# addresses. These commands may be issued over http, https, ws, or wss +# if configured on the port. If unspecified, the default is to not allow # administrative commands. # +# *SECURITY WARNING* +# 0.0.0.0 may be specified to allow access from any IP address. It must +# be the only address specified and cannot be combined with other IPs. +# Use of this address can compromise server security, please consider its +# use carefully. +# # admin_user = # admin_password = # @@ -233,15 +241,6 @@ # # # -# [rpc_admin_allow] -# -# Specify a list of IP addresses allowed to have admin access. One per line. -# If you want to test the output of non-admin commands add this section and -# just put an ip address not under your control. -# Defaults to 127.0.0.1. -# -# -# # [rpc_startup] # # Specify a list of RPC commands to run at startup. @@ -865,7 +864,7 @@ port_wss_admin [port_rpc] port = 5005 ip = 127.0.0.1 -admin = allow +admin = 127.0.0.1 protocol = https [port_peer] @@ -876,7 +875,7 @@ protocol = peer [port_wss_admin] port = 6006 ip = 127.0.0.1 -admin = allow +admin = 127.0.0.1 protocol = wss #[port_ws_public] diff --git a/src/beast/beast/net/IPAddress.h b/src/beast/beast/net/IPAddress.h index e59020a963..f5bb8bbe90 100644 --- a/src/beast/beast/net/IPAddress.h +++ b/src/beast/beast/net/IPAddress.h @@ -139,6 +139,14 @@ public: return m_v6; } + /** Returns `true` if this address represents 0.0.0.0 */ + bool + is_any () const + { + return is_v4 () ? m_v4 == IP::AddressV4::any () + : false; // m_v6 == IP::AddressV6::any(); + } + template friend void diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 8f207846c4..5ed904152d 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -213,7 +213,6 @@ public: int WEBSOCKET_PING_FREQ; // RPC parameters - std::vector RPC_ADMIN_ALLOW; Json::Value RPC_STARTUP; // Path searching diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h index e3cde6de71..f6dd5bdf5e 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/ripple/core/ConfigSections.h @@ -57,7 +57,6 @@ struct ConfigSection #define SECTION_PATH_SEARCH_MAX "path_search_max" #define SECTION_PEER_PRIVATE "peer_private" #define SECTION_PEERS_MAX "peers_max" -#define SECTION_RPC_ADMIN_ALLOW "rpc_admin_allow" #define SECTION_RPC_STARTUP "rpc_startup" #define SECTION_SMS_FROM "sms_from" #define SECTION_SMS_KEY "sms_key" diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 188e1f911e..f0044e1c66 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -201,8 +201,6 @@ Config::Config () WEBSOCKET_PING_FREQ = (5 * 60); - RPC_ADMIN_ALLOW.push_back (beast::IP::Endpoint::from_string("127.0.0.1")); - PEER_PRIVATE = false; PEERS_MAX = 0; // indicates "use default" @@ -431,14 +429,6 @@ void Config::loadFromString (std::string const& fileContents) if (getSingleSection (secConfig, SECTION_PEERS_MAX, strTemp)) PEERS_MAX = beast::lexicalCastThrow (strTemp); - if (auto s = getIniFileSection (secConfig, SECTION_RPC_ADMIN_ALLOW)) - { - std::vector parsedAddresses; - parseAddresses (parsedAddresses, (*s).cbegin(), (*s).cend()); - RPC_ADMIN_ALLOW.insert (RPC_ADMIN_ALLOW.end(), - parsedAddresses.cbegin (), parsedAddresses.cend ()); - } - if (getSingleSection (secConfig, SECTION_NODE_SIZE, strTemp)) { if (strTemp == "tiny") diff --git a/src/ripple/server/Port.h b/src/ripple/server/Port.h index b92d1e1d21..26b5fcc20a 100644 --- a/src/ripple/server/Port.h +++ b/src/ripple/server/Port.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_SERVER_PORT_H_INCLUDED #define RIPPLE_SERVER_PORT_H_INCLUDED +#include #include #include #include @@ -39,7 +40,7 @@ struct Port boost::asio::ip::address ip; std::uint16_t port = 0; std::set protocol; - bool allow_admin = false; + std::vector admin_ip; std::string user; std::string password; std::string admin_user; @@ -97,11 +98,16 @@ inline std::ostream& operator<< (std::ostream& os, Port const& p) { - os << - "'" << p.name << - "' (ip=" << p.ip << ":" << p.port << - (p.allow_admin ? ", admin, " : ", ") << - p.protocols() << ")"; + os << "'" << p.name << "' (ip=" << p.ip << ":" << p.port << ", "; + + if (! p.admin_ip.empty ()) + { + os << "admin IPs:"; + for (auto const& ip : p.admin_ip) + os << ip.to_string () << ", "; + } + + os << p.protocols () << ")"; return os; } diff --git a/src/ripple/server/Role.h b/src/ripple/server/Role.h index ff06b279f9..e8a13fb985 100644 --- a/src/ripple/server/Role.h +++ b/src/ripple/server/Role.h @@ -45,8 +45,7 @@ enum class Role */ Role requestRole (Role const& required, HTTP::Port const& port, - Json::Value const& jsonRPC, beast::IP::Endpoint const& remoteIp, - std::vector const& admin_allow); + Json::Value const& jsonRPC, beast::IP::Endpoint const& remoteIp); } // ripple diff --git a/src/ripple/server/impl/Role.cpp b/src/ripple/server/impl/Role.cpp index 3d4f2fd383..9b4139feae 100644 --- a/src/ripple/server/impl/Role.cpp +++ b/src/ripple/server/impl/Role.cpp @@ -26,7 +26,7 @@ bool passwordUnrequiredOrSentCorrect (HTTP::Port const& port, Json::Value const& params) { - assert(port.allow_admin); + assert(! port.admin_ip.empty ()); bool const passwordRequired = (!port.admin_user.empty() || !port.admin_password.empty()); @@ -38,29 +38,28 @@ passwordUnrequiredOrSentCorrect (HTTP::Port const& port, } bool -ipAllowed (beast::IP::Endpoint const& remoteIp, - std::vector const& adminAllow) +ipAllowed (beast::IP::Address const& remoteIp, + std::vector const& adminIp) { - return std::find(adminAllow.begin(), adminAllow.end(), - remoteIp.at_port(0)) != adminAllow.end(); + return std::find_if (adminIp.begin (), adminIp.end (), + [&remoteIp](beast::IP::Address const& ip) { return ip.is_any () || + ip == remoteIp; }) != adminIp.end (); } bool isAdmin (HTTP::Port const& port, Json::Value const& params, - beast::IP::Endpoint const& remoteIp, - std::vector const& adminAllow) + beast::IP::Address const& remoteIp) { - return (port.allow_admin && ipAllowed(remoteIp, adminAllow)) && - passwordUnrequiredOrSentCorrect(port, params); + return ipAllowed (remoteIp, port.admin_ip) && + passwordUnrequiredOrSentCorrect (port, params); } Role requestRole (Role const& required, HTTP::Port const& port, - Json::Value const& params, beast::IP::Endpoint const& remoteIp, - std::vector const& adminAllow) + Json::Value const& params, beast::IP::Endpoint const& remoteIp) { Role role (Role::GUEST); - if (isAdmin(port, params, remoteIp, adminAllow)) + if (isAdmin(port, params, remoteIp.address ())) role = Role::ADMIN; if (required == Role::ADMIN && role != required) role = Role::FORBID; diff --git a/src/ripple/server/impl/ServerHandlerImp.cpp b/src/ripple/server/impl/ServerHandlerImp.cpp index a414e5c61a..59813ef746 100644 --- a/src/ripple/server/impl/ServerHandlerImp.cpp +++ b/src/ripple/server/impl/ServerHandlerImp.cpp @@ -291,7 +291,6 @@ ServerHandlerImp::processRequest ( } /* ---------------------------------------------------------------------- */ - auto const& admin_allow = getConfig().RPC_ADMIN_ALLOW; auto role = Role::FORBID; auto required = RPC::roleRequired(id.asString()); @@ -300,12 +299,12 @@ ServerHandlerImp::processRequest ( jsonRPC["params"][Json::UInt(0)].isObject()) { role = requestRole(required, port, jsonRPC["params"][Json::UInt(0)], - remoteIPAddress, admin_allow); + remoteIPAddress); } else { role = requestRole(required, port, Json::objectValue, - remoteIPAddress, admin_allow); + remoteIPAddress); } Resource::Consumer usage; @@ -521,7 +520,7 @@ struct ParsedPort boost::optional ip; boost::optional port; - boost::optional allow_admin; + boost::optional> admin_ip; }; void @@ -579,19 +578,35 @@ parse_Port (ParsedPort& port, Section const& section, std::ostream& log) auto const result = section.find("admin"); if (result.second) { - if (result.first == "no") + std::stringstream ss (result.first); + std::string ip; + bool has_any (false); + + port.admin_ip.emplace (); + while (std::getline (ss, ip, ',')) { - port.allow_admin = false; - } - else if (result.first == "allow") - { - port.allow_admin = true; - } - else - { - log << "Invalid value '" << result.first << - "' for key 'admin' in [" << section.name() << "]\n"; - throw std::exception(); + beast::IP::Address const addr( + beast::IP::Endpoint::from_string_altform (ip).address ()); + + if (addr.is_any ()) + { + has_any = true; + } + else if (is_unspecified (addr)) + { + log << "Invalid value '" << ip << "' for key 'admin' in [" + << section.name() << "]\n"; + throw std::exception (); + } + + if (has_any && ! port.admin_ip->empty ()) + { + log << "IP specified with 0.0.0.0 '" << ip << + "' for key 'admin' in [" << section.name () << "]\n"; + throw std::exception (); + } + + port.admin_ip->emplace_back (addr); } } } @@ -629,11 +644,9 @@ to_Port(ParsedPort const& parsed, std::ostream& log) throw std::exception(); } p.port = *parsed.port; - - if (! parsed.allow_admin) - p.allow_admin = false; - else - p.allow_admin = *parsed.allow_admin; + + if (parsed.admin_ip) + p.admin_ip = *parsed.admin_ip; if (parsed.protocol.empty()) { diff --git a/src/ripple/websocket/Connection.h b/src/ripple/websocket/Connection.h index a63aa191dc..0346ace01c 100644 --- a/src/ripple/websocket/Connection.h +++ b/src/ripple/websocket/Connection.h @@ -100,7 +100,7 @@ public: void setPingTimer (); private: - HTTP::Port const& port_; + HTTP::Port const& m_port; Resource::Manager& m_resourceManager; Resource::Consumer m_usage; bool const m_isPublic; @@ -129,7 +129,7 @@ ConnectionImpl ::ConnectionImpl ( boost::asio::io_service& io_service) : InfoSub (source, // usage resourceManager.newInboundEndpoint (remoteAddress)) - , port_ (handler.port()) + , m_port (handler.port ()) , m_resourceManager (resourceManager) , m_isPublic (handler.getPublic ()) , m_remoteAddress (remoteAddress) @@ -262,8 +262,7 @@ Json::Value ConnectionImpl ::invokeCommand (Json::Value& jvRequest) Json::Value jvResult (Json::objectValue); auto required = RPC::roleRequired (jvRequest[jss::command].asString()); - Role const role = requestRole (required, port_, jvRequest, m_remoteAddress, - getConfig().RPC_ADMIN_ALLOW); + Role const role = requestRole (required, m_port, jvRequest, m_remoteAddress); if (Role::FORBID == role) { diff --git a/src/ripple/websocket/Handler.h b/src/ripple/websocket/Handler.h index 9c74309271..d18cfb37bc 100644 --- a/src/ripple/websocket/Handler.h +++ b/src/ripple/websocket/Handler.h @@ -107,7 +107,7 @@ public: bool getPublic() { - return port().allow_admin; + return ! port ().admin_ip.empty (); }; void send (connection_ptr const& cpClient, message_ptr const& mpMessage) diff --git a/src/ripple/websocket/Server.h b/src/ripple/websocket/Server.h index 239a3acf2c..aee44b2c47 100644 --- a/src/ripple/websocket/Server.h +++ b/src/ripple/websocket/Server.h @@ -60,10 +60,7 @@ private: void run () override { WriteLog (lsWARNING, WebSocket) - << "Websocket: '" << desc_.port.name - << "' creating endpoint " << desc_.port.ip.to_string() - << ":" << std::to_string(desc_.port.port) - << (desc_.port.allow_admin ? "(Admin)" : ""); + << "Websocket: creating endpoint " << desc_.port; auto handler = WebSocket::makeHandler (desc_); { @@ -72,10 +69,7 @@ private: } WriteLog (lsWARNING, WebSocket) - << "Websocket: '" << desc_.port.name - << "' listening on " << desc_.port.ip.to_string() - << ":" << std::to_string(desc_.port.port) - << (desc_.port.allow_admin ? "(Admin)" : ""); + << "Websocket: listening on " << desc_.port; listen(); { @@ -84,26 +78,17 @@ private: } WriteLog (lsWARNING, WebSocket) - << "Websocket: '" << desc_.port.name - << "' finished listening on " << desc_.port.ip.to_string() - << ":" << std::to_string(desc_.port.port) - << (desc_.port.allow_admin ? "(Admin)" : ""); + << "Websocket: finished listening on " << desc_.port; stopped (); WriteLog (lsWARNING, WebSocket) - << "Websocket: '" << desc_.port.name - << "' stopped on " << desc_.port.ip.to_string() - << ":" << std::to_string(desc_.port.port) - << (desc_.port.allow_admin ? "(Admin)" : ""); + << "Websocket: stopped on " << desc_.port; } void onStop () override { WriteLog (lsWARNING, WebSocket) - << "Websocket: '" << desc_.port.name - << "' onStop " << desc_.port.ip.to_string() - << ":" << std::to_string(desc_.port.port) - << (desc_.port.allow_admin ? "(Admin)" : ""); + << "Websocket: onStop " << desc_.port; typename WebSocket::EndpointPtr endpoint; {