debuginfod: add CORS support

Message ID 023bab16-beca-40d0-a52c-c8c802aea701@gmail.com
State Committed
Headers
Series debuginfod: add CORS support |

Commit Message

Henning Meyer Dec. 4, 2024, 10:59 p.m. UTC
  This is my first attempt at implementing CORS support in debuginfod.
I should probably add or change tests. Since debuginfod-find does not 
need this functionality, it would be another test done with curl. I had 
a look at the existing tests,
run-debuginfod-webapi-concurrency.sh looks like it would be a good template.

I have confirmed that I can use debuginfod with this patch from my web 
application at
https://core-explorer.github.io/cdx-type/

Signed-off-by: Henning Meyer <hmeyer.eu@gmail.com>

---
  debuginfod/ChangeLog      |  6 ++++++
  debuginfod/debuginfod.cxx | 35 +++++++++++++++++++++++++++++++++--
  2 files changed, 39 insertions(+), 2 deletions(-)

                string header_dup;
@@ -3537,6 +3538,7 @@ handle_metrics (off_t* size)
      {
        *size = os.size();
        add_mhd_response_header (r, "Content-Type", "text/plain");
+      add_mhd_response_header (r, "Access-Control-Allow-Origin", "*");
      }
    return r;
  }
@@ -3763,7 +3765,10 @@ handle_metadata (MHD_Connection* conn,
                                         MHD_RESPMEM_MUST_COPY);
    *size = strlen(metadata_str);
    if (r)
+  {
      add_mhd_response_header(r, "Content-Type", "application/json");
+    add_mhd_response_header (r, "Access-Control-Allow-Origin", "*");
+  }
    return r;
  }

@@ -3780,11 +3785,28 @@ handle_root (off_t* size)
      {
        *size = version.size ();
        add_mhd_response_header (r, "Content-Type", "text/plain");
+      add_mhd_response_header (r, "Access-Control-Allow-Origin", "*");
      }
    return r;
  }


