Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:python-keystonemiddleware
User: [email protected]
Usertags: pu

Hi,

I have uploaded python-keystonemiddleware 10.9.0-2+deb13u1
to Trixie p-u to address below CVE.

[ Reason ]
CVE-2026-115642 / OSSA-2026-050: keystonemiddleware access_rules bypass
via X-Service-Token.

Basically, in more understandable words: this means that an app
credentials gets the full rights of the project it belongs, instead of
more restrictive rights. The patch fixes that.

[ Impact ]
As per above.

[ Tests ]
As always, new tests checking the patch fixes the issue is added,
and that's ran at package build time and autopkgtest. Also verified
there's no regression in my own functional CI.

[ Risks ]
The upstream fix just checks that a token is not a service token,
which is where the confusion happened. That's not a big change, and
it is comprehensive.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

Please approve python-keystonemiddleware 10.9.0-2+deb13u2 into
Trixie p-u.

Cheers,

Thomas Goirand (zigo)
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 
08:57:22.000000000 +0100
+++ python-keystonemiddleware-10.9.0/debian/changelog   2026-10-30 
16:11:18.000000000 +0100
@@ -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 <[email protected]>  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 01:00:00.000000000 +0100
+++ 
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 16:11:18.000000000 +0100
@@ -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 <[email protected]>
+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='[email protected]',
+                                   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 <https://launchpad.net/bugs/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 
08:57:22.000000000 +0100
+++ python-keystonemiddleware-10.9.0/debian/patches/series      2026-10-30 
16:11:18.000000000 +0100
@@ -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

Reply via email to