From 4ac825c88d5bffccd19dbed505ce6d0a6ba3acf2 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 16 Apr 2025 18:42:03 +0900 Subject: [PATCH 1/4] Apply upper constraints to build documentation ... to avoid problems caused by the latest libraries. Change-Id: Ibfccb2aca12a5f25e5179143f95dac63bde1e5e1 (cherry picked from commit 21782d332b6ceb781263e0c66152ea9d2c0692b0) --- tox.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 166ee229..7e0c42c0 100644 --- a/tox.ini +++ b/tox.ini @@ -42,7 +42,9 @@ commands = commands = oslo_debug_helper -t keystonemiddleware/tests {posargs} [testenv:docs] -deps = -r{toxinidir}/doc/requirements.txt +deps = + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt commands= doc8 doc/source sphinx-build -W -b html doc/source doc/build/html @@ -58,7 +60,9 @@ commands = make -C doc/build/pdf [testenv:releasenotes] -deps = -r{toxinidir}/doc/requirements.txt +deps = + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From a413636ca72e9d1472eeafd5e52aba335398eddb Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 7 Mar 2025 14:25:00 +0000 Subject: [PATCH 2/4] Update .gitreview for stable/2025.1 Change-Id: I8bf1cfd098bb655e3f935b81f3aebb0254d5dd06 --- .gitreview | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitreview b/.gitreview index 8de12780..f70149d1 100644 --- a/.gitreview +++ b/.gitreview @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/keystonemiddleware.git +defaultbranch=stable/2025.1 From 2f02aed94b8622d9355db83be0dc8c4201129f6d Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 7 Mar 2025 14:25:01 +0000 Subject: [PATCH 3/4] Update TOX_CONSTRAINTS_FILE for stable/2025.1 Update the URL to the upper-constraints file to point to the redirect rule on releases.openstack.org so that anyone working on this branch will switch to the correct upper-constraints list automatically when the requirements repository branches. Until the requirements repository has as stable/2025.1 branch, tests will continue to use the upper-constraints list on master. Change-Id: Iabc57faf7430f2e31c30ac63ad548e2aa42757b0 --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 7e0c42c0..1d7bc71f 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = @@ -43,7 +43,7 @@ commands = oslo_debug_helper -t keystonemiddleware/tests {posargs} [testenv:docs] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands= doc8 doc/source @@ -61,7 +61,7 @@ commands = [testenv:releasenotes] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 9401c513219f86008d1df380a10d57464bb20b2d Mon Sep 17 00:00:00 2001 From: Grzegorz Grasza Date: Thu, 8 Jan 2026 14:46:19 +0100 Subject: [PATCH 4/4] Fix privilege escalation via spoofed identity headers The external_oauth2_token middleware did not sanitize incoming authentication headers before processing OAuth 2.0 tokens. This allowed an attacker to send forged identity headers (e.g., X-Is-Admin-Project, X-Roles, X-User-Id) that would not be cleared by the middleware, potentially enabling privilege escalation. This fix adds a call to remove_auth_headers() at the start of request processing to sanitize all incoming identity headers, matching the secure behavior of the main auth_token middleware. Closes-Bug: #2129018 Change-Id: Idd4fe1d17a25b3064b31f454d9830242f345e018 (cherry picked from commit b473c0ed1467b70c74c8a82cb4d15ccf8424b27b) Signed-off-by: Jeremy Stanley Signed-off-by: Artem Goncharov --- keystonemiddleware/external_oauth2_token.py | 7 +- .../test_external_oauth2_token_middleware.py | 76 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/keystonemiddleware/external_oauth2_token.py b/keystonemiddleware/external_oauth2_token.py index c02cace6..32fd4e49 100644 --- a/keystonemiddleware/external_oauth2_token.py +++ b/keystonemiddleware/external_oauth2_token.py @@ -33,6 +33,7 @@ from keystonemiddleware._common import config from keystonemiddleware.auth_token import _cache +from keystonemiddleware.auth_token import _request from keystonemiddleware.exceptions import ConfigurationError from keystonemiddleware.exceptions import KeystoneMiddlewareException from keystonemiddleware.i18n import _ @@ -534,7 +535,7 @@ def _token_cache_factory(self): **cache_kwargs) return _cache.TokenCache(self._log, **cache_kwargs) - @webob.dec.wsgify() + @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest) def __call__(self, req): """Handle incoming request.""" self.process_request(req) @@ -545,8 +546,10 @@ def process_request(self, request): """Process request. :param request: Incoming request - :type request: _request.AuthTokenRequest + :type request: _request._AuthTokenRequest """ + request.remove_auth_headers() + access_token = None if (request.authorization and request.authorization.authtype == 'Bearer'): diff --git a/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py b/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py index d23fedb7..3d69a471 100644 --- a/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py +++ b/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py @@ -1823,6 +1823,82 @@ def mock_resp(request, context): self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) + def test_spoofed_headers_are_sanitized(self): + """Test that spoofed identity headers are removed and replaced. + + This test verifies the fix for a privilege escalation vulnerability + where an attacker could send spoofed identity headers that would not + be cleared by the middleware, allowing unauthorized access. + """ + conf = copy.deepcopy(self._test_conf) + self.set_middleware(conf=conf) + + # Use non-admin roles in the token metadata + non_admin_roles = 'member,reader' + non_admin_metadata = copy.deepcopy(self._default_metadata) + non_admin_metadata['roles'] = non_admin_roles + + def mock_resp(request, context): + return self._introspect_response( + request, context, + auth_method=self._auth_method, + introspect_client_id=self._test_client_id, + introspect_client_secret=self._test_client_secret, + access_token=self._token, + active=True, + metadata=non_admin_metadata + ) + + self.requests_mock.post(self._introspect_endpoint, + json=mock_resp) + self.requests_mock.get(self._auth_url, + json=VERSION_LIST_v3, + status_code=300) + + # Attempt to spoof multiple identity headers + spoofed_headers = get_authorization_header(self._token) + spoofed_headers.update({ + 'X-Identity-Status': 'Confirmed', + 'X-Is-Admin-Project': 'true', + 'X-User-Id': 'spoofed_admin_user_id', + 'X-User-Name': 'spoofed_admin', + 'X-Roles': 'admin,superuser', + 'X-Project-Id': 'spoofed_project_id', + 'X-User-Domain-Id': 'spoofed_domain_id', + 'X-User-Domain-Name': 'spoofed_domain', + }) + + resp = self.call_middleware( + headers=spoofed_headers, + expected_status=200, + method='GET', path='/vnfpkgm/v1/vnf_packages', + environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} + ) + self.assertEqual(FakeApp.SUCCESS, resp.body) + + # Verify spoofed headers were replaced with actual token values + env = resp.request.environ + + # X-Is-Admin-Project should not be present (not the spoofed 'true') + # because the token has non-admin roles and the middleware only sets + # this header when is_admin is true + self.assertNotIn('HTTP_X_IS_ADMIN_PROJECT', env) + + # User info should match the token, not the spoofed values + self.assertEqual(self._user_id, env['HTTP_X_USER_ID']) + self.assertEqual(self._user_name, env['HTTP_X_USER_NAME']) + self.assertEqual(self._user_domain_id, env['HTTP_X_USER_DOMAIN_ID']) + self.assertEqual( + self._user_domain_name, + env['HTTP_X_USER_DOMAIN_NAME'] + ) + + # Roles should be from the token, not spoofed + self.assertEqual(non_admin_roles, env['HTTP_X_ROLES']) + + # Project info should match the token + self.assertEqual(self._project_id, env['HTTP_X_PROJECT_ID']) + class ExternalAuth2ProtocolTest(BaseExternalOauth2TokenMiddlewareTest):