mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-28 06:55:50 +00:00
Apply resource limits to proxied clients:
Resource limits were not properly applied to connections with known IP addresses but no corresponding users. Add unit tests for unlimited vs. limited ports.
This commit is contained in:
committed by
Nik Bougalis
parent
872478d965
commit
504b3441dd
@@ -26,6 +26,7 @@
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<void (Json::Value const& jvInput)> callbackFuncP = std::function<void (Json::Value const& jvInput)> ());
|
||||
std::function<void (Json::Value const& jvInput)> callbackFuncP = std::function<void (Json::Value const& jvInput)> (),
|
||||
std::unordered_map<std::string, std::string> headers = {});
|
||||
}
|
||||
|
||||
/** Given a rippled command line, return the corresponding JSON.
|
||||
@@ -64,7 +66,8 @@ cmdLineToJSONRPC (std::vector<std::string> const& args, beast::Journal j);
|
||||
*/
|
||||
std::pair<int, Json::Value>
|
||||
rpcClient(std::vector<std::string> const& args,
|
||||
Config const& config, Logs& logs);
|
||||
Config const& config, Logs& logs,
|
||||
std::unordered_map<std::string, std::string> const& headers = {});
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -60,7 +61,7 @@ std::string createHTTPPost (
|
||||
std::string const& strHost,
|
||||
std::string const& strPath,
|
||||
std::string const& strMsg,
|
||||
std::map<std::string, std::string> const& mapRequestHeaders)
|
||||
std::unordered_map<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string> 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<std::string> const& args, beast::Journal j)
|
||||
|
||||
std::pair<int, Json::Value>
|
||||
rpcClient(std::vector<std::string> const& args,
|
||||
Config const& config, Logs& logs)
|
||||
Config const& config, Logs& logs,
|
||||
std::unordered_map<std::string, std::string> const& headers)
|
||||
{
|
||||
static_assert(rpcBAD_SYNTAX == 1 && rpcSUCCESS == 0,
|
||||
"Expect specific rpc enum values.");
|
||||
@@ -1415,7 +1422,8 @@ rpcClient(std::vector<std::string> 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<void (Json::Value const& jvInput)> callbackFuncP)
|
||||
std::function<void (Json::Value const& jvInput)> callbackFuncP,
|
||||
std::unordered_map<std::string, std::string> headers)
|
||||
{
|
||||
auto j = logs.journal ("HTTPClient");
|
||||
|
||||
@@ -1511,11 +1520,8 @@ void fromNetwork (
|
||||
}
|
||||
|
||||
// HTTP basic authentication
|
||||
auto const auth = base64_encode(strUsername + ":" + strPassword);
|
||||
|
||||
std::map<std::string, std::string> 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <ripple/beast/net/IPEndpoint.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/beast/utility/PropertyStream.h>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<std::recursive_mutex> _(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;
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/beast/core/CurrentThreadName.h>
|
||||
#include <ripple/beast/net/IPAddressConversion.h>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,10 +20,13 @@
|
||||
#ifndef RIPPLE_SERVER_ROLE_H_INCLUDED
|
||||
#define RIPPLE_SERVER_ROLE_H_INCLUDED
|
||||
|
||||
#include <ripple/server/Port.h>
|
||||
#include <ripple/beast/net/IPEndpoint.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/resource/ResourceManager.h>
|
||||
#include <ripple/beast/net/IPEndpoint.h>
|
||||
#include <ripple/server/Handoff.h>
|
||||
#include <ripple/server/Port.h>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<beast::IP::Address> const& adminIp);
|
||||
|
||||
boost::string_view
|
||||
forwardedFor(http_request_type const& request);
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <ripple/resource/ResourceManager.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/rpc/RPCHandler.h>
|
||||
#include <ripple/server/SimpleWriter.h>
|
||||
#include <boost/beast/http/fields.hpp>
|
||||
@@ -199,11 +200,14 @@ ServerHandlerImp::onHandoff(
|
||||
}
|
||||
|
||||
auto is {std::make_shared<WSInfoSub>(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<Session> 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<JobQueue::Coro> 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();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <ripple/rpc/RPCHandler.h>
|
||||
#include <ripple/app/main/CollectorManager.h>
|
||||
#include <ripple/json/Output.h>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
@@ -180,7 +181,7 @@ private:
|
||||
processRequest (Port const& port, std::string const& request,
|
||||
beast::IP::Endpoint const& remoteIPAddress, Output&&,
|
||||
std::shared_ptr<JobQueue::Coro> coro,
|
||||
std::string forwardedFor, std::string user);
|
||||
boost::string_view forwardedFor, boost::string_view user);
|
||||
|
||||
Handoff
|
||||
statusResponse(http_request_type const& request) const;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <ripple/beast/net/IPAddressConversion.h>
|
||||
#include <ripple/json/json_writer.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -273,6 +273,12 @@ public:
|
||||
The command is examined and used to build
|
||||
the correct JSON as per the arguments.
|
||||
*/
|
||||
template<class... Args>
|
||||
Json::Value
|
||||
rpc(std::unordered_map<std::string, std::string> const& headers,
|
||||
std::string const& cmd,
|
||||
Args&&... args);
|
||||
|
||||
template<class... Args>
|
||||
Json::Value
|
||||
rpc(std::string const& cmd, Args&&... args);
|
||||
@@ -641,7 +647,8 @@ protected:
|
||||
TER ter_ = tesSUCCESS;
|
||||
|
||||
Json::Value
|
||||
do_rpc(std::vector<std::string> const& args);
|
||||
do_rpc(std::vector<std::string> const& args,
|
||||
std::unordered_map<std::string, std::string> const& headers = {});
|
||||
|
||||
void
|
||||
autofill_sig (JTx& jt);
|
||||
@@ -734,13 +741,22 @@ protected:
|
||||
AccountID, Account> map_;
|
||||
};
|
||||
|
||||
template<class... Args>
|
||||
Json::Value
|
||||
Env::rpc(std::unordered_map<std::string, std::string> const& headers,
|
||||
std::string const& cmd,
|
||||
Args&&... args)
|
||||
{
|
||||
return do_rpc(std::vector<std::string>{cmd, std::forward<Args>(args)...},
|
||||
headers);
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
Json::Value
|
||||
Env::rpc(std::string const& cmd, Args&&... args)
|
||||
{
|
||||
std::vector<std::string> vs{cmd,
|
||||
std::forward<Args>(args)...};
|
||||
return do_rpc(vs);
|
||||
return rpc(std::unordered_map<std::string, std::string>(), cmd,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // jtx
|
||||
|
||||
@@ -47,7 +47,8 @@ public:
|
||||
|
||||
/** Returns a client operating through WebSockets/S. */
|
||||
std::unique_ptr<WSClient>
|
||||
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<std::string, std::string> const& headers = {});
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -86,6 +86,9 @@ envconfig(F&& modfunc, Args&&... args)
|
||||
std::unique_ptr<Config>
|
||||
no_admin(std::unique_ptr<Config>);
|
||||
|
||||
std::unique_ptr<Config>
|
||||
secure_gateway(std::unique_ptr<Config>);
|
||||
|
||||
/// @brief adjust configuration with params needed to be a validator
|
||||
///
|
||||
/// this is intended for use with envconfig, as in
|
||||
|
||||
@@ -442,9 +442,10 @@ Env::st (JTx const& jt)
|
||||
}
|
||||
|
||||
Json::Value
|
||||
Env::do_rpc(std::vector<std::string> const& args)
|
||||
Env::do_rpc(std::vector<std::string> const& args,
|
||||
std::unordered_map<std::string, std::string> const& headers)
|
||||
{
|
||||
return rpcClient(args, app().config(), app().logs()).second;
|
||||
return rpcClient(args, app().config(), app().logs(), headers).second;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
#include <boost/beast/websocket.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <ripple/beast/unit_test.h>
|
||||
|
||||
@@ -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<std::string, std::string> 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<WSClient>
|
||||
makeWSClient(Config const& cfg, bool v2, unsigned rpc_version)
|
||||
makeWSClient(Config const& cfg, bool v2, unsigned rpc_version,
|
||||
std::unordered_map<std::string, std::string> const& headers)
|
||||
{
|
||||
return std::make_unique<WSClientImpl>(cfg, v2, rpc_version);
|
||||
return std::make_unique<WSClientImpl>(cfg, v2, rpc_version, headers);
|
||||
}
|
||||
|
||||
} // test
|
||||
|
||||
@@ -72,6 +72,15 @@ no_admin(std::unique_ptr<Config> cfg)
|
||||
return cfg;
|
||||
}
|
||||
|
||||
std::unique_ptr<Config>
|
||||
secure_gateway(std::unique_ptr<Config> 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<Config>
|
||||
|
||||
@@ -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<Consumer(beast::IP::Endpoint)> 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);
|
||||
|
||||
113
src/test/rpc/Roles_test.cpp
Normal file
113
src/test/rpc/Roles_test.cpp
Normal file
@@ -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 <ripple/beast/unit_test.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <test/jtx.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
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<std::string, std::string> 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
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <test/rpc/OwnerInfo_test.cpp>
|
||||
#include <test/rpc/Peers_test.cpp>
|
||||
#include <test/rpc/RobustTransaction_test.cpp>
|
||||
#include <test/rpc/Roles_test.cpp>
|
||||
#include <test/rpc/RPCCall_test.cpp>
|
||||
#include <test/rpc/RPCOverload_test.cpp>
|
||||
#include <test/rpc/ServerInfo_test.cpp>
|
||||
|
||||
Reference in New Issue
Block a user