Skip to content

Fix Keystone CVE-2026-{42999,42998,43000,43001,44394} (Epoxy)#25

Merged
priteau merged 18 commits into
stackhpc/2025.1from
epoxy-cve-2026-42998-43001
May 29, 2026
Merged

Fix Keystone CVE-2026-{42999,42998,43000,43001,44394} (Epoxy)#25
priteau merged 18 commits into
stackhpc/2025.1from
epoxy-cve-2026-42998-43001

Conversation

@seunghun1ee
Copy link
Copy Markdown
Member

Last 5 commits are related to the 5 CVEs in the title.
Rest of them are from upstream Keystone stable/2025.1 branch. We've been not syncing upstream some time.

gtema and others added 13 commits January 5, 2026 21:48
Trusts support custom setting and returning custom properties. Change
jsonschema to allow additional properties.

Closes-Bug: 2119543
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Change-Id: I49a8d1d669c6f942bc798cac093f75350692220e
(cherry picked from commit 69e5fc5)
(cherry picked from commit 1bdb16c)
flake8-import-order depends on pkg_resources, which was removed from
setuptools in version 82.0.0. Pin setuptools<82 in the hacking hook's
additional_dependencies to ensure pkg_resources remains available.

Generated-By: Oz <oz-agent@warp.dev>
Change-Id: I1ad4410d2fda8e1efc526dd8327d8218b87478c2
Signed-off-by: Dave Wilde <dwilde@redhat.com>
interpretation of the ldap enabled attribute as boolean
is only done if enabled_invert setting is set to true.

Depends-on: https://review.opendev.org/c/openstack/keystone/+/982656
Closes-Bug: #2121152
Change-Id: I7260bf46adf003aef7c7ac0d436c3758f658cb0c
Signed-off-by: Benedikt Trefzer <benedikt.trefzer@cirrax.com>
(cherry picked from commit 98e3e6b)
Per PEP 3333, mod_wsgi decodes WSGI environ values as ISO-8859-1
(Latin-1), per Section 3.2.4 of RFC 7230. When OIDC IdPs send
assertion values containing non-ASCII characters encoded as UTF-8
(e.g. Spanish 'ñ' in group names like 'España'), the raw UTF-8
bytes are misinterpreted as Latin-1, producing mojibake. This
causes dynamic group mapping to fail because the corrupted group
name does not match the local group in the database.

Fix get_assertion_params_from_env() to reverse the Latin-1 decode
by encoding back to ISO-8859-1 and re-decoding as UTF-8. If the
roundtrip fails, the value is kept as-is, preserving backward
compatibility with legitimately Latin-1 encoded data.

Change-Id: I970b3373017478f3359ac0eef0a8cda27a89918d
Closes-Bug: #2146771
Assisted-by: Claude Opus (Anthropic)
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
(cherry picked from commit 1678987)
A restricted application credential could be used to create EC2
credentials granting full user access to S3, bypassing the role
restriction. Add the same _check_unrestricted_application_credential
guard that already protects application credential create/delete
endpoints.

Additionally, tighten the ec2_create_credential and ec2_delete_credential
policies to require at least member role, as these are write operations
that should not be accessible to reader-role users regardless of whether
they are using an application credential.

Change-Id: Ib6904ec9f1bc069a9f607d39814b1d2633c17f53
Closes-Bug: #2142138
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
Verify that the _check_unrestricted_application_credential guard on
the OS-EC2 credential create endpoint blocks restricted application
credentials from creating EC2 credentials, while still allowing
unrestricted application credentials to do so.

Generated-By: claude-opus-4-6 (OpenCode)
Related-Bug: #2142138
Change-Id: I733305ba61bd8362f0c9675e257b1d42a6ef4053
Signed-off-by: Boris Bobrov <b.bobrov@sap.com>
(cherry picked from commit c87ce6e)
…ials

