mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-19 13:27:35 +02:00
Merge d38dbaad98 into merged_master (Bitcoin PR bitcoin/bitcoin#28167)
This commit is contained in:
commit
cf5c455fa1
8 changed files with 132 additions and 11 deletions
|
|
@ -35,8 +35,10 @@ it will use a special cookie file for authentication. The cookie is generated wi
|
|||
content when the daemon starts, and deleted when it exits. Read access to this file
|
||||
controls who can access it through RPC.
|
||||
|
||||
By default the cookie is stored in the data directory, but it's location can be overridden
|
||||
with the option '-rpccookiefile'.
|
||||
By default the cookie is stored in the data directory, but its location can be
|
||||
overridden with the option `-rpccookiefile`. Default file permissions for the
|
||||
cookie are "owner" (i.e. user read/writeable) via default application-wide file
|
||||
umask of `0077`, but these can be overridden with the `-rpccookieperms` option.
|
||||
|
||||
This allows for running bitcoind without having to do any manual configuration.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include <netaddress.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <rpc/server.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <walletinitinterface.h>
|
||||
|
|
@ -19,6 +21,7 @@
|
|||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -291,8 +294,20 @@ static bool InitRPCAuthentication()
|
|||
{
|
||||
if (gArgs.GetArg("-rpcpassword", "") == "")
|
||||
{
|
||||
LogPrintf("Using random cookie authentication.\n");
|
||||
if (!GenerateAuthCookie(&strRPCUserColonPass)) {
|
||||
LogInfo("Using random cookie authentication.\n");
|
||||
|
||||
std::optional<fs::perms> cookie_perms{std::nullopt};
|
||||
auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
|
||||
if (cookie_perms_arg) {
|
||||
auto perm_opt = InterpretPermString(*cookie_perms_arg);
|
||||
if (!perm_opt) {
|
||||
LogInfo("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.\n", *cookie_perms_arg);
|
||||
return false;
|
||||
}
|
||||
cookie_perms = *perm_opt;
|
||||
}
|
||||
|
||||
if (!GenerateAuthCookie(&strRPCUserColonPass, cookie_perms)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -673,6 +673,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
|||
argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpccookieperms=<readable-by>", strprintf("Set permissions on the RPC auth cookie file so that it is readable by [owner|group|all] (default: owner [via umask 0077])"), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@
|
|||
|
||||
#include <rpc/request.h>
|
||||
|
||||
#include <util/fs.h>
|
||||
|
||||
#include <common/args.h>
|
||||
#include <logging.h>
|
||||
#include <random.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
|
|
@ -99,7 +98,7 @@ static fs::path GetAuthCookieFile(bool temp=false)
|
|||
|
||||
static bool g_generated_cookie = false;
|
||||
|
||||
bool GenerateAuthCookie(std::string *cookie_out)
|
||||
bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms)
|
||||
{
|
||||
const size_t COOKIE_SIZE = 32;
|
||||
unsigned char rand_pwd[COOKIE_SIZE];
|
||||
|
|
@ -113,7 +112,7 @@ bool GenerateAuthCookie(std::string *cookie_out)
|
|||
fs::path filepath_tmp = GetAuthCookieFile(true);
|
||||
file.open(filepath_tmp);
|
||||
if (!file.is_open()) {
|
||||
LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
|
||||
LogInfo("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
|
||||
return false;
|
||||
}
|
||||
file << cookie;
|
||||
|
|
@ -121,11 +120,21 @@ bool GenerateAuthCookie(std::string *cookie_out)
|
|||
|
||||
fs::path filepath = GetAuthCookieFile(false);
|
||||
if (!RenameOver(filepath_tmp, filepath)) {
|
||||
LogPrintf("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
|
||||
LogInfo("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
|
||||
return false;
|
||||
}
|
||||
if (cookie_perms) {
|
||||
std::error_code code;
|
||||
fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
|
||||
if (code) {
|
||||
LogInfo("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
g_generated_cookie = true;
|
||||
LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
|
||||
LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
|
||||
LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions()));
|
||||
|
||||
if (cookie_out)
|
||||
*cookie_out = cookie;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <univalue.h>
|
||||
#include <util/fs.h>
|
||||
|
||||
enum class JSONRPCVersion {
|
||||
V1_LEGACY,
|
||||
|
|
@ -23,7 +24,7 @@ UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue
|
|||
UniValue JSONRPCError(int code, const std::string& message);
|
||||
|
||||
/** Generate a new RPC authentication cookie and write it to disk */
|
||||
bool GenerateAuthCookie(std::string *cookie_out);
|
||||
bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms=std::nullopt);
|
||||
/** Read the RPC authentication cookie from disk */
|
||||
bool GetAuthCookie(std::string *cookie_out);
|
||||
/** Delete RPC authentication cookie from disk */
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
|
@ -269,3 +270,42 @@ bool TryCreateDirectories(const fs::path& p)
|
|||
// create_directories didn't create the directory, it had to have existed already
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string PermsToSymbolicString(fs::perms p)
|
||||
{
|
||||
std::string perm_str(9, '-');
|
||||
|
||||
auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
|
||||
if ((p & required_perm) != fs::perms::none) {
|
||||
perm_str[pos] = letter;
|
||||
}
|
||||
};
|
||||
|
||||
set_perm(0, fs::perms::owner_read, 'r');
|
||||
set_perm(1, fs::perms::owner_write, 'w');
|
||||
set_perm(2, fs::perms::owner_exec, 'x');
|
||||
set_perm(3, fs::perms::group_read, 'r');
|
||||
set_perm(4, fs::perms::group_write, 'w');
|
||||
set_perm(5, fs::perms::group_exec, 'x');
|
||||
set_perm(6, fs::perms::others_read, 'r');
|
||||
set_perm(7, fs::perms::others_write, 'w');
|
||||
set_perm(8, fs::perms::others_exec, 'x');
|
||||
|
||||
return perm_str;
|
||||
}
|
||||
|
||||
std::optional<fs::perms> InterpretPermString(const std::string& s)
|
||||
{
|
||||
if (s == "owner") {
|
||||
return fs::perms::owner_read | fs::perms::owner_write;
|
||||
} else if (s == "group") {
|
||||
return fs::perms::owner_read | fs::perms::owner_write |
|
||||
fs::perms::group_read;
|
||||
} else if (s == "all") {
|
||||
return fs::perms::owner_read | fs::perms::owner_write |
|
||||
fs::perms::group_read |
|
||||
fs::perms::others_read;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <cstdio>
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* Ensure file contents are fully committed to disk, using a platform-specific
|
||||
|
|
@ -62,6 +63,19 @@ void ReleaseDirectoryLocks();
|
|||
bool TryCreateDirectories(const fs::path& p);
|
||||
fs::path GetDefaultDataDir();
|
||||
|
||||
/** Convert fs::perms to symbolic string of the form 'rwxrwxrwx'
|
||||
*
|
||||
* @param[in] p the perms to be converted
|
||||
* @return Symbolic permissions string
|
||||
*/
|
||||
std::string PermsToSymbolicString(fs::perms p);
|
||||
/** Interpret a custom permissions level string as fs::perms
|
||||
*
|
||||
* @param[in] s Permission level string
|
||||
* @return Permissions as fs::perms
|
||||
*/
|
||||
std::optional<fs::perms> InterpretPermString(const std::string& s);
|
||||
|
||||
#ifdef WIN32
|
||||
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ from test_framework.util import (
|
|||
)
|
||||
|
||||
import http.client
|
||||
import os
|
||||
import platform
|
||||
import urllib.parse
|
||||
import subprocess
|
||||
from random import SystemRandom
|
||||
import string
|
||||
import configparser
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def call_with_auth(node, user, password):
|
||||
|
|
@ -85,6 +88,40 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
|||
self.log.info('Wrong...')
|
||||
assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status)
|
||||
|
||||
def test_rpccookieperms(self):
|
||||
p = {"owner": 0o600, "group": 0o640, "all": 0o644}
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
self.log.info(f"Skip cookie file permissions checks as OS detected as: {platform.system()=}")
|
||||
return
|
||||
|
||||
self.log.info('Check cookie file permissions can be set using -rpccookieperms')
|
||||
|
||||
cookie_file_path = self.nodes[1].chain_path / '.cookie'
|
||||
PERM_BITS_UMASK = 0o777
|
||||
|
||||
def test_perm(perm: Optional[str]):
|
||||
if not perm:
|
||||
perm = 'owner'
|
||||
self.restart_node(1)
|
||||
else:
|
||||
self.restart_node(1, extra_args=[f"-rpccookieperms={perm}"])
|
||||
|
||||
file_stat = os.stat(cookie_file_path)
|
||||
actual_perms = file_stat.st_mode & PERM_BITS_UMASK
|
||||
expected_perms = p[perm]
|
||||
assert_equal(expected_perms, actual_perms)
|
||||
|
||||
# Remove any leftover rpc{user|password} config options from previous tests
|
||||
self.nodes[1].replace_in_config([("rpcuser", "#rpcuser"), ("rpcpassword", "#rpcpassword")])
|
||||
|
||||
self.log.info('Check default cookie permission')
|
||||
test_perm(None)
|
||||
|
||||
self.log.info('Check custom cookie permissions')
|
||||
for perm in ["owner", "group", "all"]:
|
||||
test_perm(perm)
|
||||
|
||||
def run_test(self):
|
||||
self.conf_setup()
|
||||
self.log.info('Check correctness of the rpcauth config option')
|
||||
|
|
@ -116,6 +153,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
|||
(self.nodes[0].chain_path / ".cookie.tmp").mkdir()
|
||||
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
|
||||
|
||||
self.test_rpccookieperms()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
HTTPBasicsTest().main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue