mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 14:05:51 +00:00
Change the security model for RPC admin access.
This commit is contained in:
@@ -565,10 +565,6 @@ int commandLineRPC(const std::vector<std::string>& vCmd)
|
||||
RPCParser rpParser;
|
||||
Json::Value jvRpcParams(Json::arrayValue);
|
||||
|
||||
if (theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty())
|
||||
throw std::runtime_error("You must set rpcpassword=<password> in the configuration file. "
|
||||
"If the file does not exist, create it with owner-readable-only file permissions.");
|
||||
|
||||
if (vCmd.empty()) return 1; // 1 = print usage.
|
||||
|
||||
for (int i = 1; i != vCmd.size(); i++)
|
||||
@@ -597,8 +593,8 @@ int commandLineRPC(const std::vector<std::string>& vCmd)
|
||||
jvOutput = callRPC(
|
||||
theConfig.RPC_IP,
|
||||
theConfig.RPC_PORT,
|
||||
theConfig.RPC_USER,
|
||||
theConfig.RPC_PASSWORD,
|
||||
theConfig.RPC_ADMIN_USER,
|
||||
theConfig.RPC_ADMIN_PASSWORD,
|
||||
"",
|
||||
jvRequest.isMember("method") // Allow parser to rewrite method.
|
||||
? jvRequest["method"].asString()
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list"
|
||||
#define SECTION_PEER_START_MAX "peer_start_max"
|
||||
#define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote"
|
||||
#define SECTION_RPC_ADMIN_USER "rpc_admin_user"
|
||||
#define SECTION_RPC_ADMIN_PASSWORD "rpc_admin_password"
|
||||
#define SECTION_RPC_IP "rpc_ip"
|
||||
#define SECTION_RPC_PORT "rpc_port"
|
||||
#define SECTION_RPC_STARTUP "rpc_startup"
|
||||
@@ -179,8 +181,6 @@ Config::Config()
|
||||
LEDGER_SECONDS = 60;
|
||||
LEDGER_CREATOR = false;
|
||||
|
||||
RPC_USER = "admin";
|
||||
RPC_PASSWORD = "pass";
|
||||
RPC_ALLOW_REMOTE = false;
|
||||
|
||||
PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST;
|
||||
@@ -298,6 +298,8 @@ void Config::load()
|
||||
if (sectionSingleB(secConfig, SECTION_PEER_PRIVATE, strTemp))
|
||||
PEER_PRIVATE = boost::lexical_cast<bool>(strTemp);
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_USER, RPC_ADMIN_USER);
|
||||
(void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_PASSWORD, RPC_ADMIN_PASSWORD);
|
||||
(void) sectionSingleB(secConfig, SECTION_RPC_IP, RPC_IP);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp))
|
||||
|
||||
@@ -112,8 +112,8 @@ public:
|
||||
// RPC parameters
|
||||
std::string RPC_IP;
|
||||
int RPC_PORT;
|
||||
std::string RPC_USER;
|
||||
std::string RPC_PASSWORD;
|
||||
std::string RPC_ADMIN_USER;
|
||||
std::string RPC_ADMIN_PASSWORD;
|
||||
bool RPC_ALLOW_REMOTE;
|
||||
std::vector<Json::Value> RPC_STARTUP;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ bool RPCDoor::isClientAllowed(const std::string& ip)
|
||||
{
|
||||
if (theConfig.RPC_ALLOW_REMOTE)
|
||||
return true;
|
||||
|
||||
if (ip == "127.0.0.1")
|
||||
return true;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Json::Value rpcError(int iError, Json::Value jvResult)
|
||||
{ rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." },
|
||||
{ rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." },
|
||||
{ rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." },
|
||||
{ rpcFORBIDDEN, "forbidden", "Bad credentials." },
|
||||
{ rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." },
|
||||
{ rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." },
|
||||
{ rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." },
|
||||
|
||||
@@ -7,6 +7,7 @@ enum {
|
||||
rpcSUCCESS = 0,
|
||||
rpcBAD_SYNTAX, // Must be 1 to print usage to command line.
|
||||
rpcJSON_RPC,
|
||||
rpcFORBIDDEN,
|
||||
|
||||
// Error numbers beyond this line are not stable between versions.
|
||||
// Programs should use error tokens.
|
||||
|
||||
@@ -25,6 +25,38 @@
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp)
|
||||
{
|
||||
int iRole;
|
||||
bool bPasswordSupplied = jvRequest.isMember("user") || jvRequest.isMember("password");
|
||||
bool bPasswordRequired = !theConfig.RPC_ADMIN_USER.empty() || !theConfig.RPC_ADMIN_PASSWORD.empty();
|
||||
|
||||
bool bPasswordWrong = bPasswordSupplied
|
||||
? bPasswordRequired
|
||||
// Supplied, required, and incorrect.
|
||||
? theConfig.RPC_ADMIN_USER != (jvRequest.isMember("user") ? jvRequest["user"].asString() : "")
|
||||
|| theConfig.RPC_ADMIN_PASSWORD != (jvRequest.isMember("user") ? jvRequest["password"].asString() : "")
|
||||
// Supplied and not required.
|
||||
: true
|
||||
: false;
|
||||
// Meets IP restriction for admin.
|
||||
bool bAdminIP = strRemoteIp == "127.0.0.1";
|
||||
|
||||
if (bPasswordWrong // Wrong
|
||||
|| (bPasswordSupplied && !bAdminIP)) // Supplied and doesn't meet IP filter.
|
||||
{
|
||||
iRole = RPCHandler::FORBID;
|
||||
}
|
||||
// If supplied, password is correct.
|
||||
else
|
||||
{
|
||||
// Allow admin, if from admin IP and no password is required or it was supplied and correct.
|
||||
iRole = bAdminIP && (!bPasswordRequired || bPasswordSupplied) ? RPCHandler::ADMIN : RPCHandler::GUEST;
|
||||
}
|
||||
|
||||
return iRole;
|
||||
}
|
||||
|
||||
RPCHandler::RPCHandler(NetworkOPs* netOps)
|
||||
{
|
||||
mNetOps = netOps;
|
||||
@@ -2314,11 +2346,8 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
||||
|
||||
if (jvRequest.isMember("url"))
|
||||
{
|
||||
// Temporarily off.
|
||||
#if 0
|
||||
if (mRole != ADMIN)
|
||||
return rpcError(rpcNO_PERMISSION);
|
||||
#endif
|
||||
|
||||
std::string strUrl = jvRequest["url"].asString();
|
||||
std::string strUsername = jvRequest.isMember("username") ? jvRequest["username"].asString() : "";
|
||||
|
||||
@@ -110,7 +110,7 @@ class RPCHandler
|
||||
|
||||
public:
|
||||
|
||||
enum { GUEST, USER, ADMIN };
|
||||
enum { GUEST, USER, ADMIN, FORBID };
|
||||
|
||||
RPCHandler(NetworkOPs* netOps);
|
||||
RPCHandler(NetworkOPs* netOps, InfoSub* infoSub);
|
||||
@@ -136,5 +136,7 @@ public:
|
||||
static Json::Value runHandler(const std::string& name, const Json::Value& params);
|
||||
};
|
||||
|
||||
int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp);
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
|
||||
@@ -32,9 +32,6 @@ RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwor
|
||||
void RPCServer::connected()
|
||||
{
|
||||
//std::cerr << "RPC request" << std::endl;
|
||||
if (mSocket.remote_endpoint().address().to_string()=="127.0.0.1") mRole = RPCHandler::ADMIN;
|
||||
else mRole = RPCHandler::GUEST;
|
||||
|
||||
boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n",
|
||||
boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error));
|
||||
}
|
||||
@@ -114,16 +111,17 @@ std::string RPCServer::handleRequest(const std::string& requestStr)
|
||||
Json::Value id;
|
||||
|
||||
// Parse request
|
||||
Json::Value valRequest;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(requestStr, valRequest) || valRequest.isNull() || !valRequest.isObject())
|
||||
Json::Value jvRequest;
|
||||
Json::Reader reader;
|
||||
|
||||
if (!reader.parse(requestStr, jvRequest) || jvRequest.isNull() || !jvRequest.isObject())
|
||||
return(HTTPReply(400, "unable to parse request"));
|
||||
|
||||
// Parse id now so errors from here on will have the id
|
||||
id = valRequest["id"];
|
||||
id = jvRequest["id"];
|
||||
|
||||
// Parse method
|
||||
Json::Value valMethod = valRequest["method"];
|
||||
Json::Value valMethod = jvRequest["method"];
|
||||
if (valMethod.isNull())
|
||||
return(HTTPReply(400, "null method"));
|
||||
if (!valMethod.isString())
|
||||
@@ -131,11 +129,24 @@ std::string RPCServer::handleRequest(const std::string& requestStr)
|
||||
std::string strMethod = valMethod.asString();
|
||||
|
||||
// Parse params
|
||||
Json::Value valParams = valRequest["params"];
|
||||
Json::Value valParams = jvRequest["params"];
|
||||
|
||||
if (valParams.isNull())
|
||||
{
|
||||
valParams = Json::Value(Json::arrayValue);
|
||||
}
|
||||
else if (!valParams.isArray())
|
||||
return(HTTPReply(400, "params unparseable"));
|
||||
{
|
||||
return HTTPReply(400, "params unparseable");
|
||||
}
|
||||
|
||||
mRole = iAdminGet(jvRequest, mSocket.remote_endpoint().address().to_string());
|
||||
|
||||
if (RPCHandler::FORBID == mRole)
|
||||
{
|
||||
// XXX This needs rate limiting to prevent brute forcing password.
|
||||
return HTTPReply(403, "Forbidden");
|
||||
}
|
||||
|
||||
RPCHandler mRPCHandler(mNetOps);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "CallRPC.h"
|
||||
#include "InstanceCounter.h"
|
||||
#include "Log.h"
|
||||
#include "RPCErr.h"
|
||||
|
||||
DEFINE_INSTANCE(WebSocketConnection);
|
||||
|
||||
@@ -91,9 +92,18 @@ public:
|
||||
RPCHandler mRPCHandler(&mNetwork, this);
|
||||
Json::Value jvResult(Json::objectValue);
|
||||
|
||||
jvResult["result"] = mRPCHandler.doCommand(
|
||||
jvRequest,
|
||||
mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN);
|
||||
int iRole = mHandler->getPublic()
|
||||
? RPCHandler::GUEST // Don't check on the public interface.
|
||||
: iAdminGet(jvRequest, "127.0.0.1"); // XXX Fix this to return the remote IP.
|
||||
|
||||
if (RPCHandler::FORBID == iRole)
|
||||
{
|
||||
jvResult["result"] = rpcError(rpcFORBIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
jvResult["result"] = mRPCHandler.doCommand(jvRequest, iRole);
|
||||
}
|
||||
|
||||
// Currently we will simply unwrap errors returned by the RPC
|
||||
// API, in the future maybe we can make the responses
|
||||
|
||||
Reference in New Issue
Block a user