The POST /v3/credentials endpoint accepted EC2 credential creation
from restricted application credential tokens, bypassing the guard
on the dedicated OS-EC2 endpoint. Add the same unrestricted
application credential check to the generic credentials API for
EC2-type credentials, and update the existing test to use an
unrestricted application credential.

Related-Bug: #2142138
Generated-By: claude-opus-4-6 (OpenCode)
Signed-off-by: Boris Bobrov <b.bobrov@sap.com>
Change-Id: Idb192a2fd370fc26c7d76788e9ad1856483d3239
(cherry picked from commit d6a3dc5)
The OAuth1 authorize endpoint checked is_delegated_auth to block
trust-scoped and OAuth-scoped tokens from authorizing request
tokens, but application credential tokens were not covered by
this check. A restricted application credential could authorize
a request token with any role the user actually holds, producing
an access token that yields an unrestricted Keystone token with
roles beyond the application credential's restricted set.

Add an explicit check for application credential tokens on the
OAuth1 authorize endpoint, consistent with how trust-scoped and
OAuth-scoped tokens are already blocked.

Related-Bug: #2142138
Generated-By: claude-opus-4-6 (OpenCode)
Signed-off-by: Boris Bobrov <b.bobrov@sap.com>
Change-Id: I9506557609ff7edaa6a961f356f9b8e19faaefc3
(cherry picked from commit 29246c5)
POST /v3/credentials did not validate that the caller-supplied
project_id for an EC2-type credential matched the project of the
authenticating application credential. This allowed an attacker
holding an unrestricted application credential for project A to
create an EC2 credential targeting project B; a subsequent
/v3/ec2tokens exchange would then issue a Keystone token scoped to
project B while still carrying the original app_cred_id, enabling
cross-project lateral movement within the credential owner's role
footprint.

Two fixes:

1. credentials.py: after extracting app_cred_id from the token,
   check that credential['project_id'] == app_cred['project_id']
   for EC2-type credentials and raise ForbiddenAction otherwise.

2. EC2_S3_Resource.py: in handle_authenticate(), assert that the
   stored EC2 credential project_id matches the application
   credential's project before issuing the token.

This issue is orthogonal to CVE-2026-33551 (LP#2142138 / Gerrit
983655), which blocks restricted application credentials from
creating EC2 credentials at all. The project-boundary check is
absent regardless of the restricted flag and requires separate
treatment.

Closes-Bug: #2149775
Related-Bug: #OSPRH-29345
Assisted-by: claude-sonnet-4-6 <noreply@anthropic.com>
Change-Id: I7c10c8a52e57e63cb9c66d03d69540abefe5425c
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
(cherry picked from commit b6fd809)
(cherry picked from commit d9e18a3)
The stable/2025.1 gate is completely broken for all Keystone changes.
The grenade, grenade-skip-level, k2k federation, and other devstack-
based jobs fail during configure_tempest because tox creates a 'venv'
environment using master's upper-constraints.txt, which pins
Sphinx===9.0.4. This conflicts with tempest's doc/requirements.txt
(sphinx>=2.0.0,!=2.1.0), producing a ResolutionImpossible error from
pip. No Keystone code executes before the failure.

Fix this by setting TEMPEST_VENV_UPPER_CONSTRAINTS to the branch-
specific constraints file from the locally cloned requirements repo.
DevStack already supports this: when the variable is set to a file
path (not "master"), set_tempest_venv_constraints in lib/tempest
reads the file and exports UPPER_CONSTRAINTS_FILE pointing to it.
The cloned requirements repo on each branch has a compatible Sphinx
pin, eliminating the resolution conflict.

For grenade jobs, the variable is set via grenade_devstack_localrc
(shared) so it applies to both old-side and new-side devstack. On
the old side (stable/2024.2), $DEST expands to /opt/stack/old and
the requirements repo is cloned from stable/2024.2. On the new side,
$DEST is /opt/stack/new with stable/2025.1 constraints. Both have
Sphinx pins compatible with their respective tempest versions.

This is a standalone CI configuration change, separate from change
988237 (the EC2 credential policy fix that was blocked by this gate
failure).

Assisted-by: Claude Code (Opus 4.6)
Change-Id: I238870bb859928b7152f62dec54c51c0c1d2819b
Signed-off-by: Dave Wilde <dwilde@redhat.com>
@seunghun1ee seunghun1ee requested a review from a team as a code owner May 29, 2026 11:27
xek and others added 5 commits May 29, 2026 12:39
Delegated tokens (trusts, application credentials, OAuth1 access tokens)
are scoped to a single project at delegation time. This must be enforced
thoroughly while granting the API access to Keystone resources that
might be also bound to a single project. Without this it is possible to
gain different access (using trust to see application credentials for a
different project, reuse the MFA seed, etc).

* Credentials CRUD (/v3/credentials)

  All five CRUD operations verified ownership via user_id but did not bind
  credential.project_id to the delegating token's project scope.

  Fix: _check_credential_project_scope() - no-op for non-delegated tokens,
  raises ForbiddenAction on project mismatch. For list, out-of-scope
  credentials are silently filtered.

  Credentials with project_id=None (TOTP/MFA bindings) are treated as
  out-of-scope for any delegated token: they are user-level secrets with no
  project anchor, and a delegated token should never be able to enumerate,
  read, or mutate them - doing so would allow a stolen delegation token to
  exfiltrate or destroy a user's MFA binding.

* OS-EC2 credential CRUD (/v3/users/{id}/credentials/OS-EC2)

  POST accepted any tenant_id from a delegated token. GET and DELETE had
  no delegation check at all.

  Fix: _check_delegation_for_ec2() enforces the project boundary;
  list silently filters.

  Additionally, pre-existing OAuth1 access-token-backed EC2 credentials
  with a mismatched project_id could be used at auth-time (POST /v3/ec2tokens)
  to obtain a cross-project token. Added a check in EC2_S3_Resource.py that
  cred_data['project_id'] matches access_token['project_id'] before issuing
  the token. The trust branch does not need this check - the token provider
  uses the trust's project regardless of the credential's project_id.

* OS-OAUTH1 access token management (/v3/users/{id}/OS-OAUTH1/access_tokens)

  GET and DELETE had no delegation check. List blocked trust/OAuth but not
  app-cred tokens.

  Fix: _block_delegated_token() raises Forbidden for any delegation type
  on list, get, and delete.

* Application credential management (/v3/users/{id}/application_credentials)

  Trust-scoped and OAuth1 tokens had no guard on the application credential
  and access rule management APIs. An impersonating trust could LIST, CREATE,
  or DELETE application credentials, creating a persistent backdoor that
  outlives the trust's own expiry. App credential tokens are intentionally
  excluded - the unrestricted/restricted distinction is handled separately by
  _check_unrestricted_application_credential.

  Fix: _block_delegated_token_app_creds() raises Forbidden for trust-scoped
  and OAuth1 tokens on all six app credential and access rule endpoints.

Closes-Bug: #2150089
Related-Bug: #2149789
Related-Bug: #2149775
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Change-Id: Iaaa0ec713a0a5e062acc3209d6010982899d8f6f
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
(cherry picked from commit 16582e5192be354e26ebef4badca1213ddc4dc07)
When authenticating by application credential ID, the caller can supply a
'user' field in the payload. AppCredInfo conditionally set the user from
the credential owner only when no user field was present. If present,
BaseUserInfo resolved the caller-supplied user and attributed the resulting
token to that user instead of the credential owner.

Fix: always set auth_payload['user'] from the credential's stored user_id,
ignoring any caller-supplied value.

Closes-Bug: #2148477
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Boris Bobrov <b.bobrov@sap.com>
Change-Id: I2fe6089886eebf3775930451b87771e40b5e179e
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
(cherry picked from commit 6cd25fecdab8b9261e916ee10f3dba5aeb0c1984)
Previously only restricted application credentials were blocked, and only
for trust create and delete. This change blocks all application credentials
(restricted and unrestricted alike) from all trust operations: list, get,
create, delete, list-roles, and get-role. The 'unrestricted' flag governs
credential management, not trust management.

