aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-cli/src/lint.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-08-04 17:14:52 +0200
committerFlorian Dold <florian@dold.me>2021-08-04 17:14:52 +0200
commitf88e14f66d37c339816cb9ba73a84491e7133307 (patch)
treea6709627512ff9f7e6fd4a14d7a99669c9a95949 /packages/taler-wallet-cli/src/lint.ts
parent18c8cebbcd85e7345607572be4c66dff940ad776 (diff)
downloadwallet-core-f88e14f66d37c339816cb9ba73a84491e7133307.tar.xz
towards exchange linting
Diffstat (limited to 'packages/taler-wallet-cli/src/lint.ts')
-rw-r--r--packages/taler-wallet-cli/src/lint.ts265
1 files changed, 261 insertions, 4 deletions
diff --git a/packages/taler-wallet-cli/src/lint.ts b/packages/taler-wallet-cli/src/lint.ts
index ad00143b0..f7dfefe38 100644
--- a/packages/taler-wallet-cli/src/lint.ts
+++ b/packages/taler-wallet-cli/src/lint.ts
@@ -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);
}