]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
debuginfod: in --cors mode, add CORS response headers and OPTIONS method
authorHenning Meyer <hmeyer.eu@gmail.com>
Sat, 7 Dec 2024 20:01:54 +0000 (15:01 -0500)
committerFrank Ch. Eigler <fche@redhat.com>
Sun, 8 Dec 2024 21:01:05 +0000 (16:01 -0500)
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.eu@gmail.com>
Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
NEWS
debuginfod/debuginfod.cxx
doc/debuginfod.8
tests/run-debuginfod-federation-metrics.sh
tests/run-debuginfod-find-metadata.sh

diff --git a/NEWS b/NEWS
index 1189c6037693a2ac586f7895b52c3a9fa14d6347..4cb5b2260fecb1eab1f015524cd637165a5a37f8 100644 (file)
--- 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
index 4bb517bde80f7ee94eefc67b2607df430be3cb0d..cdf05456b41e3d026dc6c04351729fe22ac34a8d 100644 (file)
@@ -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)
index f35ce6c1a9ca0fe804eb1d6ac029139deda1ef45..1cf9a18fe2f34811e0897525f7ee974b617b8e16 100644 (file)
@@ -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
index 60fe69ca4f251e47bde32be92bb498defd127517..715a575cbc3bb0420c6862267805aeab340e06d7 100755 (executable)
@@ -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
 
index 78a34f09490f020c8edfd6f12736cafd6ea2edb7..99759cff20a8445fa9e6d28d45002b3c3b0f9454 100755 (executable)
@@ -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'