mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-12 19:20:42 +02:00
Merge 20407 via rpcauthfile-28+knots
This commit is contained in:
commit
59ccec7495
@ -26,6 +26,7 @@ def main():
|
||||
parser.add_argument('username', help='the username for authentication')
|
||||
parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?')
|
||||
parser.add_argument("-j", "--json", help="output to json instead of plain-text", action='store_true')
|
||||
parser.add_argument('--output', dest='output', help='file to store credentials, to be used with -rpcauthfile')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.password:
|
||||
@ -36,13 +37,21 @@ def main():
|
||||
# Create 16 byte hex salt
|
||||
salt = generate_salt(16)
|
||||
password_hmac = password_to_hmac(salt, args.password)
|
||||
rpcauth = f'{args.username}:{salt}${password_hmac}'
|
||||
|
||||
if args.output:
|
||||
file = open(args.output, "a", encoding="utf8")
|
||||
file.write(rpcauth + "\n")
|
||||
|
||||
if args.json:
|
||||
odict={'username':args.username, 'password':args.password, 'rpcauth':f'{args.username}:{salt}${password_hmac}'}
|
||||
odict={'username':args.username, 'password':args.password}
|
||||
if not args.output:
|
||||
odict['rpcauth'] = rpcauth
|
||||
print(json.dumps(odict))
|
||||
else:
|
||||
print('String to be appended to bitcoin.conf:')
|
||||
print(f'rpcauth={args.username}:{salt}${password_hmac}')
|
||||
if not args.output:
|
||||
print('String to be appended to bitcoin.conf:')
|
||||
print(f'rpcauth={rpcauth}')
|
||||
print(f'Your password:\n{args.password}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <walletinitinterface.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -317,7 +318,7 @@ static bool InitRPCAuthentication()
|
||||
LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n");
|
||||
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
||||
}
|
||||
if (!gArgs.GetArgs("-rpcauth").empty()) {
|
||||
if (!(gArgs.IsArgNegated("-rpcauth") || (gArgs.GetArgs("-rpcauth").empty() && gArgs.GetArgs("-rpcauthfile").empty()))) {
|
||||
LogPrintf("Using rpcauth authentication.\n");
|
||||
for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) {
|
||||
if (rpcauth.empty()) continue;
|
||||
@ -332,6 +333,21 @@ static bool InitRPCAuthentication()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const std::string& path : gArgs.GetArgs("-rpcauthfile")) {
|
||||
std::ifstream file;
|
||||
file.open(path);
|
||||
if (!file.is_open()) continue;
|
||||
std::string rpcauth;
|
||||
while (std::getline(file, rpcauth)) {
|
||||
std::vector<std::string> fields{SplitString(rpcauth, ':')};
|
||||
const std::vector<std::string> salt_hmac{SplitString(fields.back(), '$')};
|
||||
if (fields.size() == 2 && salt_hmac.size() == 2) {
|
||||
fields.pop_back();
|
||||
fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end());
|
||||
g_rpcauth.push_back(fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));
|
||||
|
@ -673,6 +673,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid values for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcauthfile=<userpw>", "A file with a single lines with same format as rpcauth. This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
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);
|
||||
|
@ -12,6 +12,7 @@ from test_framework.util import (
|
||||
|
||||
import http.client
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import urllib.parse
|
||||
import subprocess
|
||||
@ -40,6 +41,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
self.supports_cli = False
|
||||
|
||||
def conf_setup(self):
|
||||
self.authinfo = []
|
||||
|
||||
#Append rpcauth to bitcoin.conf before initialization
|
||||
self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
|
||||
rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
|
||||
@ -64,10 +67,42 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
rpcauth3 = lines[1]
|
||||
self.password = lines[3]
|
||||
|
||||
# Generate rpcauthfile with one entry
|
||||
username = 'rpcauth_single_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, "--output", Path(self.options.tmpdir) / 'rpcauth_single', username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
self.authinfo.append( (username, lines[1]) )
|
||||
|
||||
# Generate rpcauthfile with two entries
|
||||
username = 'rpcauth_multi1_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, "--output", Path(self.options.tmpdir) / 'rpcauth_multi', username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
self.authinfo.append( (username, lines[1]) )
|
||||
# Blank lines in between should get ignored
|
||||
with open(Path(self.options.tmpdir) / 'rpcauth_multi', "a", encoding='utf8') as f:
|
||||
f.write("\n\n")
|
||||
username = 'rpcauth_multi2_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, "--output", Path(self.options.tmpdir) / 'rpcauth_multi', username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
self.authinfo.append( (username, lines[1]) )
|
||||
|
||||
# Hand-generated rpcauthfile with one entry and no newline
|
||||
username = 'rpcauth_nonewline_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
assert "\n" not in lines[1]
|
||||
assert lines[1][:8] == 'rpcauth='
|
||||
with open(Path(self.options.tmpdir) / 'rpcauth_nonewline', "a", encoding='utf8') as f:
|
||||
f.write(lines[1][8:])
|
||||
self.authinfo.append( (username, lines[3]) )
|
||||
|
||||
with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
|
||||
f.write(rpcauth + "\n")
|
||||
f.write(rpcauth2 + "\n")
|
||||
f.write(rpcauth3 + "\n")
|
||||
f.write("rpcauthfile=rpcauth_single\n")
|
||||
f.write("rpcauthfile=rpcauth_multi\n")
|
||||
f.write("rpcauthfile=rpcauth_nonewline\n")
|
||||
with open(self.nodes[1].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
|
||||
f.write("rpcuser={}\n".format(self.rpcuser))
|
||||
f.write("rpcpassword={}\n".format(self.rpcpassword))
|
||||
@ -149,6 +184,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
self.test_auth(self.nodes[0], 'rt', self.rtpassword)
|
||||
self.test_auth(self.nodes[0], 'rt2', self.rt2password)
|
||||
self.test_auth(self.nodes[0], self.user, self.password)
|
||||
for info in self.authinfo:
|
||||
self.test_auth(self.nodes[0], *info)
|
||||
|
||||
self.log.info('Check correctness of the rpcuser/rpcpassword config options')
|
||||
url = urllib.parse.urlparse(self.nodes[1].url)
|
||||
@ -166,24 +203,34 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
# ...without disrupting usage of other -rpcauth tokens
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
|
||||
self.log.info('Check -norpcauth disables all previous -rpcauth params')
|
||||
self.log.info('Check -norpcauth disables all previous -rpcauth* params')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth'])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(401, call_with_auth(self.nodes[0], *info).status)
|
||||
|
||||
self.log.info('Check -norpcauth can be reversed with -rpcauth')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', '-rpcauth'])
|
||||
# FIXME: assert_equal(200, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
|
||||
self.log.info('Check -norpcauth followed by a specific -rpcauth=* restores config file -rpcauth=* values too')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', rpcauth_abc])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', '-rpcauth='])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
|
||||
self.log.info('Check -rpcauth are validated')
|
||||
self.stop_node(0)
|
||||
|
Loading…
Reference in New Issue
Block a user