From 496fea5995d1364b90f2c9b2037fffc4781f4b66 Mon Sep 17 00:00:00 2001 From: Mark Travis Date: Tue, 24 Nov 2015 17:17:56 -0800 Subject: [PATCH] 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. --- Builds/VisualStudio2015/RippleD.vcxproj | 3 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + doc/rippled-example.cfg | 23 ++++ src/ripple/app/misc/NetworkOPs.cpp | 69 +++++----- src/ripple/app/misc/NetworkOPs.h | 14 +- src/ripple/app/tx/impl/Transactor.cpp | 2 +- src/ripple/core/LoadFeeTrack.h | 2 +- src/ripple/core/impl/LoadFeeTrack.cpp | 6 +- src/ripple/ledger/ApplyView.h | 2 +- src/ripple/protocol/JsonFields.h | 2 + src/ripple/resource/Consumer.h | 2 +- src/ripple/resource/ResourceManager.h | 2 +- src/ripple/resource/impl/Consumer.cpp | 10 +- src/ripple/resource/impl/Entry.h | 12 +- src/ripple/resource/impl/Key.h | 20 ++- src/ripple/resource/impl/Kind.h | 11 +- src/ripple/resource/impl/Logic.h | 48 ++----- src/ripple/resource/impl/ResourceManager.cpp | 4 +- src/ripple/rpc/Context.h | 10 ++ src/ripple/rpc/handlers/AccountTx.cpp | 4 +- src/ripple/rpc/handlers/AccountTxOld.cpp | 4 +- src/ripple/rpc/handlers/BookOffers.cpp | 2 +- src/ripple/rpc/handlers/LedgerData.cpp | 2 +- src/ripple/rpc/handlers/LedgerHandler.cpp | 4 +- src/ripple/rpc/handlers/Ping.cpp | 27 +++- src/ripple/rpc/handlers/RipplePathFind.cpp | 4 +- src/ripple/rpc/handlers/Submit.cpp | 2 +- src/ripple/rpc/handlers/Subscribe.cpp | 2 +- src/ripple/rpc/handlers/TxHistory.cpp | 2 +- src/ripple/rpc/impl/RPCHandler.cpp | 24 +++- src/ripple/rpc/impl/TransactionSign.cpp | 12 +- src/ripple/rpc/impl/TransactionSign.h | 6 +- src/ripple/rpc/impl/Utilities.cpp | 2 +- src/ripple/server/Port.h | 17 +-- src/ripple/server/Role.h | 27 +++- src/ripple/server/Session.h | 10 ++ src/ripple/server/impl/Peer.h | 25 ++++ src/ripple/server/impl/Port.cpp | 49 +++++++ src/ripple/server/impl/Role.cpp | 56 ++++++-- src/ripple/server/impl/ServerHandlerImp.cpp | 130 ++++++++++++------ src/ripple/server/impl/ServerHandlerImp.h | 3 +- src/ripple/unity/server.cpp | 1 + src/ripple/websocket/Connection.h | 38 ++++- src/ripple/websocket/Handler.h | 3 +- .../websocketpp/transport/asio/connection.hpp | 29 +++- src/websocketpp_02/src/connection.hpp | 24 ++++ src/websocketpp_02/src/roles/server.hpp | 3 + 47 files changed, 538 insertions(+), 219 deletions(-) create mode 100644 src/ripple/server/impl/Port.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 63c2bd51e..c9a1f6ec0 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -3525,6 +3525,9 @@ + + True + True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 14d053373..66051e1b0 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -4167,6 +4167,9 @@ ripple\server\impl + + ripple\server\impl + ripple\server\impl diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index db0280a8d..47de42bf2 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -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 = # ssl_cert = # ssl_chain = diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 3a9ef7298..c285aaf39 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -221,18 +221,18 @@ public: void processTransaction ( std::shared_ptr& 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, - 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, - 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& lpLedger, + void getBookPage (bool bUnlimited, std::shared_ptr& 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 const& iTrans } void NetworkOPsImp::processTransaction (std::shared_ptr& 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& 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, - bool bAdmin, FailHard failType) + bool bUnlimited, FailHard failType) { std::lock_guard 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 } void NetworkOPsImp::doTransactionSync (std::shared_ptr transaction, - bool bAdmin, FailHard failType) + bool bUnlimited, FailHard failType) { std::unique_lock 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& 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::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 ret; @@ -1845,7 +1846,7 @@ std::vector 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& 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 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 ()) diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 79868373a..f56bf8d2c 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -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, - 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& 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; using MetaTxsList = std::vector; 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; //-------------------------------------------------------------------------- // diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 647896540..85d2b58da 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -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); } //------------------------------------------------------------------------------ diff --git a/src/ripple/core/LoadFeeTrack.h b/src/ripple/core/LoadFeeTrack.h index d1391e999..7e9df9272 100644 --- a/src/ripple/core/LoadFeeTrack.h +++ b/src/ripple/core/LoadFeeTrack.h @@ -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) { diff --git a/src/ripple/core/impl/LoadFeeTrack.cpp b/src/ripple/core/impl/LoadFeeTrack.cpp index ec40ca477..c59fb32fc 100644 --- a/src/ripple/core/impl/LoadFeeTrack.cpp +++ b/src/ripple/core/impl/LoadFeeTrack.cpp @@ -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: diff --git a/src/ripple/ledger/ApplyView.h b/src/ripple/ledger/ApplyView.h index 3ebe1f747..80147c581 100644 --- a/src/ripple/ledger/ApplyView.h +++ b/src/ripple/ledger/ApplyView.h @@ -47,7 +47,7 @@ enum ApplyFlags tapRETRY = 0x20, // Transaction came from a privileged source - tapADMIN = 0x400, + tapUNLIMITED = 0x400, }; inline diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index a0e50aeb1..5417856b4 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -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 diff --git a/src/ripple/resource/Consumer.h b/src/ripple/resource/Consumer.h index 9392f01ee..077e73978 100644 --- a/src/ripple/resource/Consumer.h +++ b/src/ripple/resource/Consumer.h @@ -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. diff --git a/src/ripple/resource/ResourceManager.h b/src/ripple/resource/ResourceManager.h index fe4edcee5..0500cf357 100644 --- a/src/ripple/resource/ResourceManager.h +++ b/src/ripple/resource/ResourceManager.h @@ -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; diff --git a/src/ripple/resource/impl/Consumer.cpp b/src/ripple/resource/impl/Consumer.cpp index 8c15d622a..9b7e1b212 100644 --- a/src/ripple/resource/impl/Consumer.cpp +++ b/src/ripple/resource/impl/Consumer.cpp @@ -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; diff --git a/src/ripple/resource/impl/Entry.h b/src/ripple/resource/impl/Entry.h index 2c77dfed1..2533e442d 100644 --- a/src/ripple/resource/impl/Entry.h +++ b/src/ripple/resource/impl/Entry.h @@ -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 diff --git a/src/ripple/resource/impl/Key.h b/src/ripple/resource/impl/Key.h index 5638b1e52..ea73497fa 100644 --- a/src/ripple/resource/impl/Key.h +++ b/src/ripple/resource/impl/Key.h @@ -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: diff --git a/src/ripple/resource/impl/Kind.h b/src/ripple/resource/impl/Kind.h index b7c2d71ba..0a63b4b89 100644 --- a/src/ripple/resource/impl/Kind.h +++ b/src/ripple/resource/impl/Kind.h @@ -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 }; } diff --git a/src/ripple/resource/impl/Logic.h b/src/ripple/resource/impl/Logic.h index fa89baa65..38cb16d2c 100644 --- a/src/ripple/resource/impl/Logic.h +++ b/src/ripple/resource/impl/Logic.h @@ -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 _(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 _(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 _(lock_); @@ -498,7 +472,7 @@ public: bool disconnect (Entry& entry) { - if (entry.admin()) + if (entry.isUnlimited()) return false; std::lock_guard _(lock_); diff --git a/src/ripple/resource/impl/ResourceManager.cpp b/src/ripple/resource/impl/ResourceManager.cpp index de80e4774..4e02b7726 100644 --- a/src/ripple/resource/impl/ResourceManager.cpp +++ b/src/ripple/resource/impl/ResourceManager.cpp @@ -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 diff --git a/src/ripple/rpc/Context.h b/src/ripple/rpc/Context.h index 2dd918e66..41ecc60f3 100644 --- a/src/ripple/rpc/Context.h +++ b/src/ripple/rpc/Context.h @@ -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; InfoSub::pointer infoSub; + Headers headers; }; } // RPC diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index 2a912d27c..b57787283 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -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) { diff --git a/src/ripple/rpc/handlers/AccountTxOld.cpp b/src/ripple/rpc/handlers/AccountTxOld.cpp index 9afed5fa6..2b0c513be 100644 --- a/src/ripple/rpc/handlers/AccountTxOld.cpp +++ b/src/ripple/rpc/handlers/AccountTxOld.cpp @@ -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) { diff --git a/src/ripple/rpc/handlers/BookOffers.cpp b/src/ripple/rpc/handlers/BookOffers.cpp index af7846041..f6fed2dd9 100644 --- a/src/ripple/rpc/handlers/BookOffers.cpp +++ b/src/ripple/rpc/handlers/BookOffers.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/LedgerData.cpp b/src/ripple/rpc/handlers/LedgerData.cpp index 1206ab0e7..4c8ec73c2 100644 --- a/src/ripple/rpc/handlers/LedgerData.cpp +++ b/src/ripple/rpc/handlers/LedgerData.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/LedgerHandler.cpp b/src/ripple/rpc/handlers/LedgerHandler.cpp index b77659b62..1cfdceaac 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.cpp +++ b/src/ripple/rpc/handlers/LedgerHandler.cpp @@ -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; } diff --git a/src/ripple/rpc/handlers/Ping.cpp b/src/ripple/rpc/handlers/Ping.cpp index 31116310b..b1caf97b8 100644 --- a/src/ripple/rpc/handlers/Ping.cpp +++ b/src/ripple/rpc/handlers/Ping.cpp @@ -19,6 +19,9 @@ #include #include +#include +#include +#include namespace ripple { @@ -28,7 +31,29 @@ struct Context; Json::Value doPing (RPC::Context& context) { - return Json::Value (Json::objectValue); + // 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 diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/ripple/rpc/handlers/RipplePathFind.cpp index d59230261..10288c229 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/rpc/handlers/RipplePathFind.cpp @@ -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; } diff --git a/src/ripple/rpc/handlers/Submit.cpp b/src/ripple/rpc/handlers/Submit.cpp index 6b1a973a0..29c680db7 100644 --- a/src/ripple/rpc/handlers/Submit.cpp +++ b/src/ripple/rpc/handlers/Submit.cpp @@ -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) { diff --git a/src/ripple/rpc/handlers/Subscribe.cpp b/src/ripple/rpc/handlers/Subscribe.cpp index e413fc1fd..0d92aa5d0 100644 --- a/src/ripple/rpc/handlers/Subscribe.cpp +++ b/src/ripple/rpc/handlers/Subscribe.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/TxHistory.cpp b/src/ripple/rpc/handlers/TxHistory.cpp index e8214e7fd..e7eb9bdd2 100644 --- a/src/ripple/rpc/handlers/TxHistory.cpp +++ b/src/ripple/rpc/handlers/TxHistory.cpp @@ -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; diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index c0be57864..302e5a793 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -112,7 +112,7 @@ namespace { error_code_i fillHandler (Context& context, boost::optional& 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_) - return callMethod (context, method, handler->name_, result); + { + 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; } diff --git a/src/ripple/rpc/impl/TransactionSign.cpp b/src/ripple/rpc/impl/TransactionSign.cpp index bf600dd5c..0ea2272d8 100644 --- a/src/ripple/rpc/impl/TransactionSign.cpp +++ b/src/ripple/rpc/impl/TransactionSign.cpp @@ -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&) { diff --git a/src/ripple/rpc/impl/TransactionSign.h b/src/ripple/rpc/impl/TransactionSign.h index 21d0d47bd..95517a772 100644 --- a/src/ripple/rpc/impl/TransactionSign.h +++ b/src/ripple/rpc/impl/TransactionSign.h @@ -65,14 +65,14 @@ Json::Value checkFee ( // Return a std::function<> that calls NetworkOPs::processTransaction. using ProcessTransactionFn = std::function& 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, - 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); }; } diff --git a/src/ripple/rpc/impl/Utilities.cpp b/src/ripple/rpc/impl/Utilities.cpp index 1b651809c..b2c3cee86 100644 --- a/src/ripple/rpc/impl/Utilities.cpp +++ b/src/ripple/rpc/impl/Utilities.cpp @@ -123,7 +123,7 @@ boost::optional 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; diff --git a/src/ripple/server/Port.h b/src/ripple/server/Port.h index 26b5fcc20..a20bf2fc7 100644 --- a/src/ripple/server/Port.h +++ b/src/ripple/server/Port.h @@ -41,6 +41,7 @@ struct Port std::uint16_t port = 0; std::set protocol; std::vector admin_ip; + std::vector 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 diff --git a/src/ripple/server/Role.h b/src/ripple/server/Role.h index 6956d81e8..e42ec60ee 100644 --- a/src/ripple/server/Role.h +++ b/src/ripple/server/Role.h @@ -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 diff --git a/src/ripple/server/Session.h b/src/ripple/server/Session.h index 6e609ef7c..7b280d5a9 100644 --- a/src/ripple/server/Session.h +++ b/src/ripple/server/Session.h @@ -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& diff --git a/src/ripple/server/impl/Peer.h b/src/ripple/server/impl/Peer.h index 7c014b90a..c1aab6646 100644 --- a/src/ripple/server/impl/Peer.h +++ b/src/ripple/server/impl/Peer.h @@ -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,9 +390,20 @@ Peer::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) diff --git a/src/ripple/server/impl/Port.cpp b/src/ripple/server/impl/Port.cpp new file mode 100644 index 000000000..b1931973e --- /dev/null +++ b/src/ripple/server/impl/Port.cpp @@ -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 + +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 diff --git a/src/ripple/server/impl/Role.cpp b/src/ripple/server/impl/Role.cpp index 0e6e642ed..3acd57172 100644 --- a/src/ripple/server/impl/Role.cpp +++ b/src/ripple/server/impl/Role.cpp @@ -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¶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; } 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); +} + } diff --git a/src/ripple/server/impl/ServerHandlerImp.cpp b/src/ripple/server/impl/ServerHandlerImp.cpp index 6a9a251e1..819598c49 100644 --- a/src/ripple/server/impl/ServerHandlerImp.cpp +++ b/src/ripple/server/impl/ServerHandlerImp.cpp @@ -204,7 +204,8 @@ ServerHandlerImp::processSession (std::shared_ptr const& session, std::shared_ptr 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 const& session, void ServerHandlerImp::processRequest (HTTP::Port const& port, std::string const& request, beast::IP::Endpoint const& remoteIPAddress, - Output&& output, std::shared_ptr jobCoro) + Output&& output, std::shared_ptr 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 ip; boost::optional port; boost::optional> admin_ip; + boost::optional> secure_gateway_ip; }; +void +populate (Section const& section, std::string const& field, std::ostream& log, + boost::optional>& ips, + bool allowAllIps, std::vector 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 (); + } + + 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 (); + } + + 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 (); - } - - 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 (); - } - - 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()) { diff --git a/src/ripple/server/impl/ServerHandlerImp.h b/src/ripple/server/impl/ServerHandlerImp.h index 5c6b75c53..a512d7c05 100644 --- a/src/ripple/server/impl/ServerHandlerImp.h +++ b/src/ripple/server/impl/ServerHandlerImp.h @@ -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); + std::shared_ptr jobCoro, + std::string forwardedFor, std::string user); // // PropertyStream diff --git a/src/ripple/unity/server.cpp b/src/ripple/unity/server.cpp index 6f9563ea8..1b874e582 100644 --- a/src/ripple/unity/server.cpp +++ b/src/ripple/unity/server.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/src/ripple/websocket/Connection.h b/src/ripple/websocket/Connection.h index e444276e0..d80f40df8 100644 --- a/src/ripple/websocket/Connection.h +++ b/src/ripple/websocket/Connection.h @@ -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 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 m_receiveQueue; NetworkOPs& m_netOPs; @@ -133,13 +136,18 @@ ConnectionImpl ::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 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 ::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 @@ -273,7 +287,8 @@ Json::Value ConnectionImpl ::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 ::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 ::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 ::invokeCommand ( template void ConnectionImpl ::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 (); diff --git a/src/ripple/websocket/Handler.h b/src/ripple/websocket/Handler.h index 864ce0c54..b8c2f365a 100644 --- a/src/ripple/websocket/Handler.h +++ b/src/ripple/websocket/Handler.h @@ -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)); diff --git a/src/websocketpp/websocketpp/transport/asio/connection.hpp b/src/websocketpp/websocketpp/transport/asio/connection.hpp index 3b787d32d..15e496cbd 100644 --- a/src/websocketpp/websocketpp/transport/asio/connection.hpp +++ b/src/websocketpp/websocketpp/transport/asio/connection.hpp @@ -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 get_identity() + { + return m_identity; + } + + protected: /// Initialize transport for reading /** * init_asio is called once immediately after construction to initialize @@ -477,7 +497,7 @@ protected: } timer_ptr post_timer; - + if (config::timeout_socket_post_init > 0) { post_timer = set_timer( config::timeout_socket_post_init, @@ -1032,7 +1052,7 @@ protected: * @param callback The function to call back * @param ec The status code */ - void handle_async_shutdown_timeout(timer_ptr, init_handler callback, + void handle_async_shutdown_timeout(timer_ptr, init_handler callback, lib::error_code const & ec) { lib::error_code ret_ec; @@ -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 m_identity; }; diff --git a/src/websocketpp_02/src/connection.hpp b/src/websocketpp_02/src/connection.hpp index c4885593d..a03a88a6e 100644 --- a/src/websocketpp_02/src/connection.hpp +++ b/src/websocketpp_02/src/connection.hpp @@ -753,6 +753,27 @@ public: boost::lock_guard 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 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 m_identity; }; // connection related types that it and its policy classes need. diff --git a/src/websocketpp_02/src/roles/server.hpp b/src/websocketpp_02/src/roles/server.hpp index e7d558755..4d9f836ff 100644 --- a/src/websocketpp_02/src/roles/server.hpp +++ b/src/websocketpp_02/src/roles/server.hpp @@ -913,6 +913,9 @@ void server::connection::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(