Change the security model for RPC admin access.

This commit is contained in:
Arthur Britto
2013-01-18 00:36:22 -08:00
parent f6202011fd
commit bda80d4144
11 changed files with 98 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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." },

View File

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

View File

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

View File

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

View File

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

View File

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