Version in base suite: 10.9.0-2+deb13u1 Base version: python-keystonemiddleware_10.9.0-2+deb13u1 Target version: python-keystonemiddleware_10.9.0-2+deb13u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-keystonemiddleware/python-keystonemiddleware_10.9.0-2+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-keystonemiddleware/python-keystonemiddleware_10.9.0-2+deb13u2.dsc changelog | 9 patches/CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch | 265 ++++++++++ patches/series | 1 3 files changed, 275 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzft_2a8_/python-keystonemiddleware_10.9.0-2+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzft_2a8_/python-keystonemiddleware_10.9.0-2+deb13u2.dsc: no acceptable signature found diff -Nru python-keystonemiddleware-10.9.0/debian/changelog python-keystonemiddleware-10.9.0/debian/changelog --- python-keystonemiddleware-10.9.0/debian/changelog 2026-01-15 07:57:22.000000000 +0000 +++ python-keystonemiddleware-10.9.0/debian/changelog 2026-10-30 15:11:18.000000000 +0000 @@ -1,3 +1,12 @@ +python-keystonemiddleware (10.9.0-2+deb13u2) trixie; urgency=high + + * CVE-2026-115642 / OSSA-2026-050: keystonemiddleware access_rules bypass + via X-Service-Token. Applied upstream patch "Fix service-token presence + bypass of access_rules and token bind". (Closes: #1152543): + - CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_o....patch + + -- Thomas Goirand Fri, 30 Oct 2026 16:11:18 +0100 + python-keystonemiddleware (10.9.0-2+deb13u1) trixie-security; urgency=medium * CVE-2026-22797 / OSSA-2026-001: privilege escalation via spoofed identity diff -Nru python-keystonemiddleware-10.9.0/debian/patches/CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch python-keystonemiddleware-10.9.0/debian/patches/CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch --- python-keystonemiddleware-10.9.0/debian/patches/CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-keystonemiddleware-10.9.0/debian/patches/CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch 2026-10-30 15:11:18.000000000 +0000 @@ -0,0 +1,265 @@ +Description: Fix service-token presence bypass of access_rules and token bind + . + In BaseAuthProtocol.validate_allowed_request, the presence of the + X-Service-Token header alone was treated as sufficient to skip + application credential access_rules enforcement. The same presence + check gated _confirm_token_bind(), so an arbitrary X-Service-Token + also skipped Kerberos (and other) token binding when + delay_auth_decision left the request alive. + . + Require service_token_valid before skipping either check. +Author: Artem Goncharov +Date: Mon, 21 Sep 2026 13:58:30 +0200 +Origin: upstream, https://review.opendev.org/c/openstack/keystonemiddleware/+/1012874 +Bug: https://launchpad.net/bugs/2162748 +Bug-Debian: https://bugs.debian.org/1152543 +Last-Update: 2026-10-30 + +Index: python-keystonemiddleware/keystonemiddleware/auth_token/__init__.py +=================================================================== +--- python-keystonemiddleware.orig/keystonemiddleware/auth_token/__init__.py 2026-10-30 16:11:16.206735924 +0100 ++++ python-keystonemiddleware/keystonemiddleware/auth_token/__init__.py 2026-10-30 16:11:16.202735812 +0100 +@@ -412,7 +412,12 @@ + allow_expired=allow_expired) + if user_auth_ref.version != 'v2.0': + self.validate_allowed_request(request, data['token']) +- if not request.service_token: ++ # Require a *valid* service token before skipping bind, same ++ # presence-vs-validity fix as validate_allowed_request ++ # (LP#2162748). An arbitrary X-Service-Token must not defeat ++ # Kerberos (or other) token binding. ++ if not (request.service_token and ++ request.service_token_valid): + self._confirm_token_bind(user_auth_ref, request) + except ksm_exceptions.InvalidToken: + self.log.info('Invalid user token') +@@ -572,10 +577,10 @@ + ' [keystone_authtoken] is not a valid service' + ' type in the catalog.') + raise ksm_exceptions.InvalidToken(_('Token authorization failed')) +- if request.service_token: +- # The request may not match an allowed request, but the presence +- # of the service token indicates this is a chain of requests and +- # hence this request was not user-facing ++ if request.service_token and request.service_token_valid: ++ # The request may not match an allowed request, but a valid ++ # service token indicates this is a chain of requests and ++ # hence this request was not user-facing (LP#2162748). + return + for access_rule in access_rules: + method = access_rule['method'] +Index: python-keystonemiddleware/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py +=================================================================== +--- python-keystonemiddleware.orig/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py 2026-10-30 16:11:16.206735924 +0100 ++++ python-keystonemiddleware/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py 2026-10-30 16:11:16.202735812 +0100 +@@ -828,6 +828,65 @@ + use_kerberos='ronald@MCDONALDS.COM', + success=False) + ++ def test_kerberos_bind_skipped_by_invalid_service_token(self): ++ """LP#2162748 follow-up: garbage X-Service-Token skips Kerberos bind. ++ ++ ``_confirm_token_bind()`` is gated on ``if not request.service_token`` ++ (header presence), not ``service_token_valid``. With ++ ``delay_auth_decision``, the invalid service token does not abort ++ the request, so a Kerberos-bound user token is marked Confirmed ++ without a Negotiate session — the theft scenario binding exists ++ to prevent. ++ """ ++ conf = { ++ "www_authenticate_uri": BASE_HOST, ++ "auth_type": "admin_token", ++ "endpoint": f"{BASE_URI}/v3", ++ "token": FAKE_ADMIN_TOKEN_ID, ++ "enforce_token_bind": "kerberos", ++ "auth_version": self.auth_version, ++ "delay_auth_decision": True, ++ } ++ self.set_middleware(conf=conf) ++ ++ # Replace FakeApp — its delayed-auth branches KeyError when no ++ # service-status header is present. We only care what auth_token ++ # stamped on the request. ++ @webob.dec.wsgify ++ def _passthrough(req): ++ return webob.Response(body=b"OK", status=200) ++ ++ self.middleware._app = _passthrough ++ token = self.token_dict["uuid_token_bind"] ++ ++ # Baseline: no service token, no Kerberos → bind fails → user Invalid. ++ req = webob.Request.blank("/") ++ req.headers["X-Auth-Token"] = token ++ resp = req.get_response(self.middleware) ++ self.assertEqual(200, resp.status_int) ++ self.assertEqual( ++ "Invalid", ++ req.environ.get("HTTP_X_IDENTITY_STATUS"), ++ ) ++ ++ # Attack: same bound token + arbitrary X-Service-Token, still no ++ # Kerberos. Bind is skipped; user is Confirmed. ++ req = webob.Request.blank("/") ++ req.headers["X-Auth-Token"] = token ++ req.headers["X-Service-Token"] = "invalid-garbage" ++ resp = req.get_response(self.middleware) ++ self.assertEqual(200, resp.status_int) ++ self.assertEqual( ++ "Invalid", ++ req.environ.get("HTTP_X_SERVICE_IDENTITY_STATUS"), ++ ) ++ # Vulnerable before the fix: Confirmed without Kerberos. After the ++ # fix, bind runs because service_token_valid is False → Invalid. ++ self.assertEqual( ++ "Invalid", ++ req.environ.get("HTTP_X_IDENTITY_STATUS"), ++ ) ++ + def test_uuid_bind_token_with_unknown_named_FOO(self): + token = self.token_dict['uuid_token_bind'] + +@@ -1263,6 +1322,20 @@ + expected_status=200, + method='GET', path='/v2/images') + ++ def test_app_cred_access_rules_invalid_service_token(self): ++ """Arbitrary invalid X-Service-Token must not bypass access_rules.""" ++ self.set_middleware(conf={"service_type": "compute"}) ++ token = self.examples.v3_APP_CRED_ACCESS_RULES ++ self.call_middleware( ++ headers={ ++ "X-Auth-Token": token, ++ "X-Service-Token": "invalid-garbage", ++ }, ++ expected_status=401, ++ method="GET", ++ path="/v2.1/servers/someuuid", ++ ) ++ + def test_app_cred_no_access_rules_token(self): + self.set_middleware(conf={'service_type': 'compute'}) + token = self.examples.v3_APP_CRED_EMPTY_ACCESS_RULES +Index: python-keystonemiddleware/keystonemiddleware/tests/unit/auth_token/test_base_middleware.py +=================================================================== +--- python-keystonemiddleware.orig/keystonemiddleware/tests/unit/auth_token/test_base_middleware.py 2026-10-30 16:11:16.206735924 +0100 ++++ python-keystonemiddleware/keystonemiddleware/tests/unit/auth_token/test_base_middleware.py 2026-10-30 16:11:16.202735812 +0100 +@@ -19,6 +19,7 @@ + import webob + + from keystonemiddleware import auth_token ++from keystonemiddleware.auth_token import _exceptions as ksm_exceptions + from keystonemiddleware.auth_token import _request + + +@@ -228,3 +229,92 @@ + resp = self.call(m, headers={'X-Project-Id': project_id, + 'X-Domain-Id': domain_id}) + self.assertEqual(body, resp.text) ++ ++ def test_app_cred_access_rules_invalid_service_token(self): ++ """An invalid service token must not bypass access_rules.""" ++ access_rules = [ ++ {"path": "/v2.1/servers", "method": "GET", "service": "compute"} ++ ] ++ token_data = { ++ "is_admin": 0, ++ "user": {"id": "user1"}, ++ "methods": [], ++ "application_credential": { ++ "id": "app_cred_1", ++ "access_rules": access_rules, ++ }, ++ "catalog": [ ++ { ++ "type": "compute", ++ "name": "nova", ++ "endpoints": [ ++ { ++ "id": "ep", ++ "interface": "public", ++ "url": "http://nova:8774/v2.1", ++ } ++ ], ++ } ++ ], ++ } ++ mock_request = mock.MagicMock() ++ mock_request.method = "GET" ++ mock_request.path = "/v2.1/servers/someuuid" ++ mock_request.service_token = "garbage-invalid-token" ++ mock_request.service_token_valid = False ++ ++ class TestMiddleware(auth_token.BaseAuthProtocol): ++ def __init__(self, app): ++ super(TestMiddleware, self).__init__(app) ++ self._service_type = "compute" ++ ++ m = TestMiddleware(FakeApp()) ++ ++ self.assertRaises( ++ ksm_exceptions.InvalidToken, ++ m.validate_allowed_request, ++ mock_request, ++ token_data, ++ ) ++ ++ def test_app_cred_access_rules_valid_service_token(self): ++ """A valid service token should bypass access_rules.""" ++ access_rules = [ ++ {"path": "/v2.1/servers", "method": "GET", "service": "compute"} ++ ] ++ token_data = { ++ "is_admin": 0, ++ "user": {"id": "user1"}, ++ "methods": [], ++ "application_credential": { ++ "id": "app_cred_1", ++ "access_rules": access_rules, ++ }, ++ "catalog": [ ++ { ++ "type": "compute", ++ "name": "nova", ++ "endpoints": [ ++ { ++ "id": "ep", ++ "interface": "public", ++ "url": "http://nova:8774/v2.1", ++ } ++ ], ++ } ++ ], ++ } ++ mock_request = mock.MagicMock() ++ mock_request.method = "GET" ++ mock_request.path = "/v2.1/servers/someuuid" ++ mock_request.service_token = "valid-service-token" ++ mock_request.service_token_valid = True ++ ++ class TestMiddleware(auth_token.BaseAuthProtocol): ++ def __init__(self, app): ++ super(TestMiddleware, self).__init__(app) ++ self._service_type = "compute" ++ ++ m = TestMiddleware(FakeApp()) ++ ++ m.validate_allowed_request(mock_request, token_data) +Index: python-keystonemiddleware/releasenotes/notes/bug-2162748-service-token-presence-bypass-eedd42093bbee9b9.yaml +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-keystonemiddleware/releasenotes/notes/bug-2162748-service-token-presence-bypass-eedd42093bbee9b9.yaml 2026-10-30 16:11:16.202735812 +0100 +@@ -0,0 +1,14 @@ ++--- ++security: ++ - | ++ [`bug 2162748 `_] ++ ``auth_token`` middleware no longer treats the mere presence of an ++ ``X-Service-Token`` header as sufficient to skip application ++ credential ``access_rules`` checks or token binding ++ (``enforce_token_bind``, including Kerberos). Both paths now ++ require the service token to also be valid ++ (``service_token_valid``). Previously an arbitrary ++ ``X-Service-Token`` value could bypass ``access_rules`` and, when ++ ``delay_auth_decision`` was enabled, skip Kerberos (or other) ++ binding so a stolen bound user token would be marked Confirmed ++ without a Negotiate session. diff -Nru python-keystonemiddleware-10.9.0/debian/patches/series python-keystonemiddleware-10.9.0/debian/patches/series --- python-keystonemiddleware-10.9.0/debian/patches/series 2026-01-15 07:57:22.000000000 +0000 +++ python-keystonemiddleware-10.9.0/debian/patches/series 2026-10-30 15:11:18.000000000 +0000 @@ -1,2 +1,3 @@ no-intersphinx.patch CVE-2026-22797-OSSA-2026-001-Fix_privilege_escalation_via_spoofed_identity_headers.patch +CVE-2026-115642_OSSA-2026-050_Fix_service_token_presence_bypass_of_access_rules_and_token_bind.patch