mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-03 09:25:51 +00:00
Change the security model for RPC admin access.
This commit is contained in:
@@ -83,12 +83,12 @@
|
|||||||
#
|
#
|
||||||
# [peer_ip]:
|
# [peer_ip]:
|
||||||
# IP address or domain to bind to allow external connections from peers.
|
# IP address or domain to bind to allow external connections from peers.
|
||||||
# Defaults to not allow external connections from peers.
|
# Defaults to not binding, which disallows external connections from peers.
|
||||||
#
|
#
|
||||||
# Examples: 0.0.0.0 - Bind on all interfaces.
|
# Examples: 0.0.0.0 - Bind on all interfaces.
|
||||||
#
|
#
|
||||||
# [peer_port]:
|
# [peer_port]:
|
||||||
# Port to bind to allow external connections from peers.
|
# If peer_ip is supplied, corresponding port to bind to for peer connections.
|
||||||
#
|
#
|
||||||
# [peer_private]:
|
# [peer_private]:
|
||||||
# 0 or 1.
|
# 0 or 1.
|
||||||
@@ -97,14 +97,27 @@
|
|||||||
#
|
#
|
||||||
# [rpc_ip]:
|
# [rpc_ip]:
|
||||||
# IP address or domain to bind to allow insecure RPC connections.
|
# IP address or domain to bind to allow insecure RPC connections.
|
||||||
# Defaults to not allow RPC connections.
|
# Defaults to not binding, which disallows RPC connections.
|
||||||
#
|
#
|
||||||
# [rpc_port]:
|
# [rpc_port]:
|
||||||
# Port to bind to if allowing insecure RPC connections.
|
# If rpc_ip is supplied, corresponding port to bind to for peer connections.
|
||||||
#
|
#
|
||||||
# [rpc_allow_remote]:
|
# [rpc_allow_remote]:
|
||||||
# 0 or 1.
|
# 0 or 1.
|
||||||
# 0: only allows RPC connections from 127.0.0.1. [default]
|
# 0: Allow RPC connections only from 127.0.0.1. [default]
|
||||||
|
# 1: Allow RPC connections from any IP.
|
||||||
|
#
|
||||||
|
# [rpc_admin_user]:
|
||||||
|
# As a server, require a this user to specified and require admin_password to
|
||||||
|
# be checked for RPC admin functions.
|
||||||
|
#
|
||||||
|
# As a client, supply this to the server.
|
||||||
|
#
|
||||||
|
# [rpc_admin_password]:
|
||||||
|
# As a server, require a this password to specified and require admin_user to
|
||||||
|
# be checked for RPC admin functions.
|
||||||
|
#
|
||||||
|
# As a client, supply this to the server.
|
||||||
#
|
#
|
||||||
# [websocket_public_ip]:
|
# [websocket_public_ip]:
|
||||||
# IP address or domain to bind to allow untrusted connections from clients.
|
# IP address or domain to bind to allow untrusted connections from clients.
|
||||||
|
|||||||
@@ -565,10 +565,6 @@ int commandLineRPC(const std::vector<std::string>& vCmd)
|
|||||||
RPCParser rpParser;
|
RPCParser rpParser;
|
||||||
Json::Value jvRpcParams(Json::arrayValue);
|
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.
|
if (vCmd.empty()) return 1; // 1 = print usage.
|
||||||
|
|
||||||
for (int i = 1; i != vCmd.size(); i++)
|
for (int i = 1; i != vCmd.size(); i++)
|
||||||
@@ -597,8 +593,8 @@ int commandLineRPC(const std::vector<std::string>& vCmd)
|
|||||||
jvOutput = callRPC(
|
jvOutput = callRPC(
|
||||||
theConfig.RPC_IP,
|
theConfig.RPC_IP,
|
||||||
theConfig.RPC_PORT,
|
theConfig.RPC_PORT,
|
||||||
theConfig.RPC_USER,
|
theConfig.RPC_ADMIN_USER,
|
||||||
theConfig.RPC_PASSWORD,
|
theConfig.RPC_ADMIN_PASSWORD,
|
||||||
"",
|
"",
|
||||||
jvRequest.isMember("method") // Allow parser to rewrite method.
|
jvRequest.isMember("method") // Allow parser to rewrite method.
|
||||||
? jvRequest["method"].asString()
|
? jvRequest["method"].asString()
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
#define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list"
|
#define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list"
|
||||||
#define SECTION_PEER_START_MAX "peer_start_max"
|
#define SECTION_PEER_START_MAX "peer_start_max"
|
||||||
#define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote"
|
#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_IP "rpc_ip"
|
||||||
#define SECTION_RPC_PORT "rpc_port"
|
#define SECTION_RPC_PORT "rpc_port"
|
||||||
#define SECTION_RPC_STARTUP "rpc_startup"
|
#define SECTION_RPC_STARTUP "rpc_startup"
|
||||||
@@ -179,8 +181,6 @@ Config::Config()
|
|||||||
LEDGER_SECONDS = 60;
|
LEDGER_SECONDS = 60;
|
||||||
LEDGER_CREATOR = false;
|
LEDGER_CREATOR = false;
|
||||||
|
|
||||||
RPC_USER = "admin";
|
|
||||||
RPC_PASSWORD = "pass";
|
|
||||||
RPC_ALLOW_REMOTE = false;
|
RPC_ALLOW_REMOTE = false;
|
||||||
|
|
||||||
PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST;
|
PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST;
|
||||||
@@ -298,6 +298,8 @@ void Config::load()
|
|||||||
if (sectionSingleB(secConfig, SECTION_PEER_PRIVATE, strTemp))
|
if (sectionSingleB(secConfig, SECTION_PEER_PRIVATE, strTemp))
|
||||||
PEER_PRIVATE = boost::lexical_cast<bool>(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);
|
(void) sectionSingleB(secConfig, SECTION_RPC_IP, RPC_IP);
|
||||||
|
|
||||||
if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp))
|
if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp))
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ public:
|
|||||||
// RPC parameters
|
// RPC parameters
|
||||||
std::string RPC_IP;
|
std::string RPC_IP;
|
||||||
int RPC_PORT;
|
int RPC_PORT;
|
||||||
std::string RPC_USER;
|
std::string RPC_ADMIN_USER;
|
||||||
std::string RPC_PASSWORD;
|
std::string RPC_ADMIN_PASSWORD;
|
||||||
bool RPC_ALLOW_REMOTE;
|
bool RPC_ALLOW_REMOTE;
|
||||||
std::vector<Json::Value> RPC_STARTUP;
|
std::vector<Json::Value> RPC_STARTUP;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ bool RPCDoor::isClientAllowed(const std::string& ip)
|
|||||||
{
|
{
|
||||||
if (theConfig.RPC_ALLOW_REMOTE)
|
if (theConfig.RPC_ALLOW_REMOTE)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (ip == "127.0.0.1")
|
if (ip == "127.0.0.1")
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Json::Value rpcError(int iError, Json::Value jvResult)
|
|||||||
{ rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." },
|
{ rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." },
|
||||||
{ rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." },
|
{ rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." },
|
||||||
{ rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." },
|
{ rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." },
|
||||||
|
{ rpcFORBIDDEN, "forbidden", "Bad credentials." },
|
||||||
{ rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." },
|
{ rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." },
|
||||||
{ rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." },
|
{ rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." },
|
||||||
{ rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." },
|
{ rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ enum {
|
|||||||
rpcSUCCESS = 0,
|
rpcSUCCESS = 0,
|
||||||
rpcBAD_SYNTAX, // Must be 1 to print usage to command line.
|
rpcBAD_SYNTAX, // Must be 1 to print usage to command line.
|
||||||
rpcJSON_RPC,
|
rpcJSON_RPC,
|
||||||
|
rpcFORBIDDEN,
|
||||||
|
|
||||||
// Error numbers beyond this line are not stable between versions.
|
// Error numbers beyond this line are not stable between versions.
|
||||||
// Programs should use error tokens.
|
// Programs should use error tokens.
|
||||||
|
|||||||
@@ -25,6 +25,38 @@
|
|||||||
|
|
||||||
SETUP_LOG();
|
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)
|
RPCHandler::RPCHandler(NetworkOPs* netOps)
|
||||||
{
|
{
|
||||||
mNetOps = netOps;
|
mNetOps = netOps;
|
||||||
@@ -2314,11 +2346,8 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
|||||||
|
|
||||||
if (jvRequest.isMember("url"))
|
if (jvRequest.isMember("url"))
|
||||||
{
|
{
|
||||||
// Temporarily off.
|
|
||||||
#if 0
|
|
||||||
if (mRole != ADMIN)
|
if (mRole != ADMIN)
|
||||||
return rpcError(rpcNO_PERMISSION);
|
return rpcError(rpcNO_PERMISSION);
|
||||||
#endif
|
|
||||||
|
|
||||||
std::string strUrl = jvRequest["url"].asString();
|
std::string strUrl = jvRequest["url"].asString();
|
||||||
std::string strUsername = jvRequest.isMember("username") ? jvRequest["username"].asString() : "";
|
std::string strUsername = jvRequest.isMember("username") ? jvRequest["username"].asString() : "";
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class RPCHandler
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum { GUEST, USER, ADMIN };
|
enum { GUEST, USER, ADMIN, FORBID };
|
||||||
|
|
||||||
RPCHandler(NetworkOPs* netOps);
|
RPCHandler(NetworkOPs* netOps);
|
||||||
RPCHandler(NetworkOPs* netOps, InfoSub* infoSub);
|
RPCHandler(NetworkOPs* netOps, InfoSub* infoSub);
|
||||||
@@ -136,5 +136,7 @@ public:
|
|||||||
static Json::Value runHandler(const std::string& name, const Json::Value& params);
|
static Json::Value runHandler(const std::string& name, const Json::Value& params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
// vim:ts=4
|
// vim:ts=4
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwor
|
|||||||
void RPCServer::connected()
|
void RPCServer::connected()
|
||||||
{
|
{
|
||||||
//std::cerr << "RPC request" << std::endl;
|
//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::asio::async_read_until(mSocket, mLineBuffer, "\r\n",
|
||||||
boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error));
|
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;
|
Json::Value id;
|
||||||
|
|
||||||
// Parse request
|
// Parse request
|
||||||
Json::Value valRequest;
|
Json::Value jvRequest;
|
||||||
Json::Reader reader;
|
Json::Reader reader;
|
||||||
if (!reader.parse(requestStr, valRequest) || valRequest.isNull() || !valRequest.isObject())
|
|
||||||
|
if (!reader.parse(requestStr, jvRequest) || jvRequest.isNull() || !jvRequest.isObject())
|
||||||
return(HTTPReply(400, "unable to parse request"));
|
return(HTTPReply(400, "unable to parse request"));
|
||||||
|
|
||||||
// Parse id now so errors from here on will have the id
|
// Parse id now so errors from here on will have the id
|
||||||
id = valRequest["id"];
|
id = jvRequest["id"];
|
||||||
|
|
||||||
// Parse method
|
// Parse method
|
||||||
Json::Value valMethod = valRequest["method"];
|
Json::Value valMethod = jvRequest["method"];
|
||||||
if (valMethod.isNull())
|
if (valMethod.isNull())
|
||||||
return(HTTPReply(400, "null method"));
|
return(HTTPReply(400, "null method"));
|
||||||
if (!valMethod.isString())
|
if (!valMethod.isString())
|
||||||
@@ -131,11 +129,24 @@ std::string RPCServer::handleRequest(const std::string& requestStr)
|
|||||||
std::string strMethod = valMethod.asString();
|
std::string strMethod = valMethod.asString();
|
||||||
|
|
||||||
// Parse params
|
// Parse params
|
||||||
Json::Value valParams = valRequest["params"];
|
Json::Value valParams = jvRequest["params"];
|
||||||
|
|
||||||
if (valParams.isNull())
|
if (valParams.isNull())
|
||||||
|
{
|
||||||
valParams = Json::Value(Json::arrayValue);
|
valParams = Json::Value(Json::arrayValue);
|
||||||
|
}
|
||||||
else if (!valParams.isArray())
|
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);
|
RPCHandler mRPCHandler(mNetOps);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "CallRPC.h"
|
#include "CallRPC.h"
|
||||||
#include "InstanceCounter.h"
|
#include "InstanceCounter.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
#include "RPCErr.h"
|
||||||
|
|
||||||
DEFINE_INSTANCE(WebSocketConnection);
|
DEFINE_INSTANCE(WebSocketConnection);
|
||||||
|
|
||||||
@@ -91,9 +92,18 @@ public:
|
|||||||
RPCHandler mRPCHandler(&mNetwork, this);
|
RPCHandler mRPCHandler(&mNetwork, this);
|
||||||
Json::Value jvResult(Json::objectValue);
|
Json::Value jvResult(Json::objectValue);
|
||||||
|
|
||||||
jvResult["result"] = mRPCHandler.doCommand(
|
int iRole = mHandler->getPublic()
|
||||||
jvRequest,
|
? RPCHandler::GUEST // Don't check on the public interface.
|
||||||
mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN);
|
: 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
|
// Currently we will simply unwrap errors returned by the RPC
|
||||||
// API, in the future maybe we can make the responses
|
// API, in the future maybe we can make the responses
|
||||||
|
|||||||
Reference in New Issue
Block a user