Hi - > [...] > I am not sure what the best way forward is, > usually I would say, make it all configurable, > but debuginfod doesn't have a central configuration file.
Marty, we will not need central configuration files where we're going. > I would not like to add more environment variables. Note that the env vars are used for configuring the *client*, because it sometimes embedded deep into other preexisting applications, so we can't easily add command line options. - FChE >From e574f0089a2389267a95417c11ae5aa42a5b8bf8 Mon Sep 17 00:00:00 2001 From: "Frank Ch. Eigler" <f...@redhat.com> Date: Sat, 7 Dec 2024 15:01:54 -0500 Subject: [PATCH] debuginfod: in --cors mode, add CORS response headers and OPTIONS method From: Henning Meyer <hmeyer...@gmail.com> CORS is the Cross-Origin-Resource-Sharing mechanism explained at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 1. by default JavaScript code from Website A cannot request arbitrary resources from website B, these are called cross-origin-requests 2. The browser performs what is called a preflight check, this the OPTIONS method 3. the response allows website B fine-grained control over what the web browser should allow 4. Setting "Access-Control-Allow-Origin: *" tells the web browser to allow all access, e.g. the same behavior you get with curl or debuginfod-find The website mentions that the corresponding spec has been changed, such that preflight requests are no longer necessary, but in the browsers I use today (Firefox 132 and Chromium 131) they are still necessary. I have confirmed that I can use debuginfod with this patch from my web application at https://core-explorer.github.io/cdx-type/ FChE simplified the code and added a few quick "curl -i | grep" tests to confirm the new headers are there. * debuginfod/debuginfod.cxx (handle_options): New function. (handler_cb): Call it for OPTIONS. Add ACAO header for all successful requests. (parse_opt): Parse --cors. * tests/run-debuginfod-federation-metrics.sh, tests/run-debuginfod-find-metadata.sh: Lightly test. * doc/debuginfod.8: Document --cors option, default off. Signed-off-by: Henning Meyer <hmeyer...@gmail.com> Signed-off-by: Frank Ch. Eigler <f...@redhat.com> --- NEWS | 4 ++ debuginfod/debuginfod.cxx | 46 ++++++++++++++++++++-- doc/debuginfod.8 | 7 ++++ tests/run-debuginfod-federation-metrics.sh | 7 +++- tests/run-debuginfod-find-metadata.sh | 5 ++- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 1189c6037693..4cb5b2260fec 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +Version 0.193 (one after 0.192) + +debuginfod: Add CORS (webapp access) support to webapi. + Version 0.192 "New rules, faster tools" CONDUCT: A new code of conduct has been adopted. See the diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx index 4bb517bde80f..cdf05456b41e 100644 --- a/debuginfod/debuginfod.cxx +++ b/debuginfod/debuginfod.cxx @@ -448,6 +448,8 @@ static const struct argp_option options[] = { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 }, { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 }, { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 }, +#define ARGP_KEY_CORS 0x1000 + { "cors", ARGP_KEY_CORS, NULL, 0, "Add CORS response headers to HTTP queries, default no.", 0 }, { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 }, { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 }, { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 }, @@ -510,6 +512,7 @@ static volatile sig_atomic_t sigusr1 = 0; static volatile sig_atomic_t forced_groom_count = 0; static volatile sig_atomic_t sigusr2 = 0; static unsigned http_port = 8002; +static bool webapi_cors = false; static unsigned rescan_s = 300; static unsigned groom_s = 86400; static bool maxigroom = false; @@ -614,6 +617,9 @@ parse_opt (int key, char *arg, if (http_port == 0 || http_port > 65535) argp_failure(state, 1, EINVAL, "port number"); break; + case ARGP_KEY_CORS: + webapi_cors = true; + break; case 'F': scan_files = true; break; case 'R': scan_archives[".rpm"]="cat"; // libarchive groks rpm natively @@ -3785,6 +3791,23 @@ handle_root (off_t* size) } +static struct MHD_Response* +handle_options (off_t* size) +{ + static char empty_body[] = " "; + MHD_Response* r = MHD_create_response_from_buffer (1, empty_body, + MHD_RESPMEM_PERSISTENT); + if (r != NULL) + { + *size = 1; + add_mhd_response_header (r, "Access-Control-Allow-Origin", "*"); + add_mhd_response_header (r, "Access-Control-Allow-Methods", "GET, OPTIONS"); + add_mhd_response_header (r, "Access-Control-Allow-Headers", "cache-control"); + } + return r; +} + + //////////////////////////////////////////////////////////////////////// @@ -3838,8 +3861,17 @@ handler_cb (void * /*cls*/, try { - if (string(method) != "GET") - throw reportable_exception(400, "we support GET only"); + if (webapi_cors && method == string("OPTIONS")) + { + inc_metric("http_requests_total", "type", method); + r = handle_options(& http_size); + rc = MHD_queue_response (connection, MHD_HTTP_OK, r); + http_code = MHD_HTTP_OK; + MHD_destroy_response (r); + return rc; + } + else if (string(method) != "GET") + throw reportable_exception(400, "we support OPTIONS+GET only"); /* Start decoding the URL. */ size_t slash1 = url_copy.find('/', 1); @@ -3887,7 +3919,7 @@ handler_cb (void * /*cls*/, // get the resulting fd so we can report its size int fd; - r = handle_buildid(connection, buildid, artifacttype, suffix, &fd); + r = handle_buildid (connection, buildid, artifacttype, suffix, &fd); if (r) { struct stat fs; @@ -3934,6 +3966,9 @@ handler_cb (void * /*cls*/, throw reportable_exception(406, "File too large, max size=" + std::to_string(maxsize)); } + if (webapi_cors) + // add ACAO header for all successful requests + add_mhd_response_header (r, "Access-Control-Allow-Origin", "*"); rc = MHD_queue_response (connection, MHD_HTTP_OK, r); http_code = MHD_HTTP_OK; MHD_destroy_response (r); @@ -4023,6 +4058,7 @@ dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles) { string artifacttype = "debuginfo"; r = handle_buildid (0, buildid, artifacttype, "", &alt_fd); + // NB: no need for ACAO etc. headers; this is not getting sent to a client } catch (const reportable_exception& e) { @@ -5706,7 +5742,9 @@ main (int argc, char *argv[]) } obatched(clog) << "started http server on" << (d4 != NULL ? " IPv4 " : " IPv4 IPv6 ") - << "port=" << http_port << endl; + << "port=" << http_port + << (webapi_cors ? " with cors" : "") + << endl; // add maxigroom sql if -G given if (maxigroom) diff --git a/doc/debuginfod.8 b/doc/debuginfod.8 index f35ce6c1a9ca..1cf9a18fe2f3 100644 --- a/doc/debuginfod.8 +++ b/doc/debuginfod.8 @@ -154,6 +154,12 @@ listen, to service HTTP requests. Both IPv4 and IPV6 sockets are opened, if possible. The webapi is documented below. The default port number is 8002. +.TP +.B "\-\-cors" +Add CORS-related response headers and OPTIONS method processing. +This allows third-party webapps to query debuginfod data, which may +or may not be desirable. Default is no. + .TP .B "\-I REGEX" "\-\-include=REGEX" "\-X REGEX" "\-\-exclude=REGEX" Govern the inclusion and exclusion of file names under the search @@ -563,3 +569,4 @@ Default database file. .I "debuginfod-find(1)" .I "sqlite3(1)" .I \%https://prometheus.io/docs/instrumenting/exporters/ +.I \%https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS diff --git a/tests/run-debuginfod-federation-metrics.sh b/tests/run-debuginfod-federation-metrics.sh index 60fe69ca4f25..715a575cbc3b 100755 --- a/tests/run-debuginfod-federation-metrics.sh +++ b/tests/run-debuginfod-federation-metrics.sh @@ -37,7 +37,7 @@ base=9000 get_ports # Launch server which will be unable to follow symlinks -env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -d ${DB} -F -U -t0 -g0 -p $PORT1 L D F > vlog$PORT1 2>&1 & +env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -d ${DB} -F -U -t0 -g0 -p $PORT1 --cors L D F > vlog$PORT1 2>&1 & PID1=$! tempfiles vlog$PORT1 errfiles vlog$PORT1 @@ -75,7 +75,7 @@ wait_ready $PORT1 'thread_busy{role="http-metrics"}' 1 export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache2 mkdir -p $DEBUGINFOD_CACHE_PATH # NB: run in -L symlink-following mode for the L subdir -env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS=http://127.0.0.1:$PORT1 ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -d ${DB}_2 -F -U -p $PORT2 -L L D > vlog$PORT2 2>&1 & +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS=http://127.0.0.1:$PORT1 ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -d ${DB}_2 -F -U -p $PORT2 --cors -L L D > vlog$PORT2 2>&1 & PID2=$! tempfiles vlog$PORT2 errfiles vlog$PORT2 @@ -153,6 +153,8 @@ testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog 1 curl -s http://127.0.0.1:$PORT1/badapi curl -s http://127.0.0.1:$PORT1/metrics curl -s http://127.0.0.1:$PORT2/metrics +curl -i -s http://127.0.0.1:$PORT1/metrics | grep -i access.control.allow.origin: +curl -X OPTIONS -i -s http://127.0.0.1:$PORT1/ | grep -i access.control.allow.origin: curl -s http://127.0.0.1:$PORT1/metrics | grep -q 'http_responses_total.*result.*error' curl -s http://127.0.0.1:$PORT2/metrics | grep -q 'http_responses_total.*result.*upstream' curl -s http://127.0.0.1:$PORT1/metrics | grep 'http_responses_duration_milliseconds_count' @@ -181,6 +183,7 @@ rm -f .client_cache*/$BUILDID/debuginfo testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID +curl -i -s http://127.0.0.1:$PORT2/buildid/$BUILDID/debuginfo | grep -i access.control.allow.origin: rm -f .client_cache*/$BUILDID/debuginfo testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID diff --git a/tests/run-debuginfod-find-metadata.sh b/tests/run-debuginfod-find-metadata.sh index 78a34f09490f..99759cff20a8 100755 --- a/tests/run-debuginfod-find-metadata.sh +++ b/tests/run-debuginfod-find-metadata.sh @@ -52,7 +52,7 @@ wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 wait_ready $PORT1 'thread_busy{role="scan"}' 0 env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS="http://127.0.0.1:$PORT1 https://bad/url.web" ${VALGRIND_CMD} ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -U \ - -d ${DB}_2 -p $PORT2 -t0 -g0 D > vlog$PORT2 2>&1 & + -d ${DB}_2 -p $PORT2 -t0 -g0 --cors D > vlog$PORT2 2>&1 & PID2=$! tempfiles vlog$PORT2 errfiles vlog$PORT2 @@ -79,6 +79,9 @@ test $N_FOUND -eq 2 # Query via the webapi as well curl http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' +# no --cors on $PORT1's debuginfod +test "`curl -s -i http://127.0.0.1:$PORT1'/metadata?key=glob&value=/usr/bin/*hi*' | grep -i access.control.allow.origin: || true`" == "" +curl -s -i http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | grep -i access.control.allow.origin: test `curl -s http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | jq '.results[0].buildid == "f17a29b5a25bd4960531d82aa6b07c8abe84fa66"'` = 'true' test `curl -s http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | jq '.results[0].file == "/usr/bin/hithere"'` = 'true' test `curl -s http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | jq '.results[0].archive | test(".*hithere.*deb")'` = 'true' -- 2.47.1