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:
Mark Travis
2019-02-02 02:54:10 -08:00
committed by Nik Bougalis
parent 872478d965
commit 504b3441dd
26 changed files with 399 additions and 197 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
/**

View File

@@ -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:

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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&params, 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 {};
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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
View 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

View File

@@ -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>