Add Bitcoin Core RPC cookie authentication option

This commit is contained in:
Antoni Spaanderman 2023-03-26 16:39:45 +02:00
parent e5efc2957a
commit e9386ec003
No known key found for this signature in database
GPG Key ID: AE0B68E552E5DF8C
11 changed files with 78 additions and 16 deletions

View File

@ -35,7 +35,9 @@
"PORT": 8332, "PORT": 8332,
"USERNAME": "mempool", "USERNAME": "mempool",
"PASSWORD": "mempool", "PASSWORD": "mempool",
"TIMEOUT": 60000 "TIMEOUT": 60000,
"COOKIE": false,
"COOKIE_PATH": "/path/to/bitcoin/.cookie"
}, },
"ELECTRUM": { "ELECTRUM": {
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
@ -52,7 +54,9 @@
"PORT": 8332, "PORT": 8332,
"USERNAME": "mempool", "USERNAME": "mempool",
"PASSWORD": "mempool", "PASSWORD": "mempool",
"TIMEOUT": 60000 "TIMEOUT": 60000,
"COOKIE": false,
"COOKIE_PATH": "/path/to/bitcoin/.cookie"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,

View File

@ -36,7 +36,9 @@
"PORT": 15, "PORT": 15,
"USERNAME": "__CORE_RPC_USERNAME__", "USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__", "PASSWORD": "__CORE_RPC_PASSWORD__",
"TIMEOUT": 1000 "TIMEOUT": 1000,
"COOKIE": "__CORE_RPC_COOKIE__",
"COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
}, },
"ELECTRUM": { "ELECTRUM": {
"HOST": "__ELECTRUM_HOST__", "HOST": "__ELECTRUM_HOST__",
@ -53,7 +55,9 @@
"PORT": 17, "PORT": 17,
"USERNAME": "__SECOND_CORE_RPC_USERNAME__", "USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__", "PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
"TIMEOUT": 2000 "TIMEOUT": 2000,
"COOKIE": "__SECOND_CORE_RPC_COOKIE__",
"COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": false, "ENABLED": false,

View File

@ -54,7 +54,9 @@ describe('Mempool Backend Config', () => {
PORT: 8332, PORT: 8332,
USERNAME: 'mempool', USERNAME: 'mempool',
PASSWORD: 'mempool', PASSWORD: 'mempool',
TIMEOUT: 60000 TIMEOUT: 60000,
COOKIE: false,
COOKIE_PATH: ''
}); });
expect(config.SECOND_CORE_RPC).toStrictEqual({ expect(config.SECOND_CORE_RPC).toStrictEqual({
@ -62,7 +64,9 @@ describe('Mempool Backend Config', () => {
PORT: 8332, PORT: 8332,
USERNAME: 'mempool', USERNAME: 'mempool',
PASSWORD: 'mempool', PASSWORD: 'mempool',
TIMEOUT: 60000 TIMEOUT: 60000,
COOKIE: false,
COOKIE_PATH: ''
}); });
expect(config.DATABASE).toStrictEqual({ expect(config.DATABASE).toStrictEqual({

View File

@ -25,4 +25,5 @@ export interface BitcoinRpcCredentials {
user: string; user: string;
pass: string; pass: string;
timeout: number; timeout: number;
cookie?: string;
} }

View File

@ -2,12 +2,15 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index'); const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory'; import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
export const defaultCookiePath = `${process.env.HOME}/.bitcoin/${{mainnet:'',testnet:'testnet3/',signet:'signet/'}[config.MEMPOOL.NETWORK]}.cookie`;
const nodeRpcCredentials: BitcoinRpcCredentials = { const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.CORE_RPC.HOST, host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT, port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME, user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD, pass: config.CORE_RPC.PASSWORD,
timeout: config.CORE_RPC.TIMEOUT, timeout: config.CORE_RPC.TIMEOUT,
cookie: config.CORE_RPC.COOKIE ? config.CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
}; };
export default new bitcoin.Client(nodeRpcCredentials); export default new bitcoin.Client(nodeRpcCredentials);

View File

@ -2,12 +2,15 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index'); const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory'; import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
import { defaultCookiePath } from './bitcoin-client';
const nodeRpcCredentials: BitcoinRpcCredentials = { const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.SECOND_CORE_RPC.HOST, host: config.SECOND_CORE_RPC.HOST,
port: config.SECOND_CORE_RPC.PORT, port: config.SECOND_CORE_RPC.PORT,
user: config.SECOND_CORE_RPC.USERNAME, user: config.SECOND_CORE_RPC.USERNAME,
pass: config.SECOND_CORE_RPC.PASSWORD, pass: config.SECOND_CORE_RPC.PASSWORD,
timeout: config.SECOND_CORE_RPC.TIMEOUT, timeout: config.SECOND_CORE_RPC.TIMEOUT,
cookie: config.SECOND_CORE_RPC.COOKIE ? config.SECOND_CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
}; };
export default new bitcoin.Client(nodeRpcCredentials); export default new bitcoin.Client(nodeRpcCredentials);

View File

@ -70,6 +70,8 @@ interface IConfig {
USERNAME: string; USERNAME: string;
PASSWORD: string; PASSWORD: string;
TIMEOUT: number; TIMEOUT: number;
COOKIE: boolean;
COOKIE_PATH: string;
}; };
SECOND_CORE_RPC: { SECOND_CORE_RPC: {
HOST: string; HOST: string;
@ -77,6 +79,8 @@ interface IConfig {
USERNAME: string; USERNAME: string;
PASSWORD: string; PASSWORD: string;
TIMEOUT: number; TIMEOUT: number;
COOKIE: boolean;
COOKIE_PATH: string;
}; };
DATABASE: { DATABASE: {
ENABLED: boolean; ENABLED: boolean;
@ -180,6 +184,8 @@ const defaults: IConfig = {
'USERNAME': 'mempool', 'USERNAME': 'mempool',
'PASSWORD': 'mempool', 'PASSWORD': 'mempool',
'TIMEOUT': 60000, 'TIMEOUT': 60000,
'COOKIE': false,
'COOKIE_PATH': '' // default value depends on network, see src/api/bitcoin/bitcoin-client
}, },
'SECOND_CORE_RPC': { 'SECOND_CORE_RPC': {
'HOST': '127.0.0.1', 'HOST': '127.0.0.1',
@ -187,6 +193,8 @@ const defaults: IConfig = {
'USERNAME': 'mempool', 'USERNAME': 'mempool',
'PASSWORD': 'mempool', 'PASSWORD': 'mempool',
'TIMEOUT': 60000, 'TIMEOUT': 60000,
'COOKIE': false,
'COOKIE_PATH': ''
}, },
'DATABASE': { 'DATABASE': {
'ENABLED': true, 'ENABLED': true,

View File

@ -1,5 +1,6 @@
var http = require('http') var http = require('http')
var https = require('https') var https = require('https')
import { readFileSync } from 'fs';
var JsonRPC = function (opts) { var JsonRPC = function (opts) {
// @ts-ignore // @ts-ignore
@ -55,7 +56,13 @@ JsonRPC.prototype.call = function (method, params) {
} }
// use HTTP auth if user and password set // use HTTP auth if user and password set
if (this.opts.user && this.opts.pass) { if (this.opts.cookie) {
if (!this.cachedCookie) {
this.cachedCookie = readFileSync(this.opts.cookie).toString();
}
// @ts-ignore
requestOptions.auth = this.cachedCookie;
} else if (this.opts.user && this.opts.pass) {
// @ts-ignore // @ts-ignore
requestOptions.auth = this.opts.user + ':' + this.opts.pass requestOptions.auth = this.opts.user + ':' + this.opts.pass
} }
@ -93,7 +100,7 @@ JsonRPC.prototype.call = function (method, params) {
reject(err) reject(err)
}) })
request.on('response', function (response) { request.on('response', (response) => {
clearTimeout(reqTimeout) clearTimeout(reqTimeout)
// We need to buffer the response chunks in a nonblocking way. // We need to buffer the response chunks in a nonblocking way.
@ -104,7 +111,7 @@ JsonRPC.prototype.call = function (method, params) {
// When all the responses are finished, we decode the JSON and // When all the responses are finished, we decode the JSON and
// depending on whether it's got a result or an error, we call // depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise. // emitSuccess or emitError on the promise.
response.on('end', function () { response.on('end', () => {
var err var err
if (cbCalled) return if (cbCalled) return
@ -113,6 +120,14 @@ JsonRPC.prototype.call = function (method, params) {
try { try {
var decoded = JSON.parse(buffer) var decoded = JSON.parse(buffer)
} catch (e) { } catch (e) {
// if we authenticated using a cookie and it failed, read the cookie file again
if (
response.statusCode === 401 /* Unauthorized */ &&
this.opts.cookie
) {
this.cachedCookie = undefined;
}
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
err = new Error('Invalid params, response status code: ' + response.statusCode) err = new Error('Invalid params, response status code: ' + response.statusCode)
err.code = -32602 err.code = -32602

View File

@ -162,7 +162,9 @@ Corresponding `docker-compose.yml` overrides:
"PORT": 8332, "PORT": 8332,
"USERNAME": "mempool", "USERNAME": "mempool",
"PASSWORD": "mempool", "PASSWORD": "mempool",
"TIMEOUT": 60000 "TIMEOUT": 60000,
"COOKIE": "false",
"COOKIE_PATH": ""
}, },
``` ```
@ -174,7 +176,9 @@ Corresponding `docker-compose.yml` overrides:
CORE_RPC_PORT: "" CORE_RPC_PORT: ""
CORE_RPC_USERNAME: "" CORE_RPC_USERNAME: ""
CORE_RPC_PASSWORD: "" CORE_RPC_PASSWORD: ""
CORE_RPC_TIMEOUT: 60000 CORE_RPC_TIMEOUT: 60000,
CORE_RPC_COOKIE: ""
CORE_RPC_COOKIE_PATH: ""
... ...
``` ```
@ -229,7 +233,9 @@ Corresponding `docker-compose.yml` overrides:
"PORT": 8332, "PORT": 8332,
"USERNAME": "mempool", "USERNAME": "mempool",
"PASSWORD": "mempool", "PASSWORD": "mempool",
"TIMEOUT": 60000 "TIMEOUT": 60000,
"COOKIE": "false",
"COOKIE_PATH": ""
}, },
``` ```
@ -241,7 +247,9 @@ Corresponding `docker-compose.yml` overrides:
SECOND_CORE_RPC_PORT: "" SECOND_CORE_RPC_PORT: ""
SECOND_CORE_RPC_USERNAME: "" SECOND_CORE_RPC_USERNAME: ""
SECOND_CORE_RPC_PASSWORD: "" SECOND_CORE_RPC_PASSWORD: ""
SECOND_CORE_RPC_TIMEOUT: "" SECOND_CORE_RPC_TIMEOUT: "",
SECOND_CORE_RPC_COOKIE: ""
SECOND_CORE_RPC_COOKIE_PATH: ""
... ...
``` ```

View File

@ -36,7 +36,9 @@
"PORT": __CORE_RPC_PORT__, "PORT": __CORE_RPC_PORT__,
"USERNAME": "__CORE_RPC_USERNAME__", "USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__", "PASSWORD": "__CORE_RPC_PASSWORD__",
"TIMEOUT": __CORE_RPC_TIMEOUT__ "TIMEOUT": __CORE_RPC_TIMEOUT__,
"COOKIE": __CORE_RPC_COOKIE__,
"COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
}, },
"ELECTRUM": { "ELECTRUM": {
"HOST": "__ELECTRUM_HOST__", "HOST": "__ELECTRUM_HOST__",
@ -53,7 +55,9 @@
"PORT": __SECOND_CORE_RPC_PORT__, "PORT": __SECOND_CORE_RPC_PORT__,
"USERNAME": "__SECOND_CORE_RPC_USERNAME__", "USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__", "PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
"TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__ "TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__,
"COOKIE": __SECOND_CORE_RPC_COOKIE__,
"COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": __DATABASE_ENABLED__, "ENABLED": __DATABASE_ENABLED__,

View File

@ -38,6 +38,8 @@ __CORE_RPC_PORT__=${CORE_RPC_PORT:=8332}
__CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool} __CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool}
__CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool} __CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool}
__CORE_RPC_TIMEOUT__=${CORE_RPC_TIMEOUT:=60000} __CORE_RPC_TIMEOUT__=${CORE_RPC_TIMEOUT:=60000}
__CORE_RPC_COOKIE__=${CORE_RPC_COOKIE:=false}
__CORE_RPC_COOKIE_PATH__=${CORE_RPC_COOKIE:=""}
# ELECTRUM # ELECTRUM
__ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1} __ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1}
@ -55,6 +57,8 @@ __SECOND_CORE_RPC_PORT__=${SECOND_CORE_RPC_PORT:=8332}
__SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool} __SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool}
__SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool} __SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
__SECOND_CORE_RPC_TIMEOUT__=${SECOND_CORE_RPC_TIMEOUT:=60000} __SECOND_CORE_RPC_TIMEOUT__=${SECOND_CORE_RPC_TIMEOUT:=60000}
__SECOND_CORE_RPC_COOKIE__=${SECOND_CORE_RPC_COOKIE:=false}
__SECOND_CORE_RPC_COOKIE_PATH__=${SECOND_CORE_RPC_COOKIE:=""}
# DATABASE # DATABASE
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true} __DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
@ -165,6 +169,8 @@ sed -i "s!__CORE_RPC_PORT__!${__CORE_RPC_PORT__}!g" mempool-config.json
sed -i "s!__CORE_RPC_USERNAME__!${__CORE_RPC_USERNAME__}!g" mempool-config.json sed -i "s!__CORE_RPC_USERNAME__!${__CORE_RPC_USERNAME__}!g" mempool-config.json
sed -i "s!__CORE_RPC_PASSWORD__!${__CORE_RPC_PASSWORD__}!g" mempool-config.json sed -i "s!__CORE_RPC_PASSWORD__!${__CORE_RPC_PASSWORD__}!g" mempool-config.json
sed -i "s!__CORE_RPC_TIMEOUT__!${__CORE_RPC_TIMEOUT__}!g" mempool-config.json sed -i "s!__CORE_RPC_TIMEOUT__!${__CORE_RPC_TIMEOUT__}!g" mempool-config.json
sed -i "s!__CORE_RPC_COOKIE__!${__CORE_RPC_COOKIE__}!g" mempool-config.json
sed -i "s!__CORE_RPC_COOKIE_PATH__!${__CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
sed -i "s!__ELECTRUM_HOST__!${__ELECTRUM_HOST__}!g" mempool-config.json sed -i "s!__ELECTRUM_HOST__!${__ELECTRUM_HOST__}!g" mempool-config.json
sed -i "s!__ELECTRUM_PORT__!${__ELECTRUM_PORT__}!g" mempool-config.json sed -i "s!__ELECTRUM_PORT__!${__ELECTRUM_PORT__}!g" mempool-config.json
@ -179,6 +185,8 @@ sed -i "s!__SECOND_CORE_RPC_PORT__!${__SECOND_CORE_RPC_PORT__}!g" mempool-config
sed -i "s!__SECOND_CORE_RPC_USERNAME__!${__SECOND_CORE_RPC_USERNAME__}!g" mempool-config.json sed -i "s!__SECOND_CORE_RPC_USERNAME__!${__SECOND_CORE_RPC_USERNAME__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_PASSWORD__!${__SECOND_CORE_RPC_PASSWORD__}!g" mempool-config.json sed -i "s!__SECOND_CORE_RPC_PASSWORD__!${__SECOND_CORE_RPC_PASSWORD__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_TIMEOUT__!${__SECOND_CORE_RPC_TIMEOUT__}!g" mempool-config.json sed -i "s!__SECOND_CORE_RPC_TIMEOUT__!${__SECOND_CORE_RPC_TIMEOUT__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_COOKIE__!${__SECOND_CORE_RPC_COOKIE__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_COOKIE_PATH__!${__SECOND_CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
sed -i "s!__DATABASE_ENABLED__!${__DATABASE_ENABLED__}!g" mempool-config.json sed -i "s!__DATABASE_ENABLED__!${__DATABASE_ENABLED__}!g" mempool-config.json
sed -i "s!__DATABASE_HOST__!${__DATABASE_HOST__}!g" mempool-config.json sed -i "s!__DATABASE_HOST__!${__DATABASE_HOST__}!g" mempool-config.json