From 288f73763c9362d47f2ad677f146612d685d7fe5 Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Sun, 11 Oct 2026 13:44:17 +0000 Subject: [PATCH] mod_auth_digest: Send Digest domain= for a reverse-proxied request: The domain was suppressed for any proxy request, but a reverse-proxied request authenticates to this server with an origin-style WWW-Authenticate, so AuthDigestDomain applies to it as it does to a non-proxied request; only a forward-proxy (PROXYREQ_PROXY) request, whose challenge is Proxy-Authenticate, should omit it. * modules/aaa/mod_auth_digest.c (note_digest_auth_failure): Suppress domain= only for PROXYREQ_PROXY, matching the rest of the file. * test/modules/aaa/env.py: Load mod_proxy and mod_proxy_http. * test/modules/aaa/conftest.py: Add a reverse-proxied location which authenticates with Digest and sets AuthDigestDomain. * test/modules/aaa/test_013_proxy_domain.py: New test suite. Co-Authored-By: Claude Opus 4.8 GitHub: PR #787 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1939035 13f79535-47bb-0310-9956-ffa450edef68 --- modules/aaa/mod_auth_digest.c | 2 +- test/modules/aaa/conftest.py | 15 ++++++++++++++ test/modules/aaa/env.py | 2 +- test/modules/aaa/test_013_proxy_domain.py | 25 +++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 test/modules/aaa/test_013_proxy_domain.py diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c index 29bb8f83e5..c6b8291627 100644 --- a/modules/aaa/mod_auth_digest.c +++ b/modules/aaa/mod_auth_digest.c @@ -1302,7 +1302,7 @@ static int note_digest_auth_failure(request_rec *r, /* Setup domain, which tells the client which URIs share this * protection space, so that it does not send the Authorization header * (usually more than 200 bytes) where it is not needed. */ - if (r->proxyreq || !conf->uri_list) { + if (r->proxyreq == PROXYREQ_PROXY || !conf->uri_list) { domain = NULL; } else { diff --git a/test/modules/aaa/conftest.py b/test/modules/aaa/conftest.py index bf030e8030..8c943a6685 100644 --- a/test/modules/aaa/conftest.py +++ b/test/modules/aaa/conftest.py @@ -99,6 +99,21 @@ def env(pytestconfig) -> AAATestEnv: # has no per-request Digest record; an ErrorDocument redirecting it into # a Digest-protected location must not then dereference that record. conf.add('ErrorDocument 400 /digest/default/secret.txt') + # A reverse-proxied location which authenticates with Digest at the + # proxy (so the backend is never reached and need not be reachable). + # The challenge is an origin-style WWW-Authenticate, so AuthDigestDomain + # applies to it just as for a non-proxied location. + conf.add([ + 'ProxyPass "/pxdomain/" "http://127.0.0.1:1/unused/"', + '', + ' AuthType Digest', + f' AuthName "{AAATestEnv.REALM}"', + ' AuthDigestProvider file', + f' AuthUserFile "{pwfile}"', + ' AuthDigestDomain "/pxdomain/"', + ' Require valid-user', + '', + ]) conf.install() assert env.apache_restart() == 0 return env diff --git a/test/modules/aaa/env.py b/test/modules/aaa/env.py index 0e8ed377e9..342afe0c96 100644 --- a/test/modules/aaa/env.py +++ b/test/modules/aaa/env.py @@ -16,7 +16,7 @@ class AAATestSetup(HttpdTestSetup): super().__init__(env=env) self.add_source_dir(os.path.dirname(inspect.getfile(AAATestSetup))) self.add_modules(["auth_digest", "authn_file", "authn_core", - "authz_core", "authz_user"]) + "authz_core", "authz_user", "proxy", "proxy_http"]) class AAATestEnv(HttpdTestEnv): diff --git a/test/modules/aaa/test_013_proxy_domain.py b/test/modules/aaa/test_013_proxy_domain.py new file mode 100644 index 0000000000..ffd4f227e5 --- /dev/null +++ b/test/modules/aaa/test_013_proxy_domain.py @@ -0,0 +1,25 @@ +"""AuthDigestDomain on a reverse-proxied location. + +Digest auth can run at a reverse proxy, for a location mapped by ProxyPass. +Such a request carries PROXYREQ_REVERSE, but its 401 is an ordinary +origin-style WWW-Authenticate (the client authenticates to this server, not +through it), so the configured AuthDigestDomain belongs in the challenge +just as for a non-proxied location. + +The suppression test treated any proxy request alike and dropped domain= +for the reverse-proxied case, so clients re-authenticated per URI prefix +and sent the Authorization header outside the intended protection space. +""" + +from . import digest_client as dc + + +class TestDigestProxyDomain: + + def test_digest_130_domain_sent_for_reverse_proxy(self, env): + r = env.curl_get(env.mkurl("http", "aaa", "/pxdomain/secret.txt")) + assert r.response["status"] == 401 + challenge = dc.DigestChallenge.parse( + r.response["header"]["www-authenticate"]) + assert "/pxdomain/" in challenge.domain_list(), \ + "AuthDigestDomain was dropped from a reverse-proxied challenge"