+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 +3860,17 @@ handler_cb (void * /*cls*/,

    try
      {
-      if (string(method) != "GET")
-        throw reportable_exception(400, "we support GET only");
+      if (method == string("OPTIONS"))
+        {
+          artifacttype = "OPTIONS";
+          inc_metric("http_requests_total", "type", artifacttype);
+          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 (method != string("GET"))
+          throw reportable_exception(400, "we only support GET and 
OPTIONS");

        /* Start decoding the URL. */
        size_t slash1 = url_copy.find('/', 1);
  

Comments

Frank Ch. Eigler Dec. 5, 2024, 12:40 a.m. UTC | #1
Hi -

> This is my first attempt at implementing CORS support in debuginfod.

Looks good to me really; will wait for a glance from others.

> I should probably add or change tests. [...]

I wouldn't overthink it - just add a "-i" to any random curl command
and look for ACAO:*.

I reconfigured the debuginfod.elfutils.org server (at the reverse
proxy level) to emit that header already.  This may be enough for your
web gadget to start working by default.  I'm not sure the OPTIONS part
is needed for that.

- FChE
  
Mark Wielaard Dec. 5, 2024, 2:35 p.m. UTC | #2
Hi Frank, Hi Henning,

On Wed, 2024-12-04 at 19:40 -0500, Frank Ch. Eigler wrote:
> > This is my first attempt at implementing CORS support in debuginfod.
> 
> Looks good to me really; will wait for a glance from others.

So the code looks correct if you want to handle the OPTION command and
add a header saying "Access-Control-Allow-Origin: *" to all request
replies.

But I have to admit I have no idea what that means. Could you include
an URL to a quick summary when and why you would add such an header?

Thanks,

Mark
  
Henning Meyer Dec. 6, 2024, 12:01 a.m. UTC | #3
Hi Mark,

it 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. Regards, Henning

On 05.12.24 15:35, Mark Wielaard wrote:

Hi Frank, Hi Henning,

On Wed, 2024-12-04 at 19:40 -0500, Frank Ch. Eigler wrote:
>> This is my first attempt at implementing CORS support in debuginfod.
> Looks good to me really; will wait for a glance from others.
So the code looks correct if you want to handle the OPTION command and
add a header saying "Access-Control-Allow-Origin: *" to all request
replies.

But I have to admit I have no idea what that means. Could you include
an URL to a quick summary when and why you would add such an header?

Thanks,

Mark
  
Frank Ch. Eigler Dec. 7, 2024, 8:05 p.m. UTC | #4
Hi -

I'm planning to commit this shortly:

From 4ebefc8f3b4b8fb68a55c960e70122fda50a0fb9 Mon Sep 17 00:00:00 2001
From: "Frank Ch. Eigler" <fche@redhat.com>
Date: Sat, 7 Dec 2024 15:01:54 -0500
Subject: [PATCH] debuginfod: add CORS response headers and OPTIONS method

From: Henning Meyer <hmeyer.eu@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.

Signed-off-by: Henning Meyer <hmeyer.eu@gmail.com>
Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
---
 debuginfod/debuginfod.cxx                  | 35 ++++++++++++++++++++--
 tests/run-debuginfod-federation-metrics.sh |  3 ++
 tests/run-debuginfod-find-metadata.sh      |  1 +
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
index 4bb517bde80f..fbcd0f627dc8 100644
--- a/debuginfod/debuginfod.cxx
+++ b/debuginfod/debuginfod.cxx
@@ -3785,6 +3785,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 +3855,17 @@ handler_cb (void * /*cls*/,
 
   try
     {
-      if (string(method) != "GET")
-        throw reportable_exception(400, "we support GET only");
+      if (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 +3913,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 +3960,8 @@ handler_cb (void * /*cls*/,
           throw reportable_exception(406, "File too large, max size=" + std::to_string(maxsize));
         }
 
+      // 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 +4051,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)
             {
diff --git a/tests/run-debuginfod-federation-metrics.sh b/tests/run-debuginfod-federation-metrics.sh
index 60fe69ca4f25..eef800010935 100755
--- a/tests/run-debuginfod-federation-metrics.sh
+++ b/tests/run-debuginfod-federation-metrics.sh
@@ -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..041d08233281 100755
--- a/tests/run-debuginfod-find-metadata.sh
+++ b/tests/run-debuginfod-find-metadata.sh
@@ -79,6 +79,7 @@ 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*'
+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'
  
Mark Wielaard Dec. 8, 2024, 12:11 a.m. UTC | #5
Hi Henning,

On Fri, Dec 06, 2024 at 01:01:34AM +0100, Henning Meyer wrote:
> it is the Cross-Origin-Resource-Sharing mechanism explained at
> https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Thanks, this might be a good URL to include in a comment so others
know what the extra headers are there for.

> 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

Right and then when the browser sents an OPTION request we reply with
Access-Control-Allow-Origin: * to indicate that is fine from
everywhere. A Access-Control-Allow-Methods: GET, OPTIONS to allow both
request methods. And a Access-Control-Allow-Headers: cache-control to
indicate the browser script may use the cache-control header. Is that
the only header we want to expose to scripts?

Also we only return these three headers for an OPTION request. Why are
we setting the Access-Control-Allow-Origin also on GET requests? Is
that necessary? Why not the other Access-Control-Allow headers?

> 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.

That is not how I read it. I looks like the OPTION request is always
necessary, but some older browsers didn't do it.

I do understand now why you would like to see these headers. But might
it be an idea to just let a proxy do this for us?

Cheers,

Mark
  
Florian Weimer Dec. 8, 2024, 11:06 a.m. UTC | #6
* Frank Ch. Eigler:

> Hi -
>
> I'm planning to commit this shortly:
>
> From 4ebefc8f3b4b8fb68a55c960e70122fda50a0fb9 Mon Sep 17 00:00:00 2001
> From: "Frank Ch. Eigler" <fche@redhat.com>
> Date: Sat, 7 Dec 2024 15:01:54 -0500
> Subject: [PATCH] debuginfod: add CORS response headers and OPTIONS method

What are the security implications of a shared origin when serving
(potentially third-party) debuginfo data?

I think it will allow public web clients to exfiltrate debuginfo data
from debuginfod servers on private intranets.  Previously, the
cross-origin restrictions on web content would have prevented that.

Thanks,
Florian
  
Frank Ch. Eigler Dec. 8, 2024, 1:19 p.m. UTC | #7
Hi -

> [...]
> I think it will allow public web clients to exfiltrate debuginfo data
> from debuginfod servers on private intranets.  Previously, the
> cross-origin restrictions on web content would have prevented that.

Yes, this is the flip side of the CORS default coin.  ISTM the
convenience is a larger benefit than this risk.  Users that disagree
can do the reverse-proxy header-filtering to defeat it.  'course we
can also be more noncomittal and make it a command line option.

- FChE
  
Henning Meyer Dec. 8, 2024, 6:47 p.m. UTC | #8
I think Florian is right.

I see a scenario where:
   1. someone runs a debuginfod service for closed source software in an 
internal network
   2. the names for debuginfod server instances are guessable
   3. users in corporate networks don't run adblockers
   4. the debuginfod defaults change
   5. it is now possible to exfiltrate sensitive debug information

I don't think making software insecure by default, even if it is convenient,
is appropriate in 2024. I don't want people not to run debuginfod instances
because of security considerations.

I still think it is a good idea to make it easy to enable
this when serving open source software.

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.

I would not like to add more environment variables.

Regards,
Henning

On 08.12.24 12:06, Florian Weimer wrote:
> * Frank Ch. Eigler:
>
>> Hi -
>>
>> I'm planning to commit this shortly:
>>
>>  From 4ebefc8f3b4b8fb68a55c960e70122fda50a0fb9 Mon Sep 17 00:00:00 2001
>> From: "Frank Ch. Eigler" <fche@redhat.com>
>> Date: Sat, 7 Dec 2024 15:01:54 -0500
>> Subject: [PATCH] debuginfod: add CORS response headers and OPTIONS method
> What are the security implications of a shared origin when serving
> (potentially third-party) debuginfo data?
>
> I think it will allow public web clients to exfiltrate debuginfo data
> from debuginfod servers on private intranets.  Previously, the
> cross-origin restrictions on web content would have prevented that.
>
> Thanks,
> Florian
>
  
Frank Ch. Eigler Dec. 8, 2024, 9:01 p.m. UTC | #9
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" <fche@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.eu@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.eu@gmail.com>
Signed-off-by: Frank Ch. Eigler <fche@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'
  

Patch

diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog
index 0e4810bb..8e2377a0 100644
--- a/debuginfod/ChangeLog
+++ b/debuginfod/ChangeLog
@@ -1,3 +1,9 @@ 
+2024-12-04: Henning Meyer <hmeyer.eu@gmail.com>
+    * debuginfod.cxx:
+    (handle_options): new_function
+    (handle_buildid, handle_metrics, handle_root, handle_metadata):
+    add Access-Control-Allow-Origin header
+    (handler_cb): handle http OPTIONS method
  2023-04-21  Frank Ch. Eigler <fche@redhat.com>

      * debuginfod.cxx (groom): Fix -r / -X logic.
diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
index 4bb517bd..7f7d304f 100644
--- a/debuginfod/debuginfod.cxx
+++ b/debuginfod/debuginfod.cxx
@@ -3365,6 +3365,7 @@  handle_buildid (MHD_Connection* conn,
              {
                add_mhd_response_header (r, "Content-Type",
                         "application/octet-stream");
+              add_mhd_response_header (r, 
"Access-Control-Allow-Origin", "*");
                // Copy the incoming headers
                const char * hdrs = debuginfod_get_headers(client);