diff --git a/src/ripple/net/RPCCall.h b/src/ripple/net/RPCCall.h index b39b1ab4e..0f9af52be 100644 --- a/src/ripple/net/RPCCall.h +++ b/src/ripple/net/RPCCall.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -52,7 +53,8 @@ void fromNetwork ( std::string const& strPath, std::string const& strMethod, Json::Value const& jvParams, const bool bSSL, bool quiet, Logs& logs, - std::function callbackFuncP = std::function ()); + std::function callbackFuncP = std::function (), + std::unordered_map headers = {}); } /** Given a rippled command line, return the corresponding JSON. @@ -64,7 +66,8 @@ cmdLineToJSONRPC (std::vector const& args, beast::Journal j); */ std::pair rpcClient(std::vector const& args, - Config const& config, Logs& logs); + Config const& config, Logs& logs, + std::unordered_map const& headers = {}); } // ripple diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index c0842a8e8..d2590e1ab 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace ripple { @@ -60,7 +61,7 @@ std::string createHTTPPost ( std::string const& strHost, std::string const& strPath, std::string const& strMsg, - std::map const& mapRequestHeaders) + std::unordered_map const& mapRequestHeaders) { std::ostringstream s; @@ -1264,9 +1265,14 @@ struct RPCCallImp } // Build the request. - static void onRequest (std::string const& strMethod, Json::Value const& jvParams, - const std::map& mHeaders, std::string const& strPath, - boost::asio::streambuf& sb, std::string const& strHost, beast::Journal j) + static void onRequest ( + std::string const& strMethod, + Json::Value const& jvParams, + std::unordered_map const& headers, + std::string const& strPath, + boost::asio::streambuf& sb, + std::string const& strHost, + beast::Journal j) { JLOG (j.debug()) << "requestRPC: strPath='" << strPath << "'"; @@ -1276,7 +1282,7 @@ struct RPCCallImp strHost, strPath, JSONRPCRequest (strMethod, jvParams, Json::Value (1)), - mHeaders); + headers); } }; @@ -1338,7 +1344,8 @@ cmdLineToJSONRPC (std::vector const& args, beast::Journal j) std::pair rpcClient(std::vector const& args, - Config const& config, Logs& logs) + Config const& config, Logs& logs, + std::unordered_map const& headers) { static_assert(rpcBAD_SYNTAX == 1 && rpcSUCCESS == 0, "Expect specific rpc enum values."); @@ -1415,7 +1422,8 @@ rpcClient(std::vector const& args, config.quiet(), logs, std::bind (RPCCallImp::callRPCHandler, &jvOutput, - std::placeholders::_1)); + std::placeholders::_1), + headers); isService.run(); // This blocks until there is no more outstanding async calls. } if (jvOutput.isMember ("result")) @@ -1499,7 +1507,8 @@ void fromNetwork ( std::string const& strPath, std::string const& strMethod, Json::Value const& jvParams, const bool bSSL, const bool quiet, Logs& logs, - std::function callbackFuncP) + std::function callbackFuncP, + std::unordered_map headers) { auto j = logs.journal ("HTTPClient"); @@ -1511,11 +1520,8 @@ void fromNetwork ( } // HTTP basic authentication - auto const auth = base64_encode(strUsername + ":" + strPassword); - - std::map mapRequestHeaders; - - mapRequestHeaders["Authorization"] = std::string ("Basic ") + auth; + headers["Authorization"] = std::string("Basic ") + base64_encode( + strUsername + ":" + strPassword); // Send request @@ -1535,7 +1541,7 @@ void fromNetwork ( &RPCCallImp::onRequest, strMethod, jvParams, - mapRequestHeaders, + headers, strPath, std::placeholders::_1, std::placeholders::_2, j), RPC_REPLY_MAX_BYTES, RPC_NOTIFY, diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index c7aa2e95e..c70d4a7d0 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -351,6 +351,7 @@ JSS ( proof ); // in: BookOffers JSS ( propose_seq ); // out: LedgerPropose JSS ( proposers ); // out: NetworkOPs, LedgerConsensus JSS ( protocol ); // out: PeerImp +JSS ( proxied ); // out: RPC ping JSS ( pubkey_node ); // out: NetworkOPs JSS ( pubkey_publisher ); // out: ValidatorList JSS ( pubkey_validator ); // out: NetworkOPs, ValidatorList diff --git a/src/ripple/resource/ResourceManager.h b/src/ripple/resource/ResourceManager.h index 9c15e3b05..fbe4f5f70 100644 --- a/src/ripple/resource/ResourceManager.h +++ b/src/ripple/resource/ResourceManager.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace ripple { namespace Resource { @@ -40,14 +41,18 @@ protected: public: virtual ~Manager() = 0; - /** Create a new endpoint keyed by inbound IP address. */ + /** Create a new endpoint keyed by inbound IP address or the forwarded + * IP if proxied. */ virtual Consumer newInboundEndpoint (beast::IP::Endpoint const& address) = 0; + virtual Consumer newInboundEndpoint (beast::IP::Endpoint const& address, + bool const proxy, boost::string_view const& forwardedFor) = 0; /** Create a new endpoint keyed by outbound IP address and port. */ virtual Consumer newOutboundEndpoint (beast::IP::Endpoint const& address) = 0; - /** Create a new endpoint keyed by name. */ - virtual Consumer newUnlimitedEndpoint (std::string const& name) = 0; + /** Create a new unlimited endpoint keyed by forwarded IP. */ + virtual Consumer newUnlimitedEndpoint ( + beast::IP::Endpoint const& address) = 0; /** Extract packaged consumer information for export. */ virtual Gossip exportConsumers () = 0; diff --git a/src/ripple/resource/impl/Consumer.cpp b/src/ripple/resource/impl/Consumer.cpp index fd8127c0b..6741ff6fb 100644 --- a/src/ripple/resource/impl/Consumer.cpp +++ b/src/ripple/resource/impl/Consumer.cpp @@ -99,7 +99,7 @@ Disposition Consumer::charge (Charge const& what) { Disposition d = ok; - if (m_logic && m_entry) + if (m_logic && m_entry && !m_entry->isUnlimited()) d = m_logic->charge (*m_entry, what); return d; diff --git a/src/ripple/resource/impl/Entry.h b/src/ripple/resource/impl/Entry.h index 88e079be4..5d7d102bd 100644 --- a/src/ripple/resource/impl/Entry.h +++ b/src/ripple/resource/impl/Entry.h @@ -53,16 +53,7 @@ struct Entry std::string to_string() const { - switch (key->kind) - { - case kindInbound: return key->address.to_string(); - case kindOutbound: return key->address.to_string(); - case kindUnlimited: return std::string ("\"") + key->name + "\""; - default: - assert(false); - } - - return "(undefined)"; + return key->address.to_string(); } /** diff --git a/src/ripple/resource/impl/Key.h b/src/ripple/resource/impl/Key.h index 2d7d2a267..6ceb60f82 100644 --- a/src/ripple/resource/impl/Key.h +++ b/src/ripple/resource/impl/Key.h @@ -32,47 +32,23 @@ struct Key { Kind kind; beast::IP::Endpoint address; - std::string name; Key () = delete; - // Constructor for Inbound and Outbound (non-Unlimited) keys Key (Kind k, beast::IP::Endpoint const& addr) : kind(k) , address(addr) - { - assert(kind != kindUnlimited); - } - - // Constructor for Unlimited keys - Key (std::string const& n) - : kind(kindUnlimited) - , name(n) {} struct hasher { std::size_t operator() (Key const& v) const { - switch (v.kind) - { - case kindInbound: - case kindOutbound: - return m_addr_hash (v.address); - - case kindUnlimited: - return m_name_hash (v.name); - - default: - assert(false); - }; - - return 0; + return m_addr_hash (v.address); } private: beast::uhash <> m_addr_hash; - beast::uhash <> m_name_hash; }; struct key_equal @@ -81,23 +57,7 @@ struct Key bool operator() (Key const& lhs, Key const& rhs) const { - if (lhs.kind != rhs.kind) - return false; - - switch (lhs.kind) - { - case kindInbound: - case kindOutbound: - return lhs.address == rhs.address; - - case kindUnlimited: - return lhs.name == rhs.name; - - default: - assert(false); - }; - - return false; + return lhs.kind == rhs.kind && lhs.address == rhs.address; } private: diff --git a/src/ripple/resource/impl/Logic.h b/src/ripple/resource/impl/Logic.h index 27249ddc8..3889550e0 100644 --- a/src/ripple/resource/impl/Logic.h +++ b/src/ripple/resource/impl/Logic.h @@ -172,7 +172,7 @@ public: * restrictions, such as permission to perform certain RPC calls, may be * enabled. */ - Consumer newUnlimitedEndpoint (std::string const& name) + Consumer newUnlimitedEndpoint (beast::IP::Endpoint const& address) { Entry* entry (nullptr); @@ -180,7 +180,7 @@ public: std::lock_guard _(lock_); auto result = table_.emplace (std::piecewise_construct, - std::make_tuple (name), // Key + std::make_tuple (kindUnlimited, address.at_port(1)),// Key std::make_tuple (m_clock.now())); // Entry entry = &result.first->second; diff --git a/src/ripple/resource/impl/ResourceManager.cpp b/src/ripple/resource/impl/ResourceManager.cpp index 8e1fd6149..6c550e2c4 100644 --- a/src/ripple/resource/impl/ResourceManager.cpp +++ b/src/ripple/resource/impl/ResourceManager.cpp @@ -22,7 +22,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -46,7 +49,6 @@ public: : journal_ (journal) , logic_ (collector, stopwatch(), journal) { - boost::ignore_unused (journal_); // Keep unused journal_ just in case. thread_ = std::thread {&ManagerImp::run, this}; } @@ -69,14 +71,37 @@ public: return logic_.newInboundEndpoint (address); } + Consumer newInboundEndpoint (beast::IP::Endpoint const& address, + bool const proxy, boost::string_view const& forwardedFor) override + { + if (! proxy) + return newInboundEndpoint(address); + + boost::system::error_code ec; + auto const proxiedIp = boost::asio::ip::make_address( + forwardedFor.to_string(), ec); + if (ec) + { + journal_.warn() << "forwarded for (" + << forwardedFor + << ") from proxy " + << address.to_string() + << " doesn't convert to IP endpoint: " + << ec.message(); + return newInboundEndpoint(address); + } + return newInboundEndpoint( + beast::IPAddressConversion::from_asio(proxiedIp)); + } + Consumer newOutboundEndpoint (beast::IP::Endpoint const& address) override { return logic_.newOutboundEndpoint (address); } - Consumer newUnlimitedEndpoint (std::string const& name) override + Consumer newUnlimitedEndpoint (beast::IP::Endpoint const& address) override { - return logic_.newUnlimitedEndpoint (name); + return logic_.newUnlimitedEndpoint (address); } Gossip exportConsumers () override diff --git a/src/ripple/rpc/Context.h b/src/ripple/rpc/Context.h index d545dcd4b..20a8a574e 100644 --- a/src/ripple/rpc/Context.h +++ b/src/ripple/rpc/Context.h @@ -43,8 +43,8 @@ struct Context */ struct Headers { - std::string user; - std::string forwardedFor; + boost::string_view user; + boost::string_view forwardedFor; }; beast::Journal j; diff --git a/src/ripple/rpc/Role.h b/src/ripple/rpc/Role.h index 5a1a7d4df..29e0c12a4 100644 --- a/src/ripple/rpc/Role.h +++ b/src/ripple/rpc/Role.h @@ -20,10 +20,13 @@ #ifndef RIPPLE_SERVER_ROLE_H_INCLUDED #define RIPPLE_SERVER_ROLE_H_INCLUDED -#include +#include #include #include -#include +#include +#include +#include +#include #include namespace ripple { @@ -40,25 +43,27 @@ enum class Role USER, IDENTIFIED, ADMIN, + PROXY, FORBID }; /** Return the allowed privilege role. - jsonRPC must meet the requirements of the JSON-RPC + params must meet the requirements of the JSON-RPC specification. It must be of type Object, containing the key params which is an array with at least one object. Inside this object are the optional keys 'admin_user' and 'admin_password' used to - validate the credentials. + validate the credentials. If user is non-blank, it's username + passed in the HTTP header by a secure_gateway proxy. */ Role requestRole (Role const& required, Port const& port, - Json::Value const& jsonRPC, beast::IP::Endpoint const& remoteIp, - std::string const& user); + Json::Value const& params, beast::IP::Endpoint const& remoteIp, + boost::string_view const& user); Resource::Consumer requestInboundEndpoint (Resource::Manager& manager, - beast::IP::Endpoint const& remoteAddress, - Port const& port, std::string const& user); + beast::IP::Endpoint const& remoteAddress, Role const& role, + boost::string_view const& user, boost::string_view const& forwardedFor); /** * Check if the role entitles the user to unlimited resources. @@ -67,12 +72,18 @@ bool isUnlimited (Role const& role); /** - * If the HTTP header X-User exists with a non-empty value was passed by an IP - * configured as secure_gateway, then the user can be positively identified. + * True if remoteIp is in any of adminIp + * + * @param remoteIp Remote address for which to search. + * @param adminIp List of IP's in which to search. + * @return Whether remoteIp is in adminIp. */ bool -isIdentified (Port const& port, beast::IP::Address const& remoteIp, - std::string const& user); +ipAllowed (beast::IP::Address const& remoteIp, + std::vector const& adminIp); + +boost::string_view +forwardedFor(http_request_type const& request); } // ripple diff --git a/src/ripple/rpc/handlers/Ping.cpp b/src/ripple/rpc/handlers/Ping.cpp index 8cb5a8159..e0a15c9cb 100644 --- a/src/ripple/rpc/handlers/Ping.cpp +++ b/src/ripple/rpc/handlers/Ping.cpp @@ -30,29 +30,33 @@ struct Context; Json::Value doPing (RPC::Context& context) { - // For testing connection privileges. - if (isUnlimited(context.role)) + Json::Value ret(Json::objectValue); + switch (context.role) { - Json::Value ret; - - switch (context.role) - { - case Role::ADMIN: - ret[jss::role] = "admin"; - break; - case Role::IDENTIFIED: - ret[jss::role] = "identified"; - break; - default: - ; - } - - return ret; + case Role::ADMIN: + ret[jss::role] = "admin"; + break; + case Role::IDENTIFIED: + ret[jss::role] = "identified"; + ret[jss::username] = context.headers.user.to_string(); + if (context.headers.forwardedFor.size()) + ret[jss::ip] = context.headers.forwardedFor.to_string(); + break; + case Role::PROXY: + ret[jss::role] = "proxied"; + ret[jss::ip] = context.headers.forwardedFor.to_string(); + default: + ; } - else + + // This is only accessible on ws sessions. + if (context.infoSub) { - return Json::Value (Json::objectValue); + if (context.infoSub->getConsumer().isUnlimited()) + ret[jss::unlimited] = true; } + + return ret; } } // ripple diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index 1afc1c4c1..6c8fd3812 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -276,13 +276,13 @@ Status doCommand ( ! context.headers.forwardedFor.empty()) { JLOG(context.j.debug()) << "start command: " << handler->name_ << - ", X-User: " << context.headers.user << ", X-Forwarded-For: " << + ", user: " << context.headers.user << ", forwarded for: " << context.headers.forwardedFor; auto ret = callMethod (context, method, handler->name_, result); JLOG(context.j.debug()) << "finish command: " << handler->name_ << - ", X-User: " << context.headers.user << ", X-Forwarded-For: " << + ", user: " << context.headers.user << ", forwarded for: " << context.headers.forwardedFor; return ret; diff --git a/src/ripple/rpc/impl/Role.cpp b/src/ripple/rpc/impl/Role.cpp index b515ef08b..b4b8ca016 100644 --- a/src/ripple/rpc/impl/Role.cpp +++ b/src/ripple/rpc/impl/Role.cpp @@ -18,6 +18,8 @@ //============================================================================== #include +#include +#include namespace ripple { @@ -56,7 +58,7 @@ isAdmin (Port const& port, Json::Value const& params, Role requestRole (Role const& required, Port const& port, Json::Value const& params, beast::IP::Endpoint const& remoteIp, - std::string const& user) + boost::string_view const& user) { if (isAdmin(port, params, remoteIp.address())) return Role::ADMIN; @@ -64,8 +66,12 @@ requestRole (Role const& required, Port const& port, if (required == Role::ADMIN) return Role::FORBID; - if (isIdentified(port, remoteIp.address(), user)) - return Role::IDENTIFIED; + if (ipAllowed(remoteIp.address(), port.secure_gateway_ip)) + { + if (user.size()) + return Role::IDENTIFIED; + return Role::PROXY; + } return Role::GUEST; } @@ -73,41 +79,66 @@ requestRole (Role const& required, Port const& port, /** * ADMIN and IDENTIFIED roles shall have unlimited resources. */ -bool -isUnlimited (Role const& required, Port const& port, - Json::Value const¶ms, beast::IP::Endpoint const& remoteIp, - std::string const& user) -{ - Role role = requestRole(required, port, params, remoteIp, user); - - if (role == Role::ADMIN || role == Role::IDENTIFIED) - return true; - else - return false; -} - bool isUnlimited (Role const& role) { return role == Role::ADMIN || role == Role::IDENTIFIED; } +bool +isUnlimited (Role const& required, Port const& port, + Json::Value const& params, beast::IP::Endpoint const& remoteIp, + std::string const& user) +{ + return isUnlimited(requestRole(required, port, params, remoteIp, user)); +} + Resource::Consumer requestInboundEndpoint (Resource::Manager& manager, - beast::IP::Endpoint const& remoteAddress, - Port const& port, std::string const& user) + beast::IP::Endpoint const& remoteAddress, Role const& role, + boost::string_view const& user, boost::string_view const& forwardedFor) { - if (isUnlimited (Role::GUEST, port, Json::Value(), remoteAddress, user)) - return manager.newUnlimitedEndpoint (to_string (remoteAddress)); + if (isUnlimited(role)) + return manager.newUnlimitedEndpoint (remoteAddress); - return manager.newInboundEndpoint(remoteAddress); + return manager.newInboundEndpoint(remoteAddress, role == Role::PROXY, + forwardedFor); } -bool -isIdentified (Port const& port, beast::IP::Address const& remoteIp, - std::string const& user) +boost::string_view +forwardedFor(http_request_type const& request) { - return ! user.empty() && ipAllowed (remoteIp, port.secure_gateway_ip); + auto it = request.find("X-Forwarded-For"); + if (it != request.end()) + { + return boost::beast::http::ext_list{ + it->value()}.begin()->first; + } + + it = request.find("Forwarded"); + if (it != request.end()) + { + static std::string const forStr{"for="}; + auto found = std::search(it->value().begin(), it->value().end(), + forStr.begin(), forStr.end(), + [](char c1, char c2) + { + return boost::beast::detail::ascii_tolower(c1) == + boost::beast::detail::ascii_tolower(c2); + } + ); + + if (found == it->value().end()) + return {}; + + found += forStr.size(); + auto pos{it->value().find(';', forStr.size())}; + if (pos != boost::string_view::npos) + return {found, pos + 1}; + return {found, it->value().size() - forStr.size()}; + } + + return {}; } } diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 0a1966358..1e8043311 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -199,11 +200,14 @@ ServerHandlerImp::onHandoff( } auto is {std::make_shared(m_networkOPs, ws)}; + auto const beast_remote_address = + beast::IPAddressConversion::from_asio(remote_address); is->getConsumer() = requestInboundEndpoint( - m_resourceManager, - beast::IPAddressConversion::from_asio(remote_address), - session.port(), - is->user()); + m_resourceManager, beast_remote_address, + requestRole(Role::GUEST, session.port(), Json::Value(), + beast_remote_address, is->user()), + is->user(), + is->forwarded_for()); ws->appDefined = std::move(is); ws->run(); @@ -489,12 +493,6 @@ ServerHandlerImp::processSession( else { jr[jss::status] = jss::success; - - // For testing resource limits on this connection. - if (is->getConsumer().isUnlimited() && - jv[jss::command].isString() && - jv[jss::command].asString() == "ping") - jr[jss::unlimited] = true; } if (jv.isMember(jss::id)) @@ -517,23 +515,14 @@ ServerHandlerImp::processSession (std::shared_ptr const& session, session->request().body().data()), session->remoteAddress().at_port (0), makeOutput (*session), coro, - [&] - { - auto const iter = - session->request().find( - "X-Forwarded-For"); - if(iter != session->request().end()) - return iter->value().to_string(); - return std::string{}; - }(), - [&] - { + forwardedFor(session->request()), + [&]{ auto const iter = session->request().find( "X-User"); if(iter != session->request().end()) - return iter->value().to_string(); - return std::string{}; + return iter->value(); + return boost::beast::string_view{}; }()); if(beast::rfc2616::is_keep_alive(session->request())) @@ -562,7 +551,7 @@ void ServerHandlerImp::processRequest (Port const& port, std::string const& request, beast::IP::Endpoint const& remoteIPAddress, Output&& output, std::shared_ptr coro, - std::string forwardedFor, std::string user) + boost::string_view forwardedFor, boost::string_view user) { auto rpcJ = app_.journal ("RPC"); @@ -636,12 +625,12 @@ ServerHandlerImp::processRequest (Port const& port, Resource::Consumer usage; if (isUnlimited(role)) { - usage = m_resourceManager.newUnlimitedEndpoint( - remoteIPAddress.to_string()); + usage = m_resourceManager.newUnlimitedEndpoint(remoteIPAddress); } else { - usage = m_resourceManager.newInboundEndpoint(remoteIPAddress); + usage = m_resourceManager.newInboundEndpoint(remoteIPAddress, + role == Role::PROXY, forwardedFor); if (usage.disconnect()) { if (!batch) @@ -774,7 +763,7 @@ ServerHandlerImp::processRequest (Port const& port, * Clear header-assigned values if not positively identified from a * secure_gateway. */ - if (role != Role::IDENTIFIED) + if (role != Role::IDENTIFIED && role != Role::PROXY) { forwardedFor.clear(); user.clear(); diff --git a/src/ripple/rpc/impl/ServerHandlerImp.h b/src/ripple/rpc/impl/ServerHandlerImp.h index 192eaeaf8..3e5508a8b 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.h +++ b/src/ripple/rpc/impl/ServerHandlerImp.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -180,7 +181,7 @@ private: processRequest (Port const& port, std::string const& request, beast::IP::Endpoint const& remoteIPAddress, Output&&, std::shared_ptr coro, - std::string forwardedFor, std::string user); + boost::string_view forwardedFor, boost::string_view user); Handoff statusResponse(http_request_type const& request) const; diff --git a/src/ripple/rpc/impl/WSInfoSub.h b/src/ripple/rpc/impl/WSInfoSub.h index 48157cd2f..0d5038fcf 100644 --- a/src/ripple/rpc/impl/WSInfoSub.h +++ b/src/ripple/rpc/impl/WSInfoSub.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -42,26 +43,23 @@ public: , ws_(ws) { auto const& h = ws->request(); - auto it = h.find("X-User"); - if (it != h.end() && - isIdentified( - ws->port(), beast::IPAddressConversion::from_asio( - ws->remote_endpoint()).address(), it->value().to_string())) + if (ipAllowed(beast::IPAddressConversion::from_asio( + ws->remote_endpoint()).address(), ws->port().secure_gateway_ip)) { - user_ = it->value().to_string(); - it = h.find("X-Forwarded-For"); + auto it = h.find("X-User"); if (it != h.end()) - fwdfor_ = it->value().to_string(); + user_ = it->value().to_string(); + fwdfor_ = std::string(forwardedFor(h)); } } - std::string + boost::string_view user() const { return user_; } - std::string + boost::string_view forwarded_for() const { return fwdfor_; diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 228ebade5..8ac47c160 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -273,6 +273,12 @@ public: The command is examined and used to build the correct JSON as per the arguments. */ + template + Json::Value + rpc(std::unordered_map const& headers, + std::string const& cmd, + Args&&... args); + template Json::Value rpc(std::string const& cmd, Args&&... args); @@ -641,7 +647,8 @@ protected: TER ter_ = tesSUCCESS; Json::Value - do_rpc(std::vector const& args); + do_rpc(std::vector const& args, + std::unordered_map const& headers = {}); void autofill_sig (JTx& jt); @@ -734,13 +741,22 @@ protected: AccountID, Account> map_; }; +template +Json::Value +Env::rpc(std::unordered_map const& headers, + std::string const& cmd, + Args&&... args) +{ + return do_rpc(std::vector{cmd, std::forward(args)...}, + headers); +} + template Json::Value Env::rpc(std::string const& cmd, Args&&... args) { - std::vector vs{cmd, - std::forward(args)...}; - return do_rpc(vs); + return rpc(std::unordered_map(), cmd, + std::forward(args)...); } } // jtx diff --git a/src/test/jtx/WSClient.h b/src/test/jtx/WSClient.h index a6a823f67..882629504 100644 --- a/src/test/jtx/WSClient.h +++ b/src/test/jtx/WSClient.h @@ -47,7 +47,8 @@ public: /** Returns a client operating through WebSockets/S. */ std::unique_ptr -makeWSClient(Config const& cfg, bool v2 = true, unsigned rpc_version = 2); +makeWSClient(Config const& cfg, bool v2 = true, unsigned rpc_version = 2, + std::unordered_map const& headers = {}); } // test } // ripple diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index d265678a8..93d0c21a6 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -86,6 +86,9 @@ envconfig(F&& modfunc, Args&&... args) std::unique_ptr no_admin(std::unique_ptr); +std::unique_ptr +secure_gateway(std::unique_ptr); + /// @brief adjust configuration with params needed to be a validator /// /// this is intended for use with envconfig, as in diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index d96c83966..21eecaad9 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -442,9 +442,10 @@ Env::st (JTx const& jt) } Json::Value -Env::do_rpc(std::vector const& args) +Env::do_rpc(std::vector const& args, + std::unordered_map const& headers) { - return rpcClient(args, app().config(), app().logs()).second; + return rpcClient(args, app().config(), app().logs(), headers).second; } void diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index b486ce576..3f72f4d80 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -27,6 +27,10 @@ #include #include +#include +#include + +#include #include @@ -125,7 +129,8 @@ class WSClientImpl : public WSClient } public: - WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version) + WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version, + std::unordered_map const& headers = {}) : work_(ios_) , strand_(ios_) , thread_([&]{ ios_.run(); }) @@ -137,8 +142,13 @@ public: { auto const ep = getEndpoint(cfg, v2); stream_.connect(ep); - ws_.handshake(ep.address().to_string() + - ":" + std::to_string(ep.port()), "/"); + ws_.handshake_ex(ep.address().to_string() + + ":" + std::to_string(ep.port()), "/", + [&](boost::beast::websocket::request_type &req) + { + for (auto const& h : headers) + req.set(h.first, h.second); + }); ws_.async_read(rb_, strand_.wrap(std::bind(&WSClientImpl::on_read_msg, this, std::placeholders::_1))); @@ -294,9 +304,10 @@ private: }; std::unique_ptr -makeWSClient(Config const& cfg, bool v2, unsigned rpc_version) +makeWSClient(Config const& cfg, bool v2, unsigned rpc_version, + std::unordered_map const& headers) { - return std::make_unique(cfg, v2, rpc_version); + return std::make_unique(cfg, v2, rpc_version, headers); } } // test diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 4de1066da..8365eb81c 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -72,6 +72,15 @@ no_admin(std::unique_ptr cfg) return cfg; } +std::unique_ptr +secure_gateway(std::unique_ptr cfg) +{ + (*cfg)["port_rpc"].set("admin", ""); + (*cfg)["port_ws"].set("admin",""); + (*cfg)["port_rpc"].set("secure_gateway", getEnvLocalhostAddr()); + return cfg; +} + auto constexpr defaultseed = "shUwVw52ofnCUX5m7kPTKzJdr4HEH"; std::unique_ptr diff --git a/src/test/resource/Logic_test.cpp b/src/test/resource/Logic_test.cpp index 45b84e78f..0a9ec128f 100644 --- a/src/test/resource/Logic_test.cpp +++ b/src/test/resource/Logic_test.cpp @@ -80,9 +80,12 @@ public: //-------------------------------------------------------------------------- - void testDrop (beast::Journal j) + void testDrop (beast::Journal j, bool limited) { - testcase ("Warn/drop"); + if (limited) + testcase("Limited warn/drop"); + else + testcase("Unlimited warn/drop"); TestLogic logic (j); @@ -90,8 +93,14 @@ public: beast::IP::Endpoint const addr ( beast::IP::Endpoint::from_string ("192.0.2.2")); + using namespace std::placeholders; + + std::function ep = limited ? + std::bind(&TestLogic::newInboundEndpoint, &logic, _1) : + std::bind(&TestLogic::newUnlimitedEndpoint, &logic, _1); + { - Consumer c (logic.newInboundEndpoint (addr)); + Consumer c (ep(addr)); // Create load until we get a warning int n = 10000; @@ -100,13 +109,19 @@ public: { if (n == 0) { - fail ("Loop count exceeded without warning"); + if (limited) + fail ("Loop count exceeded without warning"); + else + pass(); return; } if (c.charge (fee) == warn) { - pass (); + if (limited) + pass(); + else + fail ("Should loop forever with no warning"); break; } ++logic.clock (); @@ -117,14 +132,17 @@ public: { if (n == 0) { - fail ("Loop count exceeded without dropping"); + if (limited) + fail ("Loop count exceeded without dropping"); + else + pass(); return; } if (c.charge (fee) == drop) { // Disconnect abusive Consumer - BEAST_EXPECT(c.disconnect ()); + BEAST_EXPECT(c.disconnect () == limited); break; } ++logic.clock (); @@ -137,7 +155,10 @@ public: logic.periodicActivity(); if (c.disposition () != drop) { - fail ("Dropped consumer not put on blacklist"); + if (limited) + fail ("Dropped consumer not put on blacklist"); + else + pass(); return; } } @@ -250,7 +271,8 @@ public: using namespace beast::severities; test::SuiteJournal journal ("ResourceManager_test", *this); - testDrop (journal); + testDrop (journal, true); + testDrop (journal, false); testCharges (journal); testImports (journal); testImport (journal); diff --git a/src/test/rpc/Roles_test.cpp b/src/test/rpc/Roles_test.cpp new file mode 100644 index 000000000..3ab305d40 --- /dev/null +++ b/src/test/rpc/Roles_test.cpp @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +namespace test { + +class Roles_test : public beast::unit_test::suite +{ + void + testRoles() + { + using namespace test::jtx; + + { + Env env(*this); + + BEAST_EXPECT(env.rpc("ping")["result"]["role"] == "admin"); + BEAST_EXPECT(makeWSClient( + env.app().config())->invoke( + "ping")["result"]["unlimited"].asBool()); + } + { + Env env { *this, envconfig(no_admin) }; + + BEAST_EXPECT(! env.rpc("ping")["result"].isMember("role")); + auto wsRes = makeWSClient( + env.app().config())->invoke("ping")["result"]; + BEAST_EXPECT( + !wsRes.isMember("unlimited") || !wsRes["unlimited"].asBool()); + } + { + Env env { *this, envconfig(secure_gateway) }; + + BEAST_EXPECT(env.rpc("ping")["result"]["role"] == "proxied"); + auto wsRes = makeWSClient( + env.app().config())->invoke("ping")["result"]; + BEAST_EXPECT( + !wsRes.isMember("unlimited") || !wsRes["unlimited"].asBool()); + + std::unordered_map headers; + headers["X-Forwarded-For"] = "12.34.56.78"; + auto rpcRes = env.rpc(headers, "ping")["result"]; + BEAST_EXPECT(rpcRes["role"] == "proxied"); + BEAST_EXPECT(rpcRes["ip"] == "12.34.56.78"); + + headers["X-Forwarded-For"] = "87.65.43.21, 44.33.22.11"; + rpcRes = env.rpc(headers, "ping")["result"]; + BEAST_EXPECT(rpcRes["ip"] == "87.65.43.21"); + headers.erase("X-Forwarded-For"); + + headers["Forwarded"] = "for=88.77.66.55"; + rpcRes = env.rpc(headers, "ping")["result"]; + BEAST_EXPECT(rpcRes["ip"] == "88.77.66.55"); + + headers["Forwarded"] = "what=where;for=55.66.77.88;for=nobody;" + "who=3"; + rpcRes = env.rpc(headers, "ping")["result"]; + BEAST_EXPECT(rpcRes["ip"] == "55.66.77.88"); + + wsRes = makeWSClient( + env.app().config(), true, 2, headers)->invoke("ping")["result"]; + BEAST_EXPECT( + !wsRes.isMember("unlimited") || !wsRes["unlimited"].asBool()); + + std::string const name = "xrposhi"; + headers["X-User"] = name; + rpcRes = env.rpc(headers, "ping")["result"]; + BEAST_EXPECT(rpcRes["role"] == "identified"); + BEAST_EXPECT(rpcRes["username"] == name); + BEAST_EXPECT(rpcRes["ip"] == "55.66.77.88"); + wsRes = makeWSClient( + env.app().config(), true, 2, headers)->invoke("ping")["result"]; + BEAST_EXPECT(wsRes["unlimited"].asBool()); + } + } + +public: + void + run() override + { + testRoles(); + } +}; + +BEAST_DEFINE_TESTSUITE(Roles, app, ripple); + +} // namespace test + +} // namespace ripple diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index ff8dc138f..a3bc197c6 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include