Merge pull request #6388

0937290 doc: mention RPC random cookie authentication in release notes (Wladimir J. van der Laan)
71cbeaa rpc: Implement random-cookie based authentication (Wladimir J. van der Laan)
This commit is contained in:
Wladimir J. van der Laan 2015-07-14 13:05:35 +02:00
commit fd5dfda939
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
5 changed files with 120 additions and 27 deletions

View File

@ -4,6 +4,21 @@ release-notes at release time)
Notable changes Notable changes
=============== ===============
Random-cookie RPC authentication
---------------------------------
When no `-rpcpassword` is specified, the daemon now uses a special 'cookie'
file for authentication. This file is generated with random content when the
daemon starts, and deleted when it exits. Its contents are used as
authentication token. Read access to this file controls who can access through
RPC. By default it is stored in the data directory but its location can be
overridden with the option `-rpccookiefile`.
This is similar to Tor's CookieAuthentication: see
https://www.torproject.org/docs/tor-manual.html.en
This allows running bitcoind without having to do any manual configuration.
Example header Example header
---------------------- ----------------------

View File

@ -97,12 +97,6 @@ static bool AppInitRPC(int argc, char* argv[])
UniValue CallRPC(const string& strMethod, const UniValue& params) UniValue CallRPC(const string& strMethod, const UniValue& params)
{ {
if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "")
throw runtime_error(strprintf(
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
"If the file does not exist, create it with owner-readable-only file permissions."),
GetConfigFile().string().c_str()));
// Connect to localhost // Connect to localhost
bool fUseSSL = GetBoolArg("-rpcssl", false); bool fUseSSL = GetBoolArg("-rpcssl", false);
boost::asio::io_service io_service; boost::asio::io_service io_service;
@ -116,10 +110,24 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
if (!fConnected) if (!fConnected)
throw CConnectionFailed("couldn't connect to server"); throw CConnectionFailed("couldn't connect to server");
// Find credentials to use
std::string strRPCUserColonPass;
if (mapArgs["-rpcpassword"] == "") {
// Try fall back to cookie-based authentication if no password is provided
if (!GetAuthCookie(&strRPCUserColonPass)) {
throw runtime_error(strprintf(
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
"If the file does not exist, create it with owner-readable-only file permissions."),
GetConfigFile().string().c_str()));
}
} else {
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
// HTTP basic authentication // HTTP basic authentication
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
map<string, string> mapRequestHeaders; map<string, string> mapRequestHeaders;
mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
// Send request // Send request
string strRequest = JSONRPCRequest(strMethod, params, 1); string strRequest = JSONRPCRequest(strMethod, params, 1);

View File

@ -6,6 +6,7 @@
#include "rpcprotocol.h" #include "rpcprotocol.h"
#include "clientversion.h" #include "clientversion.h"
#include "random.h"
#include "tinyformat.h" #include "tinyformat.h"
#include "util.h" #include "util.h"
#include "utilstrencodings.h" #include "utilstrencodings.h"
@ -13,6 +14,7 @@
#include "version.h" #include "version.h"
#include <stdint.h> #include <stdint.h>
#include <fstream>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@ -287,3 +289,68 @@ UniValue JSONRPCError(int code, const string& message)
error.push_back(Pair("message", message)); error.push_back(Pair("message", message));
return error; return error;
} }
/** Username used when cookie authentication is in use (arbitrary, only for
* recognizability in debugging/logging purposes)
*/
static const std::string COOKIEAUTH_USER = "__cookie__";
/** Default name for auth cookie file */
static const std::string COOKIEAUTH_FILE = ".cookie";
boost::filesystem::path GetAuthCookieFile()
{
boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
if (!path.is_complete()) path = GetDataDir() / path;
return path;
}
bool GenerateAuthCookie(std::string *cookie_out)
{
unsigned char rand_pwd[32];
GetRandBytes(rand_pwd, 32);
std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
/** the umask determines what permissions are used to create this file -
* these are set to 077 in init.cpp unless overridden with -sysperms.
*/
std::ofstream file;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open()) {
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
return false;
}
file << cookie;
file.close();
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
if (cookie_out)
*cookie_out = cookie;
return true;
}
bool GetAuthCookie(std::string *cookie_out)
{
std::ifstream file;
std::string cookie;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open())
return false;
std::getline(file, cookie);
file.close();
if (cookie_out)
*cookie_out = cookie;
return true;
}
void DeleteAuthCookie()
{
try {
boost::filesystem::remove(GetAuthCookieFile());
} catch (const boost::filesystem::filesystem_error& e) {
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
}
}

View File

@ -14,6 +14,7 @@
#include <boost/iostreams/stream.hpp> #include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
#include <boost/filesystem.hpp>
#include "univalue/univalue.h" #include "univalue/univalue.h"
@ -165,4 +166,13 @@ UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const Un
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
UniValue JSONRPCError(int code, const std::string& message); UniValue JSONRPCError(int code, const std::string& message);
/** Get name of RPC authentication cookie file */
boost::filesystem::path GetAuthCookieFile();
/** Generate a new RPC authentication cookie and write it to disk */
bool GenerateAuthCookie(std::string *cookie_out);
/** Read the RPC authentication cookie from disk */
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
#endif // BITCOIN_RPCPROTOCOL_H #endif // BITCOIN_RPCPROTOCOL_H

View File

@ -597,27 +597,18 @@ void StartRPCThreads()
strAllowed += subnet.ToString() + " "; strAllowed += subnet.ToString() + " ";
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
if (mapArgs["-rpcpassword"] == "") if (mapArgs["-rpcpassword"] == "")
{ {
unsigned char rand_pwd[32]; LogPrintf("No rpcpassword set - using random cookie authentication\n");
GetRandBytes(rand_pwd, 32); if (!GenerateAuthCookie(&strRPCUserColonPass)) {
uiInterface.ThreadSafeMessageBox(strprintf( uiInterface.ThreadSafeMessageBox(
_("To use bitcoind, or the -server option to bitcoin-qt, you must set an rpcpassword in the configuration file:\n" _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
"%s\n" "", CClientUIInterface::MSG_ERROR);
"It is recommended you use the following random password:\n" StartShutdown();
"rpcuser=bitcoinrpc\n" return;
"rpcpassword=%s\n" }
"(you do not need to remember this password)\n" } else {
"The username and password MUST NOT be the same.\n" strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
"If the file does not exist, create it with owner-readable-only file permissions.\n"
"It is also recommended to set alertnotify so you are notified of problems;\n"
"for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" admin@foo.com\n"),
GetConfigFile().string(),
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)),
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::SECURE);
StartShutdown();
return;
} }
assert(rpc_io_service == NULL); assert(rpc_io_service == NULL);
@ -768,6 +759,8 @@ void StopRPCThreads()
} }
deadlineTimers.clear(); deadlineTimers.clear();
DeleteAuthCookie();
rpc_io_service->stop(); rpc_io_service->stop();
g_rpcSignals.Stopped(); g_rpcSignals.Stopped();
if (rpc_worker_group != NULL) if (rpc_worker_group != NULL)