exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_auditor_exec_auditor.c (13881B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing/testing_api_cmd_auditor_exec_auditor.c
     21  * @brief run all taler-helper-auditor-* programs and check that
     22  *        taler-auditor-httpd reports no findings
     23  * @author Marcello Stanisci
     24  * @author Christian Grothoff
     25  */
     26 #include "platform.h"
     27 #include "taler/taler_json_lib.h"
     28 #include <gnunet/gnunet_curl_lib.h>
     29 #include "taler/taler_testing_lib.h"
     30 
     31 
     32 /**
     33  * Auditor helpers to run, in this order.  Each is run in test mode
     34  * ("-t"), so it audits everything the exchange database holds so far
     35  * and then exits.
     36  */
     37 static const struct
     38 {
     39   /**
     40    * Name of the helper binary.
     41    */
     42   const char *name;
     43 
     44   /**
     45    * True if the helper reads the bank's history of the exchange's
     46    * accounts.  Test configurations may list accounts the bank does
     47    * not have, so those helpers are told to skip them ("-I").
     48    */
     49   bool wire;
     50 } helpers[] = {
     51   { "taler-helper-auditor-aggregation", false },
     52   { "taler-helper-auditor-aml", false },
     53   { "taler-helper-auditor-coins", false },
     54   { "taler-helper-auditor-deposits", false },
     55   { "taler-helper-auditor-purses", false },
     56   { "taler-helper-auditor-reserves", false },
     57   { "taler-helper-auditor-transfer", false },
     58   { "taler-helper-auditor-wire-credit", true },
     59   { "taler-helper-auditor-wire-debit", true },
     60   { NULL, false }
     61 };
     62 
     63 
     64 /**
     65  * /monitoring/ reports of taler-auditor-httpd that must be empty after a
     66  * correct exchange was audited.  Not listed are the balances, progress
     67  * and per-reserve/per-purse summaries, which are not findings, and
     68  * "pending-deposits" and "aml-holds", which list payouts the exchange has
     69  * not made *yet* (deposits before their wire deadline, transfers held
     70  * for KYC) and thus depend on when the test last ran the aggregator.
     71  */
     72 static const char *const reports[] = {
     73   "amount-arithmetic-inconsistencies",
     74   "bad-sig-losses",
     75   "closure-lags",
     76   "coin-inconsistencies",
     77   "denomination-key-validity-withdraw-inconsistencies",
     78   "denominations-without-sigs",
     79   "deposit-confirmations",
     80   "early-aggregations",
     81   "emergencies",
     82   "emergencies-by-count",
     83   "fee-time-inconsistencies",
     84   "kycauth-in-inconsistencies",
     85   "misattribution-in-inconsistencies",
     86   "purse-not-closed-inconsistencies",
     87   "reserve-balance-insufficient-inconsistencies",
     88   "reserve-balance-summary-wrong-inconsistencies",
     89   "reserve-in-inconsistencies",
     90   "reserve-not-closed-inconsistencies",
     91   "row-inconsistencies",
     92   "row-minor-inconsistencies",
     93   "wire-format-inconsistencies",
     94   "wire-out-inconsistencies",
     95   NULL
     96 };
     97 
     98 
     99 /**
    100  * State for a "auditor" CMD.
    101  */
    102 struct AuditorState
    103 {
    104 
    105   /**
    106    * Our interpreter.
    107    */
    108   struct TALER_TESTING_Interpreter *is;
    109 
    110   /**
    111    * Process of the helper currently running.
    112    */
    113   struct GNUNET_Process *helper_proc;
    114 
    115   /**
    116    * Handle to wait for @e helper_proc to terminate.
    117    */
    118   struct GNUNET_ChildWaitHandle *cwh;
    119 
    120   /**
    121    * Task to start the next helper.
    122    */
    123   struct GNUNET_SCHEDULER_Task *task;
    124 
    125   /**
    126    * Request for a report, or NULL.
    127    */
    128   struct GNUNET_CURL_Job *job;
    129 
    130   /**
    131    * Headers to send with each request for a report.
    132    */
    133   struct curl_slist *job_headers;
    134 
    135   /**
    136    * Configuration file used by the command.
    137    */
    138   const char *config_filename;
    139 
    140   /**
    141    * Base URL of the auditor, from @e config_filename.
    142    */
    143   char *auditor_url;
    144 
    145   /**
    146    * Index into #helpers of the helper currently running.
    147    */
    148   unsigned int helper_off;
    149 
    150   /**
    151    * Index into #reports of the report currently requested.
    152    */
    153   unsigned int report_off;
    154 
    155   /**
    156    * Number of reports that were not empty.
    157    */
    158   unsigned int findings;
    159 };
    160 
    161 
    162 /**
    163  * Request the next report from #reports, or finish the command
    164  * if all of them were checked.
    165  *
    166  * @param as our state
    167  */
    168 static void
    169 check_next_report (struct AuditorState *as);
    170 
    171 
    172 /**
    173  * Called with a report from the auditor.
    174  *
    175  * @param cls our `struct AuditorState`
    176  * @param response_code HTTP status returned by the auditor
    177  * @param response response body, NULL if not in JSON
    178  */
    179 static void
    180 report_cb (void *cls,
    181            long response_code,
    182            const void *response)
    183 {
    184   struct AuditorState *as = cls;
    185   const json_t *reply = response;
    186   const char *report = reports[as->report_off];
    187   const json_t *records;
    188 
    189   as->job = NULL;
    190   if (MHD_HTTP_OK != response_code)
    191   {
    192     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    193                 "Auditor returned HTTP status %u for report `%s'\n",
    194                 (unsigned int) response_code,
    195                 report);
    196     TALER_TESTING_interpreter_fail (as->is);
    197     return;
    198   }
    199   records = json_object_get (reply,
    200                              "records");
    201   if (! json_is_array (records))
    202   {
    203     GNUNET_break_op (0);
    204     TALER_TESTING_interpreter_fail (as->is);
    205     return;
    206   }
    207   if (0 != json_array_size (records))
    208   {
    209     char *s;
    210 
    211     s = json_dumps (records,
    212                     JSON_COMPACT);
    213     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    214                 "Auditor reports %u %s: %s\n",
    215                 (unsigned int) json_array_size (records),
    216                 report,
    217                 s);
    218     free (s);
    219     as->findings++;
    220   }
    221   as->report_off++;
    222   check_next_report (as);
    223 }
    224 
    225 
    226 static void
    227 check_next_report (struct AuditorState *as)
    228 {
    229   CURL *eh;
    230   char *url;
    231 
    232   if (NULL == reports[as->report_off])
    233   {
    234     if (0 != as->findings)
    235     {
    236       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    237                   "Auditor has findings in %u reports\n",
    238                   as->findings);
    239       TALER_TESTING_interpreter_fail (as->is);
    240       return;
    241     }
    242     TALER_TESTING_interpreter_next (as->is);
    243     return;
    244   }
    245   GNUNET_asprintf (&url,
    246                    "%smonitoring/%s",
    247                    as->auditor_url,
    248                    reports[as->report_off]);
    249   eh = curl_easy_init ();
    250   GNUNET_assert (NULL != eh);
    251   GNUNET_assert (CURLE_OK ==
    252                  curl_easy_setopt (eh,
    253                                    CURLOPT_URL,
    254                                    url));
    255   GNUNET_free (url);
    256   as->job = GNUNET_CURL_job_add2 (TALER_TESTING_interpreter_get_context (
    257                                     as->is),
    258                                   eh,
    259                                   as->job_headers,
    260                                   &report_cb,
    261                                   as);
    262   if (NULL == as->job)
    263   {
    264     GNUNET_break (0);
    265     TALER_TESTING_interpreter_fail (as->is);
    266     return;
    267   }
    268 }
    269 
    270 
    271 /**
    272  * Start the next helper from #helpers, or start checking
    273  * the reports if all helpers have run.
    274  *
    275  * @param cls our `struct AuditorState`
    276  */
    277 static void
    278 run_next_helper (void *cls);
    279 
    280 
    281 /**
    282  * Called when the helper we started has terminated.
    283  *
    284  * @param cls our `struct AuditorState`
    285  * @param type how the helper terminated
    286  * @param code exit code of the helper
    287  */
    288 static void
    289 helper_done (void *cls,
    290              enum GNUNET_OS_ProcessStatusType type,
    291              long unsigned int code)
    292 {
    293   struct AuditorState *as = cls;
    294 
    295   as->cwh = NULL;
    296   GNUNET_process_destroy (as->helper_proc);
    297   as->helper_proc = NULL;
    298   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
    299        (0 != code) )
    300   {
    301     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    302                 "%s failed (status %d, code %lu)\n",
    303                 helpers[as->helper_off].name,
    304                 (int) type,
    305                 code);
    306     TALER_TESTING_interpreter_fail (as->is);
    307     return;
    308   }
    309   as->helper_off++;
    310   /* Not directly: GNUNET_wait_child() must not be called from
    311      within a GNUNET_ChildCompletedCallback. */
    312   as->task = GNUNET_SCHEDULER_add_now (&run_next_helper,
    313                                        as);
    314 }
    315 
    316 
    317 static void
    318 run_next_helper (void *cls)
    319 {
    320   struct AuditorState *as = cls;
    321   const char *helper = helpers[as->helper_off].name;
    322 
    323   as->task = NULL;
    324   if (NULL == helper)
    325   {
    326     check_next_report (as);
    327     return;
    328   }
    329   as->helper_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    330   if (GNUNET_OK !=
    331       GNUNET_process_run_command_va (as->helper_proc,
    332                                      helper,
    333                                      helper,
    334                                      "-c", as->config_filename,
    335                                      "-L", "INFO",
    336                                      "-i", /* we audit the exchange DB directly */
    337                                      "-t", /* exit when done */
    338                                      helpers[as->helper_off].wire
    339                                      ? "-I" /* ignore unknown accounts */
    340                                      : NULL,
    341                                      NULL))
    342   {
    343     GNUNET_break (0);
    344     GNUNET_process_destroy (as->helper_proc);
    345     as->helper_proc = NULL;
    346     TALER_TESTING_interpreter_fail (as->is);
    347     return;
    348   }
    349   as->cwh = GNUNET_wait_child (as->helper_proc,
    350                                &helper_done,
    351                                as);
    352 }
    353 
    354 
    355 /**
    356  * Run the command; runs all auditor helpers against the exchange
    357  * database and then asks the auditor for its findings.
    358  *
    359  * @param cls closure.
    360  * @param cmd the commaind being run.
    361  * @param is interpreter state.
    362  */
    363 static void
    364 auditor_run (void *cls,
    365              const struct TALER_TESTING_Command *cmd,
    366              struct TALER_TESTING_Interpreter *is)
    367 {
    368   struct AuditorState *as = cls;
    369   struct GNUNET_CONFIGURATION_Handle *cfg;
    370 
    371   (void) cmd;
    372   as->is = is;
    373   cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ());
    374   if (GNUNET_OK !=
    375       GNUNET_CONFIGURATION_load (cfg,
    376                                  as->config_filename))
    377   {
    378     GNUNET_break (0);
    379     GNUNET_CONFIGURATION_destroy (cfg);
    380     TALER_TESTING_interpreter_fail (is);
    381     return;
    382   }
    383   if (GNUNET_OK !=
    384       GNUNET_CONFIGURATION_get_value_string (cfg,
    385                                              "auditor",
    386                                              "BASE_URL",
    387                                              &as->auditor_url))
    388   {
    389     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    390                                "auditor",
    391                                "BASE_URL");
    392     GNUNET_CONFIGURATION_destroy (cfg);
    393     TALER_TESTING_interpreter_fail (is);
    394     return;
    395   }
    396   {
    397     const char *tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN");
    398     char *cfg_tok = NULL;
    399 
    400     /* Find the token the way taler-auditor-httpd does: the environment
    401        variable overrides the configuration, and the token may be stored
    402        with or without the RFC 8959 prefix.  If there is none, the httpd
    403        must have been started with -n. */
    404     if ( (NULL == tok) &&
    405          (GNUNET_OK ==
    406           GNUNET_CONFIGURATION_get_value_string (cfg,
    407                                                  "auditor",
    408                                                  "ACCESS_TOKEN",
    409                                                  &cfg_tok)) )
    410       tok = cfg_tok;
    411     if (NULL != tok)
    412     {
    413       char *hdr;
    414 
    415       if (0 != strncasecmp (tok,
    416                             "secret-token:",
    417                             strlen ("secret-token:")))
    418       {
    419         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    420                                    "auditor",
    421                                    "ACCESS_TOKEN",
    422                                    "lacks RFC 8959 prefix");
    423         GNUNET_CONFIGURATION_destroy (cfg);
    424         TALER_TESTING_interpreter_fail (is);
    425         return;
    426       }
    427       tok += strlen ("secret-token:");
    428       GNUNET_asprintf (&hdr,
    429                        "%s: Bearer secret-token:%s",
    430                        MHD_HTTP_HEADER_AUTHORIZATION,
    431                        tok);
    432       as->job_headers = curl_slist_append (as->job_headers,
    433                                            hdr);
    434       GNUNET_free (hdr);
    435     }
    436     GNUNET_free (cfg_tok);
    437   }
    438   GNUNET_CONFIGURATION_destroy (cfg);
    439   run_next_helper (as);
    440 }
    441 
    442 
    443 /**
    444  * Free the state of a "auditor" CMD, and possibly kills its
    445  * process if it did not terminate correctly.
    446  *
    447  * @param cls closure.
    448  * @param cmd the command being freed.
    449  */
    450 static void
    451 auditor_cleanup (void *cls,
    452                  const struct TALER_TESTING_Command *cmd)
    453 {
    454   struct AuditorState *as = cls;
    455 
    456   (void) cmd;
    457   if (NULL != as->task)
    458   {
    459     GNUNET_SCHEDULER_cancel (as->task);
    460     as->task = NULL;
    461   }
    462   if (NULL != as->cwh)
    463   {
    464     GNUNET_wait_child_cancel (as->cwh);
    465     as->cwh = NULL;
    466   }
    467   if (NULL != as->helper_proc)
    468   {
    469     GNUNET_break (GNUNET_OK ==
    470                   GNUNET_process_kill (as->helper_proc,
    471                                        SIGKILL));
    472     GNUNET_process_wait (as->helper_proc,
    473                          true,
    474                          NULL,
    475                          NULL);
    476     GNUNET_process_destroy (as->helper_proc);
    477     as->helper_proc = NULL;
    478   }
    479   if (NULL != as->job)
    480   {
    481     GNUNET_CURL_job_cancel (as->job);
    482     as->job = NULL;
    483   }
    484   curl_slist_free_all (as->job_headers);
    485   GNUNET_free (as->auditor_url);
    486   GNUNET_free (as);
    487 }
    488 
    489 
    490 struct TALER_TESTING_Command
    491 TALER_TESTING_cmd_exec_auditor (const char *label,
    492                                 const char *config_filename)
    493 {
    494   struct AuditorState *as;
    495 
    496   as = GNUNET_new (struct AuditorState);
    497   as->config_filename = config_filename;
    498   {
    499     struct TALER_TESTING_Command cmd = {
    500       .cls = as,
    501       .label = label,
    502       .run = &auditor_run,
    503       .cleanup = &auditor_cleanup
    504     };
    505 
    506     return cmd;
    507   }
    508 }
    509 
    510 
    511 /* end of testing_api_cmd_auditor_exec_auditor.c */