summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-cli/src/lint.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-cli/src/lint.ts')
-rw-r--r--packages/taler-wallet-cli/src/lint.ts534
1 files changed, 0 insertions, 534 deletions
diff --git a/packages/taler-wallet-cli/src/lint.ts b/packages/taler-wallet-cli/src/lint.ts
deleted file mode 100644
index 2b888ccf4..000000000
--- a/packages/taler-wallet-cli/src/lint.ts
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * The deployment linter implements checks for a deployment
- * of the GNU Taler exchange. It is meant to help sysadmins
- * when setting up an exchange.
- *
- * The linter does checks in the configuration and uses
- * various tools of the exchange in test mode (-t).
- *
- * To be able to run the tools as the right user, the linter should be
- * run as root.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import {
- codecForExchangeKeysJson,
- codecForKeysManagementResponse,
- Configuration,
- decodeCrock,
-} from "@gnu-taler/taler-util";
-import {
- NodeHttpLib,
- readSuccessResponseJsonOrThrow,
-} from "@gnu-taler/taler-wallet-core";
-import { URL } from "url";
-import { spawn } from "child_process";
-import { delayMs } from "./harness/harness.js";
-
-interface BasicConf {
- mainCurrency: string;
-}
-
-interface PubkeyConf {
- masterPublicKey: string;
-}
-
-const httpLib = new NodeHttpLib();
-
-interface ShellResult {
- stdout: string;
- stderr: string;
- status: number;
-}
-
-interface LintContext {
- /**
- * Be more verbose.
- */
- verbose: boolean;
-
- /**
- * Always continue even after errors.
- */
- cont: boolean;
-
- cfg: Configuration;
-
- numErr: number;
-}
-
-/**
- * Run a shell command, return stdout.
- */
-export async function sh(
- context: LintContext,
- command: string,
- env: { [index: string]: string | undefined } = process.env,
-): Promise<ShellResult> {
- if (context.verbose) {
- console.log("executing command:", command);
- }
- 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) => {
- if (code != 0 && context.verbose) {
- 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(context: LintContext): BasicConf {
- const cfg = context.cfg;
- const currencyEntry = cfg.getString("taler", "currency");
- let mainCurrency: string | undefined;
-
- if (!currencyEntry.value) {
- context.numErr++;
- console.log("error: currency not defined in section TALER option CURRENCY");
- console.log("Aborting further checks.");
- process.exit(1);
- } else {
- mainCurrency = currencyEntry.value.toUpperCase();
- }
-
- if (mainCurrency === "KUDOS") {
- console.log(
- "warning: section TALER option CURRENCY contains toy currency value KUDOS",
- );
- }
-
- const roundUnit = cfg.getAmount("taler", "currency_round_unit");
- const ru = roundUnit.required();
- if (ru.currency.toLowerCase() != mainCurrency.toLowerCase()) {
- context.numErr++;
- console.log(
- "error: [TALER]/CURRENCY_ROUND_UNIT: currency does not match main currency",
- );
- }
- return { mainCurrency };
-}
-
-function checkCoinConfig(context: LintContext, basic: BasicConf): void {
- const cfg = context.cfg;
- const coinPrefix1 = "COIN_";
- const coinPrefix2 = "COIN-";
- let numCoins = 0;
-
- for (const secName of cfg.getSectionNames()) {
- if (!(secName.startsWith(coinPrefix1) || secName.startsWith(coinPrefix2))) {
- continue;
- }
- numCoins++;
-
- // FIXME: check that section is well-formed
- }
-
- if (numCoins == 0) {
- context.numErr++;
- console.log(
- "error: no coin denomination configured, please configure [coin-*] sections",
- );
- }
-}
-
-async function checkWireConfig(context: LintContext): Promise<void> {
- const cfg = context.cfg;
- 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
- }
- }
-
- if (accounts.size === 0) {
- context.numErr++;
- console.log(
- "error: No accounts configured (no sections EXCHANGE-ACCOUNT-*).",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-
- for (const acc of accounts) {
- if (!credentials.has(acc)) {
- console.log(
- `warning: no credentials configured for exchange-account-${acc}`,
- );
- }
- }
-
- for (const acc of accounts) {
- // test credit history
- {
- const res = await sh(
- context,
- "su -l --shell /bin/sh " +
- `-c 'taler-exchange-wire-gateway-client -s exchange-accountcredentials-${acc} --credit-history' ` +
- "taler-exchange-wire",
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log(
- "error: Could not run taler-exchange-wire-gateway-client. Please review logs above.",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
- }
- }
-
- // TWG client
- {
- const res = await sh(
- context,
- `su -l --shell /bin/sh -c 'taler-exchange-wirewatch -t' taler-exchange-wire`,
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log("error: Could not run wirewatch. Please review logs above.");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
- }
-
- // Wirewatch
- {
- const res = await sh(
- context,
- `su -l --shell /bin/sh -c 'taler-exchange-wirewatch -t' taler-exchange-wire`,
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log("error: Could not run wirewatch. Please review logs above.");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
- }
-
- // Closer
- {
- const res = await sh(
- context,
- `su -l --shell /bin/sh -c 'taler-exchange-closer -t' taler-exchange-closer`,
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log("error: Could not run closer. Please review logs above.");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
- }
-}
-
-async function checkAggregatorConfig(context: LintContext) {
- const res = await sh(
- context,
- "su -l --shell /bin/sh -c 'taler-exchange-aggregator -t' taler-exchange-aggregator",
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log("error: Could not run aggregator. Please review logs above.");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-}
-
-async function checkCloserConfig(context: LintContext) {
- const res = await sh(
- context,
- `su -l --shell /bin/sh -c 'taler-exchange-closer -t' taler-exchange-closer`,
- );
- if (res.status != 0) {
- context.numErr++;
- console.log(res.stdout);
- console.log(res.stderr);
- console.log("error: Could not run closer. Please review logs above.");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-}
-
-function checkMasterPublicKeyConfig(context: LintContext): PubkeyConf {
- const cfg = context.cfg;
- const pub = cfg.getString("exchange", "master_public_key");
-
- const pubDecoded = decodeCrock(pub.required());
-
- if (pubDecoded.length != 32) {
- context.numErr++;
- console.log("error: invalid master public key");
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-
- return {
- masterPublicKey: pub.required(),
- };
-}
-
-export async function checkExchangeHttpd(
- context: LintContext,
- pubConf: PubkeyConf,
-): Promise<void> {
- const cfg = context.cfg;
- const baseUrlEntry = cfg.getString("exchange", "base_url");
-
- if (!baseUrlEntry.isDefined) {
- context.numErr++;
- console.log(
- "error: configuration needs to specify section EXCHANGE option BASE_URL",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-
- const baseUrl = baseUrlEntry.required();
-
- if (!baseUrl.startsWith("http")) {
- context.numErr++;
- console.log(
- "error: section EXCHANGE option BASE_URL needs to be an http or https URL",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-
- if (!baseUrl.endsWith("/")) {
- context.numErr++;
- console.log(
- "error: section EXCHANGE option BASE_URL needs to end with a slash",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
-
- if (!baseUrl.startsWith("https://")) {
- console.log(
- "warning: section EXCHANGE option BASE_URL: it is recommended to serve the exchange via HTTPS",
- );
- }
-
- {
- 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`,
- );
- }
- }
-
- // Check if we can use /keys already
- {
- const keysUrl = new URL("keys", baseUrl);
-
- const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]);
-
- if (!resp) {
- context.numErr++;
- console.log(
- "error: request to /keys timed out. " +
- "Make sure to sign and upload denomination and signing keys " +
- "with taler-exchange-offline.",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- } else {
- const keys = await readSuccessResponseJsonOrThrow(
- resp,
- codecForExchangeKeysJson(),
- );
-
- if (keys.master_public_key !== pubConf.masterPublicKey) {
- context.numErr++;
- console.log(
- "error: master public key of exchange does not match public key of live exchange",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- }
- }
- }
-
- // Check /wire
- {
- const keysUrl = new URL("wire", baseUrl);
-
- const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]);
-
- if (!resp) {
- context.numErr++;
- console.log(
- "error: request to /wire timed out. " +
- "Make sure to sign and upload accounts and wire fees " +
- "using the taler-exchange-offline tool.",
- );
- if (!context.cont) {
- console.log("Aborting further checks.");
- process.exit(1);
- }
- } else {
- if (resp.status !== 200) {
- console.log(
- "error: Can't access exchange /wire. Please check " +
- "the logs of taler-exchange-httpd for further information.",
- );
- }
- }
- }
-}
-
-/**
- * Do some basic checks in the configuration of a Taler deployment.
- */
-export async function lintExchangeDeployment(
- verbose: boolean,
- cont: boolean,
-): Promise<void> {
- if (process.getuid() != 0) {
- console.log(
- "warning: the exchange deployment linter is designed to be run as root",
- );
- }
-
- const cfg = Configuration.load();
-
- const context: LintContext = {
- cont,
- verbose,
- cfg,
- numErr: 0,
- };
-
- const basic = checkBasicConf(context);
-
- checkCoinConfig(context, basic);
-
- await checkWireConfig(context);
-
- await checkAggregatorConfig(context);
-
- await checkCloserConfig(context);
-
- const pubConf = checkMasterPublicKeyConfig(context);
-
- await checkExchangeHttpd(context, pubConf);
-
- if (context.numErr == 0) {
- console.log("Linting completed without errors.");
- process.exit(0);
- } else {
- console.log(`Linting completed with ${context.numErr} errors.`);
- process.exit(1);
- }
-}