Closes-Bug: #2148477
Related-Bug: #2149789
Related-Bug: #2150089
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Boris Bobrov <b.bobrov@sap.com>
Change-Id: I750156df18a1d6293ce99c42eb524575fcf16ea3
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
(cherry picked from commit 3c2043ab003cb4b8aa34502fe9a5a69b0a6a6e54)
When a federated token is rescoped via POST /v3/auth/tokens the
handle_scoped_token function returned response_data without an expires_at
value. Because issue_token falls back to default_expire_time when
expires_at is None, each rescope issued a fresh full-TTL token instead of
inheriting the remaining lifetime of the original token.

A user with a federated token could extend their session indefinitely by
rescoping repeatedly before expiry, bypassing operator-configured TTL
policies and IdP-level account revocation.

Fix: propagate token.expires_at from handle_scoped_token so that
issue_token uses the original token's expiry rather than resetting to
the default. The non-federated path in token.py already did this via
response_data.setdefault('expires_at', token.expires_at).

Closes-Bug: #2150379
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Artem Goncharov <artem.goncharov@gmail.com>
Change-Id: I0bbb8520e12c52edd01fb47c873f0227819706f5
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
(cherry picked from commit 75a4a0c354c7f568b28dd85182dc729553fb3a33)
…42999)

The RBAC enforcer unconditionally merged the raw JSON request body into
the policy enforcement dictionary after trusted target data had been set
from the database. An attacker could include a "target" key in the JSON
body to overwrite database-sourced RBAC target attributes, causing all
%(target.*)s policy substitutions to evaluate against attacker-controlled
values. This affected 88 endpoint/method combinations across all Keystone
API resources.

The fix namespaces user-controlled JSON body data under a "request_body"
key in the policy dict, making it structurally impossible for request body
fields to collide with internal keys like "target" or view_args.

The only in-tree policy rule that depended on the old JSON body merge
behavior was identity:create_trust, which referenced
%(trust.trustor_user_id)s from the request body at the top level of the
policy dict. This is updated to use target_attr and the
%(target.trust.trustor_user_id)s substitution, consistent with all other
trust policy rules.

Additionally, query-string filter values had the same structural issue:
_extract_filter_values() results were merged at the top level, meaning a
filter key matching a view_arg key (e.g. user_id on
/v3/users/{user_id}/... endpoints using ADMIN_OR_SYSTEM_READER_OR_OWNER)
could be overwritten by an attacker-controlled ?user_id= query param,
bypassing ownership checks. Filter values are now namespaced under
"filter_attr". No in-tree policy rule references filter values via %(key)s
substitutions, so this is backwards-compatible for upstream deployments.

Closes-Bug: #2148398
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Boris Bobrov <b.bobrov@sap.com>
Co-authored-by: Artem Goncharov <artem.goncharov@gmail.com>
Change-Id: I295d1ac27faad05a680bb2b3fac8cfa27fa1c4bd
Signed-off-by: Grzegorz Grasza <xek@redhat.com>
(cherry picked from commit 22b51f5d5d86350d3fbc66697e4097bacf2a8ce9)
@seunghun1ee seunghun1ee force-pushed the epoxy-cve-2026-42998-43001 branch from 876636e to ef5dc5d Compare May 29, 2026 11:40
Copy link
Copy Markdown
Member

@priteau priteau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran unit tests locally, they are passing fine:

======
Totals
======
Ran: 5762 tests in 96.6488 sec.
 - Passed: 5045
 - Skipped: 715
 - Expected Fail: 2
 - Unexpected Success: 0
 - Failed: 0
Sum of execute time for each test: 1419.2618 sec.

@priteau priteau merged commit 92c3e99 into stackhpc/2025.1 May 29, 2026
1 of 4 checks passed
@priteau priteau deleted the epoxy-cve-2026-42998-43001 branch May 29, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants