Secure gateway:

This is designed for use by proxies in front of rippled. Configured IPs
can forward identifying user data in HTTP headers, including
user name and origin IP. If the user name exists, then resource limits
are lifted for that session. However, administrative commands are still
reserved only for administrative sessions.
This commit is contained in:
Mark Travis
2015-11-24 17:17:56 -08:00
committed by Nik Bougalis
parent 810175ae95
commit 496fea5995
47 changed files with 538 additions and 219 deletions

View File

@@ -3525,6 +3525,9 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\server\impl\PlainPeer.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\server\impl\Port.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\server\impl\Role.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>

View File

@@ -4167,6 +4167,9 @@
<ClInclude Include="..\..\src\ripple\server\impl\PlainPeer.h">
<Filter>ripple\server\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\server\impl\Port.cpp">
<Filter>ripple\server\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\server\impl\Role.cpp">
<Filter>ripple\server\impl</Filter>
</ClCompile>

View File

@@ -212,6 +212,29 @@
# in the submitted JSON for any administrative command requests when
# invoking JSON-RPC commands on remote servers.
#
# secure_gateway = [ IP, IP, IP, ... ]
#
# A comma-separated list of IP addresses.
#
# When set, allows the specified IP addresses to pass HTTP headers
# containing username and remote IP address for each session. If a
# non-empty username is passed in this way, then resource controls
# such as often resulting in "tooBusy" errors will be lifted. However,
# administrative RPC commands such as "stop" will not be allowed.
# The HTTP headers that secure_gateway hosts can set are X-User and
# X-Forwarded-For. Only the X-User header affects resource controls.
# However, both header values are logged to help identify user activity.
# If no X-User header is passed, or if its value is empty, then
# resource controls will default to those for non-administrative users.
#
# The secure_gateway IP addresses are intended to represent
# proxies. Since rippled trusts these hosts, they must be
# responsible for properly authenticating the remote user.
#
# The same IP address cannot be used in both "admin" and "secure_gateway"
# lists for the same port. In this case, rippled will abort with an error
# message to the console shortly after startup
#
# ssl_key = <filename>
# ssl_cert = <filename>
# ssl_chain = <filename>

View File

@@ -221,18 +221,18 @@ public:
void processTransaction (
std::shared_ptr<Transaction>& transaction,
bool bAdmin, bool bLocal, FailHard failType) override;
bool bUnlimited, bool bLocal, FailHard failType) override;
/**
* For transactions submitted directly by a client, apply batch of
* transactions and wait for this transaction to complete.
*
* @param transaction Transaction object.
* @param bAdmin Whether an administrative client connection submitted it.
* @param bUnliimited Whether a privileged client connection submitted it.
* @param failType fail_hard setting from transaction submission.
*/
void doTransactionSync (std::shared_ptr<Transaction> transaction,
bool bAdmin, FailHard failType);
bool bUnlimited, FailHard failType);
/**
* For transactions not submitted by a locally connected client, fire and
@@ -240,11 +240,11 @@ public:
* currently being applied.
*
* @param transaction Transaction object
* @param bAdmin Whether an administrative client connection submitted it.
* @param bUnlimited Whether a privileged client connection submitted it.
* @param failType fail_hard setting from transaction submission.
*/
void doTransactionAsync (std::shared_ptr<Transaction> transaction,
bool bAdmin, FailHard failtype);
bool bUnlimited, FailHard failtype);
/**
* Apply transactions in batches. Continue until none are queued.
@@ -270,7 +270,7 @@ public:
// Book functions.
//
void getBookPage (bool bAdmin, std::shared_ptr<ReadView const>& lpLedger,
void getBookPage (bool bUnlimited, std::shared_ptr<ReadView const>& lpLedger,
Book const&, AccountID const& uTakerID, const bool bProof,
const unsigned int iLimit,
Json::Value const& jvMarker, Json::Value& jvResult)
@@ -366,19 +366,19 @@ public:
std::string selection, AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger,
bool descending, std::uint32_t offset, int limit,
bool binary, bool count, bool bAdmin);
bool binary, bool count, bool bUnlimited);
// Client information retrieval functions.
using NetworkOPs::AccountTxs;
AccountTxs getAccountTxs (
AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin) override;
std::uint32_t offset, int limit, bool bUnlimited) override;
AccountTxs getTxsAccount (
AccountID const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token, int limit,
bool bAdmin) override;
bool bUnlimited) override;
using NetworkOPs::txnMetaLedgerType;
using NetworkOPs::MetaTxsList;
@@ -387,13 +387,13 @@ public:
getAccountTxsB (
AccountID const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool descending, std::uint32_t offset,
int limit, bool bAdmin) override;
int limit, bool bUnlimited) override;
MetaTxsList
getTxsAccountB (
AccountID const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token,
int limit, bool bAdmin) override;
int limit, bool bUnlimited) override;
//
// Monitoring: publisher side.
@@ -787,7 +787,7 @@ void NetworkOPsImp::submitTransaction (std::shared_ptr<STTx const> const& iTrans
}
void NetworkOPsImp::processTransaction (std::shared_ptr<Transaction>& transaction,
bool bAdmin, bool bLocal, FailHard failType)
bool bUnlimited, bool bLocal, FailHard failType)
{
auto ev = m_job_queue.getLoadEventAP (jtTXN_PROC, "ProcessTXN");
auto const newFlags = app_.getHashRouter ().getFlags (transaction->getID ());
@@ -826,20 +826,20 @@ void NetworkOPsImp::processTransaction (std::shared_ptr<Transaction>& transactio
app_.getMasterTransaction ().canonicalize (&transaction);
if (bLocal)
doTransactionSync (transaction, bAdmin, failType);
doTransactionSync (transaction, bUnlimited, failType);
else
doTransactionAsync (transaction, bAdmin, failType);
doTransactionAsync (transaction, bUnlimited, failType);
}
void NetworkOPsImp::doTransactionAsync (std::shared_ptr<Transaction> transaction,
bool bAdmin, FailHard failType)
bool bUnlimited, FailHard failType)
{
std::lock_guard<std::mutex> lock (mMutex);
if (transaction->getApplying())
return;
mTransactions.push_back (TransactionStatus (transaction, bAdmin, false,
mTransactions.push_back (TransactionStatus (transaction, bUnlimited, false,
failType));
transaction->setApplying();
@@ -852,14 +852,14 @@ void NetworkOPsImp::doTransactionAsync (std::shared_ptr<Transaction> transaction
}
void NetworkOPsImp::doTransactionSync (std::shared_ptr<Transaction> transaction,
bool bAdmin, FailHard failType)
bool bUnlimited, FailHard failType)
{
std::unique_lock<std::mutex> lock (mMutex);
if (! transaction->getApplying())
{
mTransactions.push_back (TransactionStatus (transaction, bAdmin, true,
failType));
mTransactions.push_back (TransactionStatus (transaction, bUnlimited,
true, failType));
transaction->setApplying();
}
@@ -925,7 +925,7 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
// we check before addingto the batch
ApplyFlags flags = tapNO_CHECK_SIGN;
if (e.admin)
flags = flags | tapADMIN;
flags = flags | tapUNLIMITED;
auto const result = app_.getTxQ().apply(
app_, view, e.transaction->getSTransaction(),
@@ -1690,7 +1690,7 @@ NetworkOPsImp::transactionsSQL (
std::string selection, AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit,
bool binary, bool count, bool bAdmin)
bool binary, bool count, bool bUnlimited)
{
std::uint32_t NONBINARY_PAGE_LENGTH = 200;
std::uint32_t BINARY_PAGE_LENGTH = 500;
@@ -1705,7 +1705,7 @@ NetworkOPsImp::transactionsSQL (
{
numberOfResults = binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH;
}
else if (!bAdmin)
else if (!bUnlimited)
{
numberOfResults = std::min (
binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH,
@@ -1772,14 +1772,15 @@ NetworkOPsImp::transactionsSQL (
NetworkOPs::AccountTxs NetworkOPsImp::getAccountTxs (
AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin)
std::uint32_t offset, int limit, bool bUnlimited)
{
// can be called with no locks
AccountTxs ret;
std::string sql = transactionsSQL (
"AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta", account,
minLedger, maxLedger, descending, offset, limit, false, false, bAdmin);
minLedger, maxLedger, descending, offset, limit, false, false,
bUnlimited);
{
auto db = app_.getTxnDB ().checkoutDb ();
@@ -1837,7 +1838,7 @@ NetworkOPs::AccountTxs NetworkOPsImp::getAccountTxs (
std::vector<NetworkOPsImp::txnMetaLedgerType> NetworkOPsImp::getAccountTxsB (
AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin)
std::uint32_t offset, int limit, bool bUnlimited)
{
// can be called with no locks
std::vector<txnMetaLedgerType> ret;
@@ -1845,7 +1846,7 @@ std::vector<NetworkOPsImp::txnMetaLedgerType> NetworkOPsImp::getAccountTxsB (
std::string sql = transactionsSQL (
"AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta", account,
minLedger, maxLedger, descending, offset, limit, true/*binary*/, false,
bAdmin);
bUnlimited);
{
auto db = app_.getTxnDB ().checkoutDb ();
@@ -1887,7 +1888,7 @@ NetworkOPsImp::AccountTxs
NetworkOPsImp::getTxsAccount (
AccountID const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token,
int limit, bool bAdmin)
int limit, bool bUnlimited)
{
static std::uint32_t const page_length (200);
@@ -1907,7 +1908,7 @@ NetworkOPsImp::getTxsAccount (
accountTxPage(app_.getTxnDB (), app_.accountIDCache(),
std::bind(saveLedgerAsync, std::ref(app_),
std::placeholders::_1), bound, account, minLedger,
maxLedger, forward, token, limit, bAdmin,
maxLedger, forward, token, limit, bUnlimited,
page_length);
return ret;
@@ -1917,7 +1918,7 @@ NetworkOPsImp::MetaTxsList
NetworkOPsImp::getTxsAccountB (
AccountID const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token,
int limit, bool bAdmin)
int limit, bool bUnlimited)
{
static const std::uint32_t page_length (500);
@@ -1935,7 +1936,7 @@ NetworkOPsImp::getTxsAccountB (
accountTxPage(app_.getTxnDB (), app_.accountIDCache(),
std::bind(saveLedgerAsync, std::ref(app_),
std::placeholders::_1), bound, account, minLedger,
maxLedger, forward, token, limit, bAdmin,
maxLedger, forward, token, limit, bUnlimited,
page_length);
return ret;
}
@@ -2703,7 +2704,7 @@ InfoSub::pointer NetworkOPsImp::addRpcSub (
//
// FIXME : support iLimit.
void NetworkOPsImp::getBookPage (
bool bAdmin,
bool bUnlimited,
std::shared_ptr<ReadView const>& lpLedger,
Book const& book,
AccountID const& uTakerID,
@@ -2746,7 +2747,7 @@ void NetworkOPsImp::getBookPage (
auto viewJ = app_.journal ("View");
unsigned int left (iLimit == 0 ? 300 : iLimit);
if (! bAdmin && left > 300)
if (! bUnlimited && left > 300)
left = 300;
while (!bDone && left-- > 0)
@@ -2927,7 +2928,7 @@ void NetworkOPsImp::getBookPage (
// FIXME : support iLimit.
void NetworkOPsImp::getBookPage (
bool bAdmin,
bool bUnlimited,
std::shared_ptr<ReadView const> lpLedger,
Book const& book,
AccountID const& uTakerID,
@@ -2949,7 +2950,7 @@ void NetworkOPsImp::getBookPage (
lesActive.isGlobalFrozen (book.in.account);
unsigned int left (iLimit == 0 ? 300 : iLimit);
if (! bAdmin && left > 300)
if (! bUnlimited && left > 300)
left = 300;
while (left-- > 0 && obIterator.nextOffer ())

View File

@@ -118,12 +118,12 @@ public:
* submitted by clients. Process local transactions synchronously
*
* @param transaction Transaction object
* @param bAdmin Whether an administrative client connection submitted it.
* @param bUnlimited Whether a privileged client connection submitted it.
* @param bLocal Client submission.
* @param failType fail_hard setting from transaction submission.
*/
virtual void processTransaction (std::shared_ptr<Transaction>& transaction,
bool bAdmin, bool bLocal, FailHard failType) = 0;
bool bUnlimited, bool bLocal, FailHard failType) = 0;
//--------------------------------------------------------------------------
//
@@ -139,7 +139,7 @@ public:
//
virtual void getBookPage (
bool bAdmin,
bool bUnlimited,
std::shared_ptr<ReadView const>& lpLedger,
Book const& book,
AccountID const& uTakerID,
@@ -205,23 +205,23 @@ public:
virtual AccountTxs getAccountTxs (
AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin) = 0;
std::uint32_t offset, int limit, bool bUnlimited) = 0;
virtual AccountTxs getTxsAccount (
AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool forward,
Json::Value& token, int limit, bool bAdmin) = 0;
Json::Value& token, int limit, bool bUnlimited) = 0;
using txnMetaLedgerType = std::tuple<std::string, std::string, std::uint32_t>;
using MetaTxsList = std::vector<txnMetaLedgerType>;
virtual MetaTxsList getAccountTxsB (AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin) = 0;
std::uint32_t offset, int limit, bool bUnlimited) = 0;
virtual MetaTxsList getTxsAccountB (AccountID const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool forward,
Json::Value& token, int limit, bool bAdmin) = 0;
Json::Value& token, int limit, bool bUnlimited) = 0;
//--------------------------------------------------------------------------
//

View File

@@ -98,7 +98,7 @@ calculateFee(Application& app, std::uint64_t const baseFee,
Fees const& fees, ApplyFlags flags)
{
return app.getFeeTrack().scaleFeeLoad(
baseFee, fees.base, fees.units, flags & tapADMIN);
baseFee, fees.base, fees.units, flags & tapUNLIMITED);
}
//------------------------------------------------------------------------------

View File

@@ -57,7 +57,7 @@ public:
// Scale using load as well as base rate
std::uint64_t scaleFeeLoad (std::uint64_t fee, std::uint64_t baseFee,
std::uint32_t referenceFeeUnits, bool bAdmin) const;
std::uint32_t referenceFeeUnits, bool bUnlimited) const;
void setRemoteFee (std::uint32_t f)
{

View File

@@ -37,7 +37,7 @@ LoadFeeTrack::scaleFeeBase (std::uint64_t fee, std::uint64_t baseFee,
// Scale using load as well as base rate
std::uint64_t
LoadFeeTrack::scaleFeeLoad (std::uint64_t fee, std::uint64_t baseFee,
std::uint32_t referenceFeeUnits, bool bAdmin) const
std::uint32_t referenceFeeUnits, bool bUnlimited) const
{
if (fee == 0)
return fee;
@@ -49,9 +49,9 @@ LoadFeeTrack::scaleFeeLoad (std::uint64_t fee, std::uint64_t baseFee,
feeFactor = std::max(mLocalTxnLoadFee, mRemoteTxnLoadFee);
uRemFee = std::max(mRemoteTxnLoadFee, mClusterTxnLoadFee);
}
// Let admins pay the normal fee until
// Let privileged users pay the normal fee until
// the local load exceeds four times the remote.
if (bAdmin && (feeFactor > uRemFee) && (feeFactor < (4 * uRemFee)))
if (bUnlimited && (feeFactor > uRemFee) && (feeFactor < (4 * uRemFee)))
feeFactor = uRemFee;
// Compute:

View File

@@ -47,7 +47,7 @@ enum ApplyFlags
tapRETRY = 0x20,
// Transaction came from a privileged source
tapADMIN = 0x400,
tapUNLIMITED = 0x400,
};
inline

View File

@@ -318,6 +318,7 @@ JSS ( response ); // websocket
JSS ( result ); // RPC
JSS ( ripple_lines ); // out: NetworkOPs
JSS ( ripple_state ); // in: LedgerEntr
JSS ( role ); // out: Ping.cpp
JSS ( rt_accounts ); // in: Subscribe, Unsubscribe
JSS ( sanity ); // out: PeerImp
JSS ( search_depth ); // in: RipplePathFind
@@ -387,6 +388,7 @@ JSS ( type ); // in: AccountObjects
// paths/Node.cpp, OverlayImpl, Logic
JSS ( type_hex ); // out: STPathSet
JSS ( unl ); // out: UnlList
JSS ( unlimited); // out: Connection.h
JSS ( uptime ); // out: GetCounts
JSS ( url ); // in/out: Subscribe, Unsubscribe
JSS ( url_password ); // in: Subscribe

View File

@@ -46,7 +46,7 @@ public:
std::string to_string () const;
/** Returns `true` if this is a privileged endpoint. */
bool admin () const;
bool isUnlimited () const;
/** Raise the Consumer's privilege level to a Named endpoint.
The reference to the original endpoint descriptor is released.

View File

@@ -47,7 +47,7 @@ public:
virtual Consumer newOutboundEndpoint (beast::IP::Endpoint const& address) = 0;
/** Create a new endpoint keyed by name. */
virtual Consumer newAdminEndpoint (std::string const& name) = 0;
virtual Consumer newUnlimitedEndpoint (std::string const& name) = 0;
/** Extract packaged consumer information for export. */
virtual Gossip exportConsumers () = 0;

View File

@@ -78,20 +78,14 @@ std::string Consumer::to_string () const
return m_entry->to_string();
}
bool Consumer::admin () const
bool Consumer::isUnlimited () const
{
if (m_entry)
return m_entry->admin();
return m_entry->isUnlimited();
return false;
}
void Consumer::elevate (std::string const& name)
{
bassert (m_entry != nullptr);
m_entry = &m_logic->elevateToAdminEndpoint (*m_entry, name);
}
Disposition Consumer::disposition() const
{
Disposition d = ok;

View File

@@ -56,7 +56,7 @@ struct Entry
{
case kindInbound: return key->address.to_string();
case kindOutbound: return key->address.to_string();
case kindAdmin: return std::string ("\"") + key->name + "\"";
case kindUnlimited: return std::string ("\"") + key->name + "\"";
default:
bassertfalse;
}
@@ -64,10 +64,14 @@ struct Entry
return "(undefined)";
}
// Returns `true` if this connection is privileged
bool admin () const
/**
* Returns `true` if this connection should have no
* resource limits applied--it is still possible for certain RPC commands
* to be forbidden, but that depends on Role.
*/
bool isUnlimited () const
{
return key->kind == kindAdmin;
return key->kind == kindUnlimited;
}
// Balance including remote contributions

View File

@@ -36,23 +36,19 @@ struct Key
Key () = delete;
// Constructor for Inbound and Outbound (non-Admin) keys
// Constructor for Inbound and Outbound (non-Unlimited) keys
Key (Kind k, beast::IP::Endpoint const& addr)
: kind(k)
, address(addr)
, name()
{
assert(kind != kindAdmin);
assert(kind != kindUnlimited);
}
// Constructor for Admin keys
Key (Kind k, std::string const& n)
: kind(k)
, address()
// Constructor for Unlimited keys
Key (std::string const& n)
: kind(kindUnlimited)
, name(n)
{
assert(kind == kindAdmin);
}
{}
struct hasher
{
@@ -64,7 +60,7 @@ struct Key
case kindOutbound:
return m_addr_hash (v.address);
case kindAdmin:
case kindUnlimited:
return m_name_hash (v.name);
default:
@@ -92,7 +88,7 @@ struct Key
case kindOutbound:
return lhs.address == rhs.address;
case kindAdmin:
case kindUnlimited:
return lhs.name == rhs.name;
default:

View File

@@ -23,12 +23,19 @@
namespace ripple {
namespace Resource {
// Kind of consumer
/**
* Kind of consumer.
* kindInbound: Inbound connection.
* kindOutbound: Outbound connection.
* kindUnlimited: Inbound connection with no resource limits, but could be
* subjected to administrative restrictions, such as
* use of some RPC commands like "stop".
*/
enum Kind
{
kindInbound
,kindOutbound
,kindAdmin
,kindUnlimited
};
}

View File

@@ -165,7 +165,12 @@ public:
return Consumer (*this, *entry);
}
Consumer newAdminEndpoint (std::string const& name)
/**
* Create endpoint that should not have resource limits applied. Other
* restrictions, such as permission to perform certain RPC calls, may be
* enabled.
*/
Consumer newUnlimitedEndpoint (std::string const& name)
{
Entry* entry (nullptr);
@@ -173,7 +178,7 @@ public:
std::lock_guard<std::recursive_mutex> _(lock_);
auto result =
table_.emplace (std::piecewise_construct,
std::make_tuple (kindAdmin, name), // Key
std::make_tuple (name), // Key
std::make_tuple (m_clock.now())); // Entry
entry = &result.first->second;
@@ -189,42 +194,11 @@ public:
}
m_journal.debug <<
"New admin endpoint " << *entry;
"New unlimited endpoint " << *entry;
return Consumer (*this, *entry);
}
Entry& elevateToAdminEndpoint (Entry& prior, std::string const& name)
{
m_journal.info <<
"Elevate " << prior << " to " << name;
Entry* entry (nullptr);
{
std::lock_guard<std::recursive_mutex> _(lock_);
auto result =
table_.emplace (std::piecewise_construct,
std::make_tuple (kindAdmin, name), // Key
std::make_tuple (m_clock.now())); // Entry
entry = &result.first->second;
entry->key = &result.first->first;
++entry->refcount;
if (entry->refcount == 1)
{
if (! result.second)
inactive_.erase (
inactive_.iterator_to (*entry));
admin_.push_back (*entry);
}
release (prior);
}
return *entry;
}
Json::Value getJson ()
{
return getJson (warningThreshold);
@@ -450,7 +424,7 @@ public:
outbound_.erase (
outbound_.iterator_to (entry));
break;
case kindAdmin:
case kindUnlimited:
admin_.erase (
admin_.iterator_to (entry));
break;
@@ -475,7 +449,7 @@ public:
bool warn (Entry& entry)
{
if (entry.admin())
if (entry.isUnlimited())
return false;
std::lock_guard<std::recursive_mutex> _(lock_);
@@ -498,7 +472,7 @@ public:
bool disconnect (Entry& entry)
{
if (entry.admin())
if (entry.isUnlimited())
return false;
std::lock_guard<std::recursive_mutex> _(lock_);

View File

@@ -73,9 +73,9 @@ public:
return logic_.newOutboundEndpoint (address);
}
Consumer newAdminEndpoint (std::string const& name) override
Consumer newUnlimitedEndpoint (std::string const& name) override
{
return logic_.newAdminEndpoint (name);
return logic_.newUnlimitedEndpoint (name);
}
Gossip exportConsumers () override

View File

@@ -38,6 +38,15 @@ namespace RPC {
/** The context of information needed to call an RPC. */
struct Context
{
/**
* Data passed in from HTTP headers.
*/
struct Headers
{
std::string user;
std::string forwardedFor;
};
beast::Journal j;
Json::Value params;
Application& app;
@@ -47,6 +56,7 @@ struct Context
Role role;
std::shared_ptr<JobCoro> jobCoro;
InfoSub::pointer infoSub;
Headers headers;
};
} // RPC

View File

@@ -129,7 +129,7 @@ Json::Value doAccountTx (RPC::Context& context)
{
auto txns = context.netOps.getTxsAccountB (
*account, uLedgerMin, uLedgerMax, bForward, resumeToken, limit,
context.role == Role::ADMIN);
isUnlimited (context.role));
for (auto& it: txns)
{
@@ -150,7 +150,7 @@ Json::Value doAccountTx (RPC::Context& context)
{
auto txns = context.netOps.getTxsAccount (
*account, uLedgerMin, uLedgerMax, bForward, resumeToken, limit,
context.role == Role::ADMIN);
isUnlimited (context.role));
for (auto& it: txns)
{

View File

@@ -147,7 +147,7 @@ Json::Value doAccountTxOld (RPC::Context& context)
{
auto txns = context.netOps.getAccountTxsB (
*raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit,
context.role == Role::ADMIN);
isUnlimited (context.role));
for (auto it = txns.begin (), end = txns.end (); it != end; ++it)
{
@@ -169,7 +169,7 @@ Json::Value doAccountTxOld (RPC::Context& context)
{
auto txns = context.netOps.getAccountTxs (
*raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit,
context.role == Role::ADMIN);
isUnlimited (context.role));
for (auto it = txns.begin (), end = txns.end (); it != end; ++it)
{

View File

@@ -183,7 +183,7 @@ Json::Value doBookOffers (RPC::Context& context)
: Json::Value (Json::nullValue));
context.netOps.getBookPage (
context.role == Role::ADMIN,
isUnlimited (context.role),
lpLedger,
{{pay_currency, pay_issuer}, {get_currency, get_issuer}},
takerID ? *takerID : zero, bProof, limit, jvMarker, jvResult);

View File

@@ -69,7 +69,7 @@ Json::Value doLedgerData (RPC::Context& context)
}
auto maxLimit = RPC::Tuning::pageLength(isBinary);
if ((limit < 0) || ((limit > maxLimit) && (context.role != Role::ADMIN)))
if ((limit < 0) || ((limit > maxLimit) && (! isUnlimited (context.role))))
limit = maxLimit;
jvResult[jss::ledger_hash] = to_string (lpLedger->info().hash);

View File

@@ -64,11 +64,11 @@ Status LedgerHandler::check()
{
// Until some sane way to get full ledgers has been implemented,
// disallow retrieving all state nodes.
if (context_.role != Role::ADMIN)
if (! isUnlimited (context_.role))
return rpcNO_PERMISSION;
if (context_.app.getFeeTrack().isLoadedLocal() &&
context_.role != Role::ADMIN)
!isUnlimited (context_.role))
{
return rpcTOO_BUSY;
}

View File

@@ -19,6 +19,9 @@
#include <BeastConfig.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/server/Role.h>
#include <ripple/rpc/Context.h>
namespace ripple {
@@ -28,7 +31,29 @@ struct Context;
Json::Value doPing (RPC::Context& context)
{
// For testing connection privileges.
if (isUnlimited(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;
}
else
{
return Json::Value (Json::objectValue);
}
}
} // ripple

View File

@@ -116,7 +116,7 @@ Json::Value doRipplePathFind (RPC::Context& context)
return jvResult;
}
RPC::LegacyPathFind lpf (context.role == Role::ADMIN, context.app);
RPC::LegacyPathFind lpf (isUnlimited (context.role), context.app);
if (! lpf.isOk ())
return rpcError (rpcTOO_BUSY);
@@ -214,7 +214,7 @@ Json::Value doRipplePathFind (RPC::Context& context)
&& context.params[jss::search_depth].isIntegral())
{
int rLev = context.params[jss::search_depth].asInt ();
if ((rLev < level) || (context.role == Role::ADMIN))
if ((rLev < level) || (isUnlimited (context.role)))
level = rLev;
}

View File

@@ -111,7 +111,7 @@ Json::Value doSubmit (RPC::Context& context)
auto const failType = getFailHard (context);
context.netOps.processTransaction (
tpTrans, context.role == Role::ADMIN, true, failType);
tpTrans, isUnlimited (context.role), true, failType);
}
catch (std::exception& e)
{

View File

@@ -324,7 +324,7 @@ Json::Value doSubscribe (RPC::Context& context)
auto add = [&](Json::StaticString field)
{
context.netOps.getBookPage (context.role == Role::ADMIN,
context.netOps.getBookPage (isUnlimited (context.role),
lpLedger, field == jss::asks ? reversed (book) : book,
takerID ? *takerID : noAccount(), false, 0, jvMarker,
jvOffers);

View File

@@ -44,7 +44,7 @@ Json::Value doTxHistory (RPC::Context& context)
unsigned int startIndex = context.params[jss::start].asUInt ();
if ((startIndex > 10000) && (context.role != Role::ADMIN))
if ((startIndex > 10000) && (! isUnlimited (context.role)))
return rpcError (rpcNO_PERMISSION);
Json::Value obj;

View File

@@ -112,7 +112,7 @@ namespace {
error_code_i fillHandler (Context& context,
boost::optional<Handler const&>& result)
{
if (context.role != Role::ADMIN)
if (! isUnlimited (context.role))
{
// VFALCO NOTE Should we also add up the jtRPC jobs?
//
@@ -232,7 +232,27 @@ Status doCommand (
}
if (auto method = handler->valueMethod_)
{
if (! context.headers.user.empty() ||
! context.headers.forwardedFor.empty())
{
context.j.debug << "start command: " << handler->name_ <<
", X-User: " << context.headers.user << ", X-Forwarded-For: " <<
context.headers.forwardedFor;
auto ret = callMethod (context, method, handler->name_, result);
context.j.debug << "finish command: " << handler->name_ <<
", X-User: " << context.headers.user << ", X-Forwarded-For: " <<
context.headers.forwardedFor;
return ret;
}
else
{
return callMethod (context, method, handler->name_, result);
}
}
return rpcUNKNOWN_COMMAND;
}

View File

@@ -192,7 +192,7 @@ static Json::Value checkPayment(
"Cannot build XRP to XRP paths.");
{
LegacyPathFind lpf (role == Role::ADMIN, app);
LegacyPathFind lpf (isUnlimited (role), app);
if (!lpf.isOk ())
return rpcError (rpcTOO_BUSY);
@@ -284,7 +284,7 @@ checkTxJsonFields (
}
// Check for load.
if (feeTrack.isLoadedCluster() && (role != Role::ADMIN))
if (feeTrack.isLoadedCluster() && !isUnlimited (role))
{
ret.first = rpcError (rpcTOO_BUSY);
return ret;
@@ -646,10 +646,10 @@ Json::Value checkFee (
// Default fee in fee units.
std::uint64_t const feeDefault = config.TRANSACTION_FEE_BASE;
// Administrative endpoints are exempt from local fees.
// Administrative and identified endpoints are exempt from local fees.
std::uint64_t const fee =
feeTrack.scaleFeeLoad (feeDefault,
ledger->fees().base, ledger->fees().units, role == Role::ADMIN);
ledger->fees().base, ledger->fees().units, isUnlimited (role));
std::uint64_t const limit = mult * feeTrack.scaleFeeBase (
feeDefault, ledger->fees().base, ledger->fees().units);
@@ -741,7 +741,7 @@ Json::Value transactionSubmit (
{
// FIXME: For performance, should use asynch interface
processTransaction (
txn.second, role == Role::ADMIN, true, failType);
txn.second, isUnlimited (role), true, failType);
}
catch (std::exception&)
{
@@ -1112,7 +1112,7 @@ Json::Value transactionSubmitMultiSigned (
{
// FIXME: For performance, should use asynch interface
processTransaction (
txn.second, role == Role::ADMIN, true, failType);
txn.second, isUnlimited (role), true, failType);
}
catch (std::exception&)
{

View File

@@ -65,14 +65,14 @@ Json::Value checkFee (
// Return a std::function<> that calls NetworkOPs::processTransaction.
using ProcessTransactionFn =
std::function<void (std::shared_ptr<Transaction>& transaction,
bool bAdmin, bool bLocal, NetworkOPs::FailHard failType)>;
bool bUnlimited, bool bLocal, NetworkOPs::FailHard failType)>;
inline ProcessTransactionFn getProcessTxnFn (NetworkOPs& netOPs)
{
return [&netOPs](std::shared_ptr<Transaction>& transaction,
bool bAdmin, bool bLocal, NetworkOPs::FailHard failType)
bool bUnlimited, bool bLocal, NetworkOPs::FailHard failType)
{
netOPs.processTransaction(transaction, bAdmin, bLocal, failType);
netOPs.processTransaction(transaction, bUnlimited, bLocal, failType);
};
}

View File

@@ -123,7 +123,7 @@ boost::optional<Json::Value> readLimitField(
return RPC::expected_field_error (jss::limit, "unsigned integer");
limit = jvLimit.asUInt();
if (context.role != Role::ADMIN)
if (! isUnlimited (context.role))
limit = std::max(range.rmin, std::min(range.rmax, limit));
}
return boost::none;

View File

@@ -41,6 +41,7 @@ struct Port
std::uint16_t port = 0;
std::set<std::string, beast::ci_less> protocol;
std::vector<beast::IP::Address> admin_ip;
std::vector<beast::IP::Address> secure_gateway_ip;
std::string user;
std::string password;
std::string admin_user;
@@ -94,22 +95,8 @@ Port::protocols() const
return s;
}
inline
std::ostream&
operator<< (std::ostream& os, Port const& p)
{
os << "'" << p.name << "' (ip=" << p.ip << ":" << p.port << ", ";
if (! p.admin_ip.empty ())
{
os << "admin IPs:";
for (auto const& ip : p.admin_ip)
os << ip.to_string () << ", ";
}
os << p.protocols () << ")";
return os;
}
operator<< (std::ostream& os, Port const& p);
} // HTTP
} // ripple

View File

@@ -28,11 +28,17 @@
namespace ripple {
/** Indicates the level of administrative permission to grant. */
/** Indicates the level of administrative permission to grant.
* IDENTIFIED role has unlimited resources but cannot perform some
* RPC commands.
* ADMIN role has unlimited resources and is able to perform all RPC
* commands.
*/
enum class Role
{
GUEST,
USER,
IDENTIFIED,
ADMIN,
FORBID
};
@@ -46,12 +52,27 @@ enum class Role
*/
Role
requestRole (Role const& required, HTTP::Port const& port,
Json::Value const& jsonRPC, beast::IP::Endpoint const& remoteIp);
Json::Value const& jsonRPC, beast::IP::Endpoint const& remoteIp,
std::string const& user);
Resource::Consumer
requestInboundEndpoint (Resource::Manager& manager,
beast::IP::Endpoint const& remoteAddress,
HTTP::Port const& port);
HTTP::Port const& port, std::string const& user);
/**
* Check if the role entitles the user to unlimited resources.
*/
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.
*/
bool
isIdentified (HTTP::Port const& port, beast::IP::Address const& remoteIp,
std::string const& user);
} // ripple

View File

@@ -68,6 +68,16 @@ public:
beast::IP::Endpoint
remoteAddress() = 0;
/** Returns the user name if behind a proxy (secure_gateway). */
virtual
std::string
user() = 0;
/** Returns X-Forwarded-For if behind a proxy (secure_gateway). */
virtual
std::string
forwarded_for() = 0;
/** Returns the current HTTP request. */
virtual
beast::http::message&

View File

@@ -87,6 +87,8 @@ protected:
boost::asio::io_service::strand strand_;
waitable_timer timer_;
endpoint_type remote_address_;
std::string forwarded_for_;
std::string user_;
beast::Journal journal_;
std::string id_;
@@ -183,6 +185,18 @@ protected:
return beast::IPAddressConversion::from_asio(remote_address_);
}
std::string
user() override
{
return user_;
}
std::string
forwarded_for() override
{
return forwarded_for_;
}
beast::http::message&
request() override
{
@@ -376,10 +390,21 @@ Peer<Impl>::do_read (yield_context yield)
if (! ec)
{
if (parser.complete())
{
auto const iter = message_.headers.find ("X-Forwarded-For");
if (iter != message_.headers.end())
forwarded_for_ = iter->second;
auto const iter2 = message_.headers.find ("X-User");
if (iter2 != message_.headers.end())
user_ = iter2->second;
return do_request();
}
else if (eof)
{
ec = boost::asio::error::eof; // incomplete request
}
}
if (ec)
return fail (ec, "read");

View File

@@ -0,0 +1,49 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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/server/Port.h>
namespace ripple {
namespace HTTP {
std::ostream&
operator<< (std::ostream& os, Port const& p)
{
os << "'" << p.name << "' (ip=" << p.ip << ":" << p.port << ", ";
if (! p.admin_ip.empty ())
{
os << "admin IPs:";
for (auto const& ip : p.admin_ip)
os << ip.to_string () << ", ";
}
if (! p.secure_gateway_ip.empty ())
{
os << "secure_gateway IPs:";
for (auto const& ip : p.secure_gateway_ip)
os << ip.to_string () << ", ";
}
os << p.protocols () << ")";
return os;
}
} // HTTP
} // ripple

View File

@@ -56,25 +56,59 @@ isAdmin (HTTP::Port const& port, Json::Value const& params,
Role
requestRole (Role const& required, HTTP::Port const& port,
Json::Value const& params, beast::IP::Endpoint const& remoteIp)
Json::Value const& params, beast::IP::Endpoint const& remoteIp,
std::string const& user)
{
Role role (Role::GUEST);
if (isAdmin(port, params, remoteIp.address ()))
role = Role::ADMIN;
if (required == Role::ADMIN && role != required)
role = Role::FORBID;
return role;
if (isAdmin(port, params, remoteIp.address()))
return Role::ADMIN;
if (required == Role::ADMIN)
return Role::FORBID;
if (isIdentified(port, remoteIp.address(), user))
return Role::IDENTIFIED;
return Role::GUEST;
}
/**
* ADMIN and IDENTIFIED roles shall have unlimited resources.
*/
bool
isUnlimited (Role const& required, HTTP::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;
}
Resource::Consumer
requestInboundEndpoint (Resource::Manager& manager,
beast::IP::Endpoint const& remoteAddress,
HTTP::Port const& port)
HTTP::Port const& port, std::string const& user)
{
if (requestRole (Role::GUEST, port, Json::Value(), remoteAddress) ==
Role::ADMIN)
return manager.newAdminEndpoint (to_string (remoteAddress));
if (isUnlimited (Role::GUEST, port, Json::Value(), remoteAddress, user))
return manager.newUnlimitedEndpoint (to_string (remoteAddress));
return manager.newInboundEndpoint(remoteAddress);
}
bool
isIdentified (HTTP::Port const& port, beast::IP::Address const& remoteIp,
std::string const& user)
{
return ! user.empty() && ipAllowed (remoteIp, port.secure_gateway_ip);
}
}

View File

@@ -204,7 +204,8 @@ ServerHandlerImp::processSession (std::shared_ptr<HTTP::Session> const& session,
std::shared_ptr<JobCoro> jobCoro)
{
processRequest (session->port(), to_string (session->body()),
session->remoteAddress().at_port (0), makeOutput (*session), jobCoro);
session->remoteAddress().at_port (0), makeOutput (*session), jobCoro,
session->forwarded_for(), session->user());
if (session->request().keep_alive())
session->complete();
@@ -215,7 +216,8 @@ ServerHandlerImp::processSession (std::shared_ptr<HTTP::Session> const& session,
void
ServerHandlerImp::processRequest (HTTP::Port const& port,
std::string const& request, beast::IP::Endpoint const& remoteIPAddress,
Output&& output, std::shared_ptr<JobCoro> jobCoro)
Output&& output, std::shared_ptr<JobCoro> jobCoro,
std::string forwardedFor, std::string user)
{
auto rpcJ = app_.journal ("RPC");
// Move off the webserver thread onto the JobQueue.
@@ -260,18 +262,28 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
jsonRPC["params"][Json::UInt(0)].isObject())
{
role = requestRole(required, port, jsonRPC["params"][Json::UInt(0)],
remoteIPAddress);
remoteIPAddress, user);
}
else
{
role = requestRole(required, port, Json::objectValue,
remoteIPAddress);
remoteIPAddress, user);
}
/**
* Clear header-assigned values if not positively identified from a
* secure_gateway.
*/
if (role != Role::IDENTIFIED)
{
forwardedFor.clear();
user.clear();
}
Resource::Consumer usage;
if (role == Role::ADMIN)
usage = m_resourceManager.newAdminEndpoint (
if (isUnlimited (role))
usage = m_resourceManager.newUnlimitedEndpoint (
remoteIPAddress.to_string());
else
usage = m_resourceManager.newInboundEndpoint(remoteIPAddress);
@@ -338,7 +350,8 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
auto const start (std::chrono::high_resolution_clock::now ());
RPC::Context context {m_journal, params, app_, loadType, m_networkOPs,
app_.getLedgerMaster(), role, jobCoro};
app_.getLedgerMaster(), role, jobCoro, InfoSub::pointer(),
{user, forwardedFor}};
Json::Value result;
RPC::doCommand (context, result);
@@ -474,8 +487,73 @@ struct ParsedPort
boost::optional<boost::asio::ip::address> ip;
boost::optional<std::uint16_t> port;
boost::optional<std::vector<beast::IP::Address>> admin_ip;
boost::optional<std::vector<beast::IP::Address>> secure_gateway_ip;
};
void
populate (Section const& section, std::string const& field, std::ostream& log,
boost::optional<std::vector<beast::IP::Address>>& ips,
bool allowAllIps, std::vector<beast::IP::Address> const& admin_ip)
{
auto const result = section.find(field);
if (result.second)
{
std::stringstream ss (result.first);
std::string ip;
bool has_any (false);
ips.emplace();
while (std::getline (ss, ip, ','))
{
auto const addr = beast::IP::Endpoint::from_string_checked (ip);
if (! addr.second)
{
log << "Invalid value '" << ip << "' for key '" << field <<
"' in [" << section.name () << "]\n";
Throw<std::exception> ();
}
if (is_unspecified (addr.first))
{
if (! allowAllIps)
{
log << "0.0.0.0 not allowed'" <<
"' for key '" << field << "' in [" <<
section.name () << "]\n";
throw std::exception ();
}
else
{
has_any = true;
}
}
if (has_any && ! ips->empty ())
{
log << "IP specified along with 0.0.0.0 '" << ip <<
"' for key '" << field << "' in [" <<
section.name () << "]\n";
Throw<std::exception> ();
}
auto const& address = addr.first.address();
if (std::find_if (admin_ip.begin(), admin_ip.end(),
[&address] (beast::IP::Address const& ip)
{
return address == ip;
}
) != admin_ip.end())
{
log << "IP specified for " << field << " is also for " <<
"admin: " << ip << " in [" << section.name() << "]\n";
throw std::exception();
}
ips->emplace_back (addr.first.address ());
}
}
}
void
parse_Port (ParsedPort& port, Section const& section, std::ostream& log)
{
@@ -527,39 +605,9 @@ parse_Port (ParsedPort& port, Section const& section, std::ostream& log)
}
}
{
auto const result = section.find("admin");
if (result.second)
{
std::stringstream ss (result.first);
std::string ip;
bool has_any (false);
port.admin_ip.emplace ();
while (std::getline (ss, ip, ','))
{
auto const addr = beast::IP::Endpoint::from_string_checked (ip);
if (! addr.second)
{
log << "Invalid value '" << ip << "' for key 'admin' in ["
<< section.name () << "]\n";
Throw<std::exception> ();
}
if (is_unspecified (addr.first))
has_any = true;
if (has_any && ! port.admin_ip->empty ())
{
log << "IP specified along with 0.0.0.0 '" << ip <<
"' for key 'admin' in [" << section.name () << "]\n";
Throw<std::exception> ();
}
port.admin_ip->emplace_back (addr.first.address ());
}
}
}
populate (section, "admin", log, port.admin_ip, true, {});
populate (section, "secure_gateway", log, port.secure_gateway_ip, false,
port.admin_ip.get_value_or({}));
set(port.user, "user", section);
set(port.password, "password", section);
@@ -596,6 +644,8 @@ to_Port(ParsedPort const& parsed, std::ostream& log)
p.port = *parsed.port;
if (parsed.admin_ip)
p.admin_ip = *parsed.admin_ip;
if (parsed.secure_gateway_ip)
p.secure_gateway_ip = *parsed.secure_gateway_ip;
if (parsed.protocol.empty())
{

View File

@@ -114,7 +114,8 @@ private:
void
processRequest (HTTP::Port const& port, std::string const& request,
beast::IP::Endpoint const& remoteIPAddress, Output&&,
std::shared_ptr<JobCoro> jobCoro);
std::shared_ptr<JobCoro> jobCoro,
std::string forwardedFor, std::string user);
//
// PropertyStream

View File

@@ -24,6 +24,7 @@
#include <ripple/server/impl/Door.cpp>
#include <ripple/server/impl/JSONRPCUtil.cpp>
#include <ripple/server/impl/Port.cpp>
#include <ripple/server/impl/Role.cpp>
#include <ripple/server/impl/ServerImpl.cpp>
#include <ripple/server/impl/ServerHandlerImp.cpp>

View File

@@ -74,7 +74,8 @@ public:
handler_type& handler,
connection_ptr const& cpConnection,
beast::IP::Endpoint const& remoteAddress,
boost::asio::io_service& io_service);
boost::asio::io_service& io_service,
std::pair<std::string, std::string> identity);
void preDestroy ();
@@ -108,6 +109,8 @@ private:
Resource::Manager& m_resourceManager;
Resource::Consumer m_usage;
beast::IP::Endpoint const m_remoteAddress;
std::string const m_forwardedFor;
std::string const m_user;
std::mutex m_receiveQueueMutex;
std::deque <message_ptr> m_receiveQueue;
NetworkOPs& m_netOPs;
@@ -133,13 +136,18 @@ ConnectionImpl <WebSocket>::ConnectionImpl (
handler_type& handler,
connection_ptr const& cpConnection,
beast::IP::Endpoint const& remoteAddress,
boost::asio::io_service& io_service)
boost::asio::io_service& io_service,
std::pair<std::string, std::string> identity)
: InfoSub (source, requestInboundEndpoint (
resourceManager, remoteAddress, handler.port()))
resourceManager, remoteAddress, handler.port(), identity.second))
, app_(app)
, m_port (handler.port ())
, m_resourceManager (resourceManager)
, m_remoteAddress (remoteAddress)
, m_forwardedFor (isIdentified (m_port, m_remoteAddress.address(),
identity.second) ? identity.first : std::string())
, m_user (isIdentified (m_port, m_remoteAddress.address(),
identity.second) ? identity.second : std::string())
, m_netOPs (app_.getOPs ())
, m_io_service (io_service)
, m_pingTimer (io_service)
@@ -150,6 +158,12 @@ ConnectionImpl <WebSocket>::ConnectionImpl (
{
// VFALCO Disabled since it might cause hangs
pingFreq_ = 0;
if (! m_forwardedFor.empty() || ! m_user.empty())
{
j_.debug << "connect secure_gateway X-Forwarded-For: " <<
m_forwardedFor << ", X-User: " << m_user;
}
}
template <class WebSocket>
@@ -273,7 +287,8 @@ Json::Value ConnectionImpl <WebSocket>::invokeCommand (
Json::Value jvResult (Json::objectValue);
auto required = RPC::roleRequired (jvRequest[jss::command].asString());
auto role = requestRole (required, m_port, jvRequest, m_remoteAddress);
auto role = requestRole (required, m_port, jvRequest, m_remoteAddress,
m_user);
if (Role::FORBID == role)
{
@@ -283,7 +298,7 @@ Json::Value ConnectionImpl <WebSocket>::invokeCommand (
{
RPC::Context context {app_.journal ("RPCHandler"), jvRequest,
app_, loadType, m_netOPs, app_.getLedgerMaster(), role,
jobCoro, this->shared_from_this ()};
jobCoro, this->shared_from_this (), {m_user, m_forwardedFor}};
RPC::doCommand (context, jvResult[jss::result]);
}
@@ -308,6 +323,13 @@ Json::Value ConnectionImpl <WebSocket>::invokeCommand (
else
{
jvResult[jss::status] = jss::success;
// For testing resource limits on this connection.
if (jvRequest[jss::command].asString() == "ping")
{
if (getConsumer().isUnlimited())
jvResult[jss::unlimited] = true;
}
}
if (jvRequest.isMember (jss::id))
@@ -323,6 +345,12 @@ Json::Value ConnectionImpl <WebSocket>::invokeCommand (
template <class WebSocket>
void ConnectionImpl <WebSocket>::preDestroy ()
{
if (! m_forwardedFor.empty() || ! m_user.empty())
{
j_.debug << "disconnect secure_gateway X-Forwarded-For: " <<
m_forwardedFor << ", X-User: " << m_user;
}
// sever connection
this->m_pingTimer.cancel ();
m_connection.reset ();

View File

@@ -206,7 +206,8 @@ public:
*this,
cpClient,
makeBeastEndpoint (remoteEndpoint),
WebSocket::getStrand (*cpClient).get_io_service ());
WebSocket::getStrand (*cpClient).get_io_service (),
cpClient->get_identity());
connection->setPingTimer ();
auto result = mMap.emplace (cpClient, std::move (connection));

View File

@@ -345,7 +345,27 @@ public:
return m_strand;
}
protected:
/**
* Set values based on HTTP headers X-Forwarded-For and X-User to
* be passed to Connection object. This is used to identify users
* connecting through a secure_gateway.
*/
void set_identity (std::string const& forwarded_for,
std::string const& user)
{
m_identity = std::make_pair (forwarded_for, user);
}
/**
* Get values derived from contents of HTTP headers X-Forwarded-For and
* X-User. This identifies users connecting through a secure_gateway.
*/
std::pair<std::string, std::string> get_identity()
{
return m_identity;
}
protected:
/// Initialize transport for reading
/**
* init_asio is called once immediately after construction to initialize
@@ -1148,6 +1168,9 @@ private:
async_read_handler m_async_read_handler;
async_write_handler m_async_write_handler;
// Header identification: X-Forwarded-For, X-User
std::pair<std::string, std::string> m_identity;
};

View File

@@ -753,6 +753,27 @@ public:
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
return m_strand;
}
/**
* Set values based on HTTP headers X-Forwarded-For and X-User to
* be passed to Connection object. This is used to identify users
* connecting through a secure_gateway.
*/
void set_identity (std::string const& forwarded_for,
std::string const& user)
{
m_identity = std::make_pair (forwarded_for, user);
}
/**
* Get values derived from contents of HTTP headers X-Forwarded-For and
* X-User. This identifies users connecting through a secure_gateway.
*/
std::pair<std::string, std::string> get_identity()
{
return m_identity;
}
public:
//protected: TODO: figure out why VCPP2010 doesn't like protected here
@@ -1522,6 +1543,9 @@ public:
mutable boost::recursive_mutex m_lock;
boost::asio::strand m_strand;
bool m_detached; // TODO: this should be atomic
// Header identification: X-Forwarded-For, X-User
std::pair<std::string, std::string> m_identity;
};
// connection related types that it and its policy classes need.

View File

@@ -913,6 +913,9 @@ void server<endpoint>::connection<connection_type>::handle_write_response(
m_connection.m_state = session::state::OPEN;
m_connection.set_identity (get_request_header ("X-Forwarded-For"),
get_request_header ("X-User"));
m_connection.get_handler()->on_open(m_connection.shared_from_this());
get_io_service().post(