exchange

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

commit 2110963f2d8bb953903c0d66972e203b006a4387
parent e6f102bf5b8767b9f99921dd92be928d7023b762
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 11 Oct 2026 13:54:42 +0200

make auditor-httpd ACCESS_TOKEN use strict, not a mere option

Diffstat:
Mdebian/etc-taler-auditor/taler-auditor/conf.d/auditor-system.conf | 6++++++
Adebian/etc-taler-auditor/taler-auditor/secrets/auditor-access-token.secret.conf | 13+++++++++++++
Mdebian/taler-auditor.postinst | 25+++++++++++++++++++++++++
Mdebian/taler-auditor.postrm | 2++
Msrc/auditor/auditor.conf | 10++++++++++
Msrc/auditor/batch.sh | 2+-
Msrc/auditor/taler-auditor-httpd.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/auditor/test-auditor.sh | 2++
Msrc/auditor/test-kyc.sh | 2++
Msrc/auditor/test-revocation.sh | 2++
Msrc/testing/taler-unified-setup.sh | 9+++++++++
Msrc/testing/test_auditor_api-cs.conf | 5+++++
Msrc/testing/test_auditor_api-rsa.conf | 5+++++
Msrc/testing/test_auditor_api_version.c | 1+
Msrc/testing/testing_api_cmd_auditor_exec_auditor.c | 31++++++++++++++++++++++++++++---
15 files changed, 184 insertions(+), 28 deletions(-)

