towards exchange linting
This commit is contained in:
parent
18c8cebbcd
commit
f88e14f66d
@ -1429,3 +1429,25 @@ export const codecForTalerConfigResponse = (): Codec<TalerConfigResponse> =>
|
||||
.property("version", codecForString())
|
||||
.property("currency", codecOptional(codecForString()))
|
||||
.build("TalerConfigResponse");
|
||||
|
||||
export interface FutureKeysResponse {
|
||||
future_denoms: any[];
|
||||
|
||||
future_signkeys: any[];
|
||||
|
||||
master_pub: string;
|
||||
|
||||
denom_secmod_public_key: string;
|
||||
|
||||
// Public key of the signkey security module.
|
||||
signkey_secmod_public_key: string;
|
||||
}
|
||||
|
||||
export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> =>
|
||||
buildCodecForObject<FutureKeysResponse>()
|
||||
.property("master_pub", codecForString())
|
||||
.property("future_signkeys", codecForList(codecForAny()))
|
||||
.property("future_denoms", codecForList(codecForAny()))
|
||||
.property("denom_secmod_public_key", codecForAny())
|
||||
.property("signkey_secmod_public_key", codecForAny())
|
||||
.build("FutureKeysResponse");
|
||||
|
@ -55,7 +55,7 @@ import {
|
||||
WalletCoreApiClient,
|
||||
Wallet,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import { lintDeployment } from "./lint.js";
|
||||
import { lintExchangeDeployment } from "./lint.js";
|
||||
|
||||
// This module also serves as the entry point for the crypto
|
||||
// thread worker, and thus must expose these two handlers.
|
||||
@ -870,8 +870,10 @@ const deploymentCli = walletCli.subcommand("deploymentArgs", "deployment", {
|
||||
help: "Subcommands for handling GNU Taler deployments.",
|
||||
});
|
||||
|
||||
deploymentCli.subcommand("lint", "lint").action(async (args) => {
|
||||
lintDeployment();
|
||||
deploymentCli.subcommand("lintExchange", "lint-exchange", {
|
||||
help: "Run checks on the exchange deployment."
|
||||
}).action(async (args) => {
|
||||
await lintExchangeDeployment();
|
||||
});
|
||||
|
||||
deploymentCli
|
||||
|
@ -51,7 +51,6 @@ import {
|
||||
getRandomBytes,
|
||||
openPromise,
|
||||
OperationFailedError,
|
||||
WalletApiOperation,
|
||||
WalletCoreApiClient,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import {
|
||||
@ -65,49 +64,7 @@ import {
|
||||
Duration,
|
||||
parsePaytoUri,
|
||||
CoreApiResponse,
|
||||
ApplyRefundRequest,
|
||||
ApplyRefundResponse,
|
||||
codecForApplyRefundResponse,
|
||||
PreparePayRequest,
|
||||
PreparePayResult,
|
||||
codecForPreparePayResult,
|
||||
CreateDepositGroupRequest,
|
||||
CreateDepositGroupResponse,
|
||||
AbortPayWithRefundRequest,
|
||||
ConfirmPayRequest,
|
||||
ConfirmPayResult,
|
||||
codecForConfirmPayResult,
|
||||
PrepareTipRequest,
|
||||
PrepareTipResult,
|
||||
codecForPrepareTipResult,
|
||||
AcceptTipRequest,
|
||||
CoinDumpJson,
|
||||
codecForAny,
|
||||
AddExchangeRequest,
|
||||
ForceExchangeUpdateRequest,
|
||||
ForceRefreshRequest,
|
||||
ExchangesListRespose,
|
||||
codecForExchangesListResponse,
|
||||
BalancesResponse,
|
||||
codecForBalancesResponse,
|
||||
TransactionsResponse,
|
||||
codecForTransactionsResponse,
|
||||
TrackDepositGroupRequest,
|
||||
TrackDepositGroupResponse,
|
||||
IntegrationTestArgs,
|
||||
TestPayArgs,
|
||||
WithdrawTestBalanceRequest,
|
||||
GetWithdrawalDetailsForUriRequest,
|
||||
WithdrawUriInfoResponse,
|
||||
codecForWithdrawUriInfoResponse,
|
||||
BackupRecovery,
|
||||
RecoveryLoadRequest,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
AddBackupProviderRequest,
|
||||
BackupInfo,
|
||||
} from "@gnu-taler/taler-wallet-core/src/operations/backup";
|
||||
import { PendingOperationsResponse } from "@gnu-taler/taler-wallet-core/src/pending-types";
|
||||
import { CoinConfig } from "./denomStructures.js";
|
||||
|
||||
const exec = util.promisify(require("child_process").exec);
|
||||
|
@ -17,18 +17,94 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { Configuration } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
buildCodecForObject,
|
||||
Codec,
|
||||
codecForAny,
|
||||
codecForExchangeKeysJson,
|
||||
codecForKeysManagementResponse,
|
||||
codecForList,
|
||||
codecForString,
|
||||
Configuration,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
decodeCrock,
|
||||
NodeHttpLib,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import { URL } from "url";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
|
||||
interface BasicConf {
|
||||
mainCurrency: string;
|
||||
}
|
||||
|
||||
interface PubkeyConf {
|
||||
masterPublicKey: string;
|
||||
}
|
||||
|
||||
const httpLib = new NodeHttpLib();
|
||||
|
||||
interface ShellResult {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some basic checks in the configuration of a Taler deployment.
|
||||
* Run a shell command, return stdout.
|
||||
*/
|
||||
export function lintDeployment() {
|
||||
const cfg = Configuration.load();
|
||||
export async function sh(
|
||||
command: string,
|
||||
env: { [index: string]: string | undefined } = process.env,
|
||||
): Promise<ShellResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stdoutChunks: Buffer[] = [];
|
||||
const stderrChunks: Buffer[] = [];
|
||||
const proc = spawn(command, {
|
||||
stdio: ["inherit", "pipe", "pipe"],
|
||||
shell: true,
|
||||
env: env,
|
||||
});
|
||||
proc.stdout.on("data", (x) => {
|
||||
if (x instanceof Buffer) {
|
||||
stdoutChunks.push(x);
|
||||
} else {
|
||||
throw Error("unexpected data chunk type");
|
||||
}
|
||||
});
|
||||
proc.stderr.on("data", (x) => {
|
||||
if (x instanceof Buffer) {
|
||||
stderrChunks.push(x);
|
||||
} else {
|
||||
throw Error("unexpected data chunk type");
|
||||
}
|
||||
});
|
||||
proc.on("exit", (code, signal) => {
|
||||
console.log(`child process exited (${code} / ${signal})`);
|
||||
const bOut = Buffer.concat(stdoutChunks).toString("utf-8");
|
||||
const bErr = Buffer.concat(stderrChunks).toString("utf-8");
|
||||
resolve({
|
||||
status: code ?? -1,
|
||||
stderr: bErr,
|
||||
stdout: bOut,
|
||||
});
|
||||
});
|
||||
proc.on("error", () => {
|
||||
reject(Error("Child process had error"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkBasicConf(cfg: Configuration): BasicConf {
|
||||
const currencyEntry = cfg.getString("taler", "currency");
|
||||
let mainCurrency: string | undefined;
|
||||
|
||||
if (!currencyEntry.value) {
|
||||
console.log("error: currency not defined in section TALER option CURRENCY");
|
||||
process.exit(1);
|
||||
} else {
|
||||
mainCurrency = currencyEntry.value.toUpperCase();
|
||||
}
|
||||
@ -38,4 +114,185 @@ export function lintDeployment() {
|
||||
"warning: section TALER option CURRENCY contains toy currency value KUDOS",
|
||||
);
|
||||
}
|
||||
|
||||
const roundUnit = cfg.getAmount("taler", "currency_round_unit");
|
||||
if (!roundUnit.isDefined) {
|
||||
console.log(
|
||||
"error: configuration incomplete, section TALER option CURRENCY_ROUND_UNIT missing",
|
||||
);
|
||||
}
|
||||
return { mainCurrency };
|
||||
}
|
||||
|
||||
function checkCoinConfig(cfg: Configuration, basic: BasicConf): void {
|
||||
const coinPrefix = "coin_";
|
||||
let numCoins = 0;
|
||||
|
||||
for (const secName of cfg.getSectionNames()) {
|
||||
if (!secName.startsWith(coinPrefix)) {
|
||||
continue;
|
||||
}
|
||||
numCoins++;
|
||||
|
||||
// FIXME: check that section is well-formed
|
||||
}
|
||||
|
||||
console.log(
|
||||
"error: no coin denomination configured, please configure [coin_*] sections",
|
||||
);
|
||||
}
|
||||
|
||||
function checkWireConfig(cfg: Configuration): void {
|
||||
const accountPrefix = "exchange-account-";
|
||||
const accountCredentialsPrefix = "exchange-accountcredentials-";
|
||||
|
||||
let accounts = new Set<string>();
|
||||
let credentials = new Set<string>();
|
||||
|
||||
for (const secName of cfg.getSectionNames()) {
|
||||
if (secName.startsWith(accountPrefix)) {
|
||||
accounts.add(secName.slice(accountPrefix.length));
|
||||
// FIXME: check settings
|
||||
}
|
||||
|
||||
if (secName.startsWith(accountCredentialsPrefix)) {
|
||||
credentials.add(secName.slice(accountCredentialsPrefix.length));
|
||||
// FIXME: check settings
|
||||
}
|
||||
}
|
||||
|
||||
for (const acc of accounts) {
|
||||
if (!credentials.has(acc)) {
|
||||
console.log(
|
||||
`warning: no credentials configured for exchange-account-${acc}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: now try to use taler-exchange-wire-gateway-client to connect!
|
||||
// FIXME: run wirewatch in test mode here?
|
||||
// FIXME: run transfer in test mode here?
|
||||
}
|
||||
|
||||
function checkAggregatorConfig(cfg: Configuration) {
|
||||
// FIXME: run aggregator in test mode here
|
||||
}
|
||||
|
||||
function checkCloserConfig(cfg: Configuration) {
|
||||
// FIXME: run closer in test mode here
|
||||
}
|
||||
|
||||
function checkMasterPublicKeyConfig(cfg: Configuration): PubkeyConf {
|
||||
const pub = cfg.getString("exchange", "master_public_key");
|
||||
|
||||
if (!pub.isDefined) {
|
||||
console.log("error: Master public key is not set.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pubDecoded = decodeCrock(pub.required());
|
||||
|
||||
if (pubDecoded.length != 32) {
|
||||
console.log("error: invalid master public key");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
masterPublicKey: pub.required(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function checkExchangeHttpd(
|
||||
cfg: Configuration,
|
||||
pubConf: PubkeyConf,
|
||||
): Promise<void> {
|
||||
const baseUrlEntry = cfg.getString("exchange", "base_url");
|
||||
|
||||
if (!baseUrlEntry.isDefined) {
|
||||
console.log(
|
||||
"error: configuration needs to specify section EXCHANGE option BASE_URL",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseUrl = baseUrlEntry.required();
|
||||
|
||||
if (!baseUrl.startsWith("http")) {
|
||||
console.log(
|
||||
"error: section EXCHANGE option BASE_URL needs to be an http or https URL",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!baseUrl.endsWith("/")) {
|
||||
console.log(
|
||||
"error: section EXCHANGE option BASE_URL needs to end with a slash",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!baseUrl.startsWith("https://")) {
|
||||
console.log(
|
||||
"warning: section EXCHANGE option BASE_URL: it is recommended to serve the exchange via HTTPS",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
{
|
||||
const mgmtUrl = new URL("management/keys", baseUrl);
|
||||
const resp = await httpLib.get(mgmtUrl.href);
|
||||
|
||||
const futureKeys = await readSuccessResponseJsonOrThrow(
|
||||
resp,
|
||||
codecForKeysManagementResponse(),
|
||||
);
|
||||
|
||||
if (futureKeys.future_denoms.length > 0) {
|
||||
console.log(
|
||||
`warning: exchange has denomination keys that need to be signed by the offline signing procedure`,
|
||||
);
|
||||
}
|
||||
|
||||
if (futureKeys.future_signkeys.length > 0) {
|
||||
console.log(
|
||||
`warning: exchange has signing keys that need to be signed by the offline signing procedure`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const keysUrl = new URL("keys", baseUrl);
|
||||
const resp = await httpLib.get(keysUrl.href);
|
||||
const keys = await readSuccessResponseJsonOrThrow(
|
||||
resp,
|
||||
codecForExchangeKeysJson(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some basic checks in the configuration of a Taler deployment.
|
||||
*/
|
||||
export async function lintExchangeDeployment(): Promise<void> {
|
||||
if (process.getuid() != 1) {
|
||||
console.log(
|
||||
"warning: the exchange deployment linter is designed to be run as root",
|
||||
);
|
||||
}
|
||||
|
||||
const cfg = Configuration.load();
|
||||
|
||||
const basic = checkBasicConf(cfg);
|
||||
|
||||
checkCoinConfig(cfg, basic);
|
||||
|
||||
checkWireConfig(cfg);
|
||||
|
||||
checkAggregatorConfig(cfg);
|
||||
|
||||
checkCloserConfig(cfg);
|
||||
|
||||
const pubConf = checkMasterPublicKeyConfig(cfg);
|
||||
|
||||
await checkExchangeHttpd(cfg, pubConf);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user