diff --git a/debian/etc-taler-auditor/taler-auditor/conf.d/auditor-system.conf b/debian/etc-taler-auditor/taler-auditor/conf.d/auditor-system.conf @@ -11,3 +11,9 @@ DATABASE = postgres SERVE = unix UNIXPATH_MODE = 666 + +# Access token for the /monitoring/ API, readable only by the +# taler-auditor-httpd user. Inlined after the options above, so +# that they are set before other users see the section marked +# as inaccessible. +@inline-secret@ auditor ../secrets/auditor-access-token.secret.conf diff --git a/debian/etc-taler-auditor/taler-auditor/secrets/auditor-access-token.secret.conf b/debian/etc-taler-auditor/taler-auditor/secrets/auditor-access-token.secret.conf @@ -0,0 +1,13 @@ +# Access token for the /monitoring/ API of taler-auditor-httpd. +# +# Clients (such as the auditor SPA) must send it as +# "Authorization: Bearer secret-token:...". It may be stored +# with or without the "secret-token:" prefix. +# +# The package generates a random token here on installation if +# no token is configured yet. To rotate it, replace the value +# and restart taler-auditor-httpd. + +[auditor] + +# ACCESS_TOKEN = CHANGE-ME diff --git a/debian/taler-auditor.postinst b/debian/taler-auditor.postinst @@ -2,6 +2,8 @@ set -e +TOKEN_CONF="/etc/taler-auditor/secrets/auditor-access-token.secret.conf" + case "${1}" in configure) if ! getent group taler-auditor-httpd >/dev/null; @@ -34,6 +36,29 @@ configure) 640 \ /etc/taler-auditor/secrets/exchange-accountcredentials-1.secret.conf fi + if ! dpkg-statoverride --list ${TOKEN_CONF} >/dev/null 2>&1; + then + dpkg-statoverride --add \ + --update \ + taler-auditor-httpd taler-auditor-httpd \ + 640 \ + ${TOKEN_CONF} + fi + + # taler-auditor-httpd refuses to start without an access token, so + # generate one on installation. Only done while the shipped + # placeholder is still in place and no token is configured anywhere + # else, so an existing token is never overwritten. The file is + # already 0640 (above) when the token is written. + if [ -f ${TOKEN_CONF} ] \ + && grep -q '^# ACCESS_TOKEN = CHANGE-ME$' ${TOKEN_CONF} \ + && [ -z "$(taler-auditor-config -c /etc/taler-auditor/taler-auditor.conf -s auditor -o ACCESS_TOKEN 2>/dev/null || true)" ]; + then + TOKEN="secret-token:$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" + sed -i "s|^# ACCESS_TOKEN = CHANGE-ME$|ACCESS_TOKEN = ${TOKEN}|" ${TOKEN_CONF} + TOKEN="" + echo "taler-auditor: generated a random ACCESS_TOKEN in ${TOKEN_CONF}." >&2 + fi ;; diff --git a/debian/taler-auditor.postrm b/debian/taler-auditor.postrm @@ -8,6 +8,8 @@ purge) /etc/taler-auditor/secrets/auditor-db.secret.conf || true dpkg-statoverride --remove \ /etc/taler-auditor/secrets/exchange-accountcredentials-1.secret.conf || true + dpkg-statoverride --remove \ + /etc/taler-auditor/secrets/auditor-access-token.secret.conf || true deluser --system \ --quiet \ taler-auditor-httpd || true diff --git a/src/auditor/auditor.conf b/src/auditor/auditor.conf @@ -19,6 +19,16 @@ AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}offline-keys/auditor.priv # a misbehaving exchange)? BASE_URL = http://localhost:8083/ +# Access token that clients (such as the auditor SPA) must send as +# "Authorization: Bearer secret-token:TOKEN" to use the /monitoring/ +# API. It may be given with or without the RFC 8959 "secret-token:" +# prefix. The TALER_AUDITOR_ACCESS_TOKEN environment variable, if set, +# overrides this option. taler-auditor-httpd refuses to start without +# a token unless it is given -n (--no-authentication). Keep the token +# out of world-readable files, for example by putting it into a file +# loaded with @inline-secret@. +# ACCESS_TOKEN = secret-token:CHANGE-ME + # Network configuration for the normal API/service HTTP server # serve via tcp socket (on PORT) diff --git a/src/auditor/batch.sh b/src/auditor/batch.sh @@ -144,7 +144,7 @@ taler-exchange-secmod-cs -c $CONF 2> taler-exchange-secmod-cs.log & taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log & taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log & taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log & -taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log & +taler-auditor-httpd -n -L INFO -c $CONF 2> taler-auditor-httpd.log & # Wait for all bank to be available (usually the slowest) for n in `seq 1 50` diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c @@ -1411,6 +1411,68 @@ auditor_serve_process_config (void) /** + * Load the access token clients must present to use the endpoints that + * require authentication, and derive #TAH_auth from it. The + * TALER_AUDITOR_ACCESS_TOKEN environment variable takes precedence over + * the ACCESS_TOKEN option in the [auditor] section. Clients send the + * token with the RFC 8959 "secret-token:" prefix; it MUST be stored with + * it. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR if no usable token is + * configured + */ +static enum GNUNET_GenericReturnValue +load_access_token (void) +{ + const char *tok; + char *cfg_tok = NULL; + + tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN"); + if ( (NULL == tok) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "ACCESS_TOKEN", + &cfg_tok)) ) + tok = cfg_tok; + if (NULL == tok) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No access token configured: set ACCESS_TOKEN in section [auditor] (or the TALER_AUDITOR_ACCESS_TOKEN environment variable), or pass -n to run without authentication\n"); + return GNUNET_SYSERR; + } + if (0 != strncasecmp (tok, + RFC_8959_PREFIX, + strlen (RFC_8959_PREFIX))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "The configured access token lacks the `%s' prefix\n", + RFC_8959_PREFIX); + GNUNET_free (cfg_tok); + return GNUNET_SYSERR; + } + tok += strlen (RFC_8959_PREFIX); + if ('\0' == *tok) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "The configured access token is empty\n"); + GNUNET_free (cfg_tok); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_hkdf_gnunet ( + &TAH_auth, + sizeof (TAH_auth), + KDF_SALT, + strlen (KDF_SALT), + tok, + strlen (tok))); + GNUNET_free (cfg_tok); + return GNUNET_OK; +} + + +/** * Function run on shutdown. * * @param cls NULL @@ -1503,35 +1565,22 @@ run (void *cls, (void) cls; (void) args; (void) cfgfile; - if (0 == disable_auth) - { - const char *tok; - - tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN"); - if (NULL == tok) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "TALER_AUDITOR_ACCESS_TOKEN environment variable not set. Disabling authentication\n"); - disable_auth = 1; - } - else - { - GNUNET_assert (GNUNET_YES == - GNUNET_CRYPTO_hkdf_gnunet ( - &TAH_auth, - sizeof (TAH_auth), - KDF_SALT, - strlen (KDF_SALT), - tok, - strlen (tok))); - } - } - go = TALER_MHD_GO_NONE; if (auditor_connection_close) go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; TALER_MHD_setup (go); cfg = config; + if ( (0 == disable_auth) && + (GNUNET_OK != load_access_token ()) ) + { + /* Failing open here would publish the entire audit findings + database, including the DELETE and PATCH handlers, to anyone + able to reach the port. -n disables authentication on + purpose. */ + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh @@ -45,6 +45,8 @@ ALL_TESTS=$(seq 0 31) TESTS=${1:-$ALL_TESTS} export TALER_AUDITOR_TOKEN="secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0" +# Make taler-auditor-httpd require the token the requests below send. +export TALER_AUDITOR_ACCESS_TOKEN="${TALER_AUDITOR_TOKEN}" export TALER_AUDITOR_SALT="64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE1H68SK8D9P6WW32CHK6GTKCDSR64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE0" # Global variable to run the auditor processes under valgrind diff --git a/src/auditor/test-kyc.sh b/src/auditor/test-kyc.sh @@ -64,6 +64,8 @@ ALL_TESTS=$(seq 0 16) TESTS=${1:-$ALL_TESTS} export TALER_AUDITOR_TOKEN="secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0" +# Make taler-auditor-httpd require the token the requests below send. +export TALER_AUDITOR_ACCESS_TOKEN="${TALER_AUDITOR_TOKEN}" export TALER_AUDITOR_SALT="64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE1H68SK8D9P6WW32CHK6GTKCDSR64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE0" # Global variable to run the auditor processes under valgrind diff --git a/src/auditor/test-revocation.sh b/src/auditor/test-revocation.sh @@ -42,6 +42,8 @@ ALL_TESTS=$(seq 0 4) TESTS=${1:-$ALL_TESTS} export TALER_AUDITOR_TOKEN="secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0" +# Make taler-auditor-httpd require the token the requests below send. +export TALER_AUDITOR_ACCESS_TOKEN="${TALER_AUDITOR_TOKEN}" export TALER_AUDITOR_SALT="64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE1H68SK8D9P6WW32CHK6GTKCDSR64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE0" # Global variable to run the auditor processes under valgrind diff --git a/src/testing/taler-unified-setup.sh b/src/testing/taler-unified-setup.sh @@ -813,7 +813,16 @@ then echo "Launching auditor using $CONF" > taler-auditor-httpd.log >&2 echo "Launching auditor using $AUDITOR_PUB from $AUDITOR_PRIV_FILE" \ >> taler-auditor-httpd.log + # taler-auditor-httpd refuses to start without an access token + # unless given -n: require authentication iff a token is configured. + AUDITOR_AUTH="-n" + if [ -n "${TALER_AUDITOR_ACCESS_TOKEN:-}" ] \ + || [ -n "$(taler-auditor-config -c "$CONF" -s AUDITOR -o ACCESS_TOKEN 2> /dev/null || true)" ] + then + AUDITOR_AUTH="" + fi $USE_VALGRIND taler-auditor-httpd \ + $AUDITOR_AUTH \ -L "$LOGLEVEL" \ -c "$CONF" 2>> taler-auditor-httpd.log & echo " DONE" >&2 diff --git a/src/testing/test_auditor_api-cs.conf b/src/testing/test_auditor_api-cs.conf @@ -16,3 +16,8 @@ ENABLE_DEBIT = NO [exchange-account-3] ENABLE_CREDIT = NO ENABLE_DEBIT = NO + +# Make taler-auditor-httpd require authentication, so that the +# /monitoring/ requests of the auditor commands exercise it. +[auditor] +ACCESS_TOKEN = secret-token:test-auditor-api diff --git a/src/testing/test_auditor_api-rsa.conf b/src/testing/test_auditor_api-rsa.conf @@ -16,3 +16,8 @@ ENABLE_DEBIT = NO [exchange-account-3] ENABLE_CREDIT = NO ENABLE_DEBIT = NO + +# Make taler-auditor-httpd require authentication, so that the +# /monitoring/ requests of the auditor commands exercise it. +[auditor] +ACCESS_TOKEN = secret-token:test-auditor-api diff --git a/src/testing/test_auditor_api_version.c b/src/testing/test_auditor_api_version.c @@ -190,6 +190,7 @@ main (int argc, "taler-auditor-httpd", "taler-auditor-httpd", "-c", CONFIG_FILE, + "-n", "-L", "INFO", NULL)) { diff --git a/src/testing/testing_api_cmd_auditor_exec_auditor.c b/src/testing/testing_api_cmd_auditor_exec_auditor.c @@ -393,15 +393,38 @@ auditor_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } - GNUNET_CONFIGURATION_destroy (cfg); { const char *tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN"); - - /* taler-auditor-httpd requires the token iff it is set */ + char *cfg_tok = NULL; + + /* Find the token the way taler-auditor-httpd does: the environment + variable overrides the configuration, and the token may be stored + with or without the RFC 8959 prefix. If there is none, the httpd + must have been started with -n. */ + if ( (NULL == tok) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "ACCESS_TOKEN", + &cfg_tok)) ) + tok = cfg_tok; if (NULL != tok) { char *hdr; + if (0 != strncasecmp (tok, + "secret-token:", + strlen ("secret-token:"))) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "ACCESS_TOKEN", + "lacks RFC 8959 prefix"); + GNUNET_CONFIGURATION_destroy (cfg); + TALER_TESTING_interpreter_fail (is); + return; + } + tok += strlen ("secret-token:"); GNUNET_asprintf (&hdr, "%s: Bearer secret-token:%s", MHD_HTTP_HEADER_AUTHORIZATION, @@ -410,7 +433,9 @@ auditor_run (void *cls, hdr); GNUNET_free (hdr); } + GNUNET_free (cfg_tok); } + GNUNET_CONFIGURATION_destroy (cfg); run_next_helper (as); }