Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
ba29ce2
Update .gitreview for stable/2025.1
openstackadmin Mar 13, 2025
bb6dd30
Update TOX_CONSTRAINTS_FILE for stable/2025.1
openstackadmin Mar 13, 2025
79c9079
api: Don't restrict unknown querystring parameters yet
stephenfin Mar 25, 2025
a904f9b
api: Correct query string schema for access rules API
stephenfin Mar 25, 2025
379c2b0
api: Don't restrict unknown querystring parameters yet (redux)
stephenfin Mar 31, 2025
aab001f
Fix DB migrations after alembic integration
Sep 26, 2024
020370d
api: Add log when creating unscoped token
stephenfin May 29, 2025
a1ea3e0
trust schema: don't require user_id to be in uuid format
Jun 30, 2025
5a60aad
Ignore typing on the single import
gtema Aug 27, 2025
ba210d3
Fix AD nested groups issues
drencrom Jun 4, 2025
6b9b1b2
Merge "trust schema: don't require user_id to be in uuid format" into…
Sep 15, 2025
0bfe395
Merge "api: Add log when creating unscoped token" into stable/2025.1
Sep 15, 2025
28f2de7
Merge "Fix AD nested groups issues" into stable/2025.1
Nov 3, 2025
0d46250
Add service user authentication to ec2 and s3 endpoints
xek Sep 19, 2025
0e17f15
[keystone] initial throw at the ccloud train+victoria merge
Nov 14, 2019
3b72eab
[keystone] grab python 3.7 for unit testing
Nov 14, 2019
0abfe49
[keystone] added python 3 unit test required libraries
Nov 18, 2019
6a96d06
[keystone] reduce the strain on github.wdf.sap.corp
Nov 22, 2019
af09b5d
[keystone] delegate list all trusts permission check to policy rule, …
Nov 22, 2019
37c711a
Increase the limit in doctor check for our fernet
bbobrov Aug 8, 2023
2877cb0
[keystone] skip token_size test
Nov 22, 2019
d3a7e2e
[keystone] fixed random notification test failures
Nov 22, 2019
568df72
policy engine doesn't like rewrites of the identity:actions, so rever…
Nov 27, 2019
1bf9cba
[keystone] don't forward *NotFound exceptions to sentry
Dec 10, 2019
280d5a5
[keystone] skip keystone.tests.unit.test_config.ConfigTestCase.test_p…
Dec 10, 2019
43fb30f
[keystone] use proper regex format for test exclusions
Dec 10, 2019
5dc6914
ccloud: add possibility to set default tag(s)
Carthaca Feb 6, 2020
5442186
workaround https://github.com/pypa/virtualenv/issues/1551#issuecommen…
Carthaca Feb 11, 2020
15bee32
ccloud: policy modification ec2_create_credential
Carthaca Apr 20, 2020
f60e333
ccloud: consume extensions from public repo
Carthaca Nov 27, 2020
84ebd6d
Enable rate-limiting middleware
rajivmucheli Jul 14, 2021
8900e24
Switch tox to python3
bbobrov Mar 9, 2022
7b152bf
fix link in release note of bug/1794527
Carthaca May 7, 2020
8442340
Do not filter domain list via projects api with domain-scoped token
bbobrov Jun 3, 2022
9438de9
Add application credential id for audit notifications
bbobrov Mar 6, 2023
3163d4c
Drop python3 minor version from apt-get string in concourse runner
bbobrov Mar 8, 2023
0a4ecdf
Use the correct requirements file for concourse test runner
bbobrov Mar 8, 2023
a34a4e1
Cap tox to <4
bbobrov Mar 8, 2023
6219ce5
Switch dependencies to xena
bbobrov Aug 9, 2023
4626d53
Update remaining requirements to zed
bbobrov Aug 29, 2023
a806a42
concourse_unit_test_task: change the name of ldap dependency
bbobrov Sep 8, 2023
49a1254
concourse_unit_test_task: use py310 instead of py38
bbobrov Sep 8, 2023
cec933b
Pull keystone-extensions.git@stable/zed-m3 instead of just zed
fwiesel Oct 17, 2023
01acefe
sentry: ignore Unauthorized exceptions
bbobrov Nov 23, 2023
bdbe14b
Switch custom dependencies to 2023.2
bbobrov Nov 25, 2023
f7a5a4b
Rename stestr exclude option in the test runner
bbobrov Nov 25, 2023
d07225d
Try explicit declaration of crc<7.0.0 in concourse_unit_test_task
rajivmucheli Jul 29, 2024
a6a22bd
Test tox with sapcc/requirements branch
rajivmucheli Jul 29, 2024
cb72099
Gracefully disallow reauth with app-creds-bound token
bbobrov Aug 6, 2024
d8f56ed
Disable rate-limiting middleware
bbobrov Aug 7, 2024
434d2c8
Switch dependencies to 2024.1
bbobrov Nov 22, 2024
5a55373
Sentry: sanitize extra keys
bbobrov Dec 12, 2024
d59bdbf
Sentry: do not log INVALID_CREDENTIALS
bbobrov Dec 12, 2024
421ebda
Support emitting partial hash of invalid password
stanislav-zaprudskiy Oct 15, 2024
0c42e0c
Add python-binary-memcached package
s10 Jul 14, 2025
16af34e
Fix pep8 errors.
joker-at-work Jul 17, 2025
1d83436
Commit pep8 suggestions
stanislav-zaprudskiy Nov 26, 2025
5fa1eb9
Switch dependencies to 2025.1
stanislav-zaprudskiy Nov 26, 2025
d9ed9e5
concourse_unit_test_task: use `py312`
stanislav-zaprudskiy Nov 26, 2025
7f2f18c
Rework `concourse_unit_test_task` (#41)
stanislav-zaprudskiy Nov 27, 2025
e306b1e
Merge pull request #38 from sapcc/2025.1-cherry-picks
stanislav-zaprudskiy Nov 28, 2025
bbb3318
Migrates deprecated password hashes.
adrianjarvis Sep 1, 2025
766c1a6
Bring back `passlib.hash.sha512_crypt`
stanislav-zaprudskiy Dec 4, 2025
81abd56
Merge pull request #42 from sapcc/revert_sha512_crypt_removal
stanislav-zaprudskiy Dec 5, 2025
54e3e07
Disable response body validation by default
stephenfin Oct 2, 2025
1f9f665
Import LOG where it is used
gtema Dec 5, 2025
ad13ce9
Merge pull request #43 from sapcc/cherry-pick-bug/2126676
stanislav-zaprudskiy Dec 5, 2025
0fe0471
Fix role assignment cache for federated users
tz3 Nov 25, 2025
63add32
Merge pull request #44 from sapcc/cherry-pick-bug/2119031
tz3 Jan 5, 2026
1c7e96b
Fix LDAP pagination to return all users beyond page_size
tz3 Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitreview
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
host=review.opendev.org
port=29418
project=openstack/keystone.git
defaultbranch=stable/2025.1
6 changes: 6 additions & 0 deletions bindep.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ openldap2-devel [platform:suse]

# Required for sphinx graphviz image generation
graphviz [test doc]

# otherwise, it fails to build psycopg2, python-ldap
build-essential [test]

# otherwise, it fails to recognize pbr version
git [test]
27 changes: 27 additions & 0 deletions concourse_unit_test_task
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

set -ex

export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y \
python3-venv
python3 -m venv $HOME/.local/tools
source $HOME/.local/tools/bin/activate
pip install -U \
pip \
bindep \
tox

cd source
apt-get install -y \
$(bindep -b test)
git config --global --add safe.directory /source

export UPPER_CONSTRAINTS_FILE=https://raw.githubusercontent.com/sapcc/requirements/stable/2025.1-m3/upper-constraints.txt
export \
KEYSTONE_IDENTITY_BACKEND=ldap \
KEYSTONE_CLEAR_LDAP=yes \
ENABLE_LDAP_LIVE_TEST=true
tox -e py312 \
--skip-missing-interpreters=false
9 changes: 9 additions & 0 deletions custom-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
python-binary-memcached

git+https://github.com/sapcc/raven-python.git@ccloud#egg=raven[flask]
git+https://github.com/sapcc/openstack-watcher-middleware.git#egg=watcher-middleware
git+https://github.com/funkyHat/py-radius@install_requires#egg=py-radius
git+https://github.com/sapcc/keystone-extensions.git@stable/2025.1-m3#egg=keystone-extensions
git+https://github.com/sapcc/openstack-rate-limit-middleware.git#egg=rate-limit-middleware

passlib
2 changes: 2 additions & 0 deletions doc/source/getting-started/policy_mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ identity:delete_application_credential DELETE /v3/users/{use
identity:get_access_rule GET /v3/users/{user_id}/access_rules/{access_rule_id}
identity:list_access_rules GET /v3/users/{user_id}/access_rules
identity:delete_access_rule DELETE /v3/users/{user_id}/access_rules/{access_rule_id}
identity:s3tokens_validate POST /v3/s3tokens
identity:ec2tokens_validate POST /v3/es2tokens

========================================================= ===

Expand Down
25 changes: 23 additions & 2 deletions keystone/api/_shared/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ def _check_and_set_default_scoping(auth_info, auth_context):

default_project_id = user_ref.get('default_project_id')
if not default_project_id:
# User has no default project. He shall get an unscoped token.
# User has no default project. They shall get an unscoped token.
msg = (
"User %(user_id)s doesn't have a default project. "
" The token will be unscoped rather than scoped to a project."
)
LOG.debug(msg, {'user_id': user_ref['id']})
return

# make sure user's default project is legit before scoping to it
Expand Down Expand Up @@ -226,7 +231,23 @@ def authenticate_for_token(auth=None):
app_cred_id = None
if 'application_credential' in method_names:
token_auth = auth_info.auth['identity']
app_cred_id = token_auth['application_credential']['id']
try:
app_cred_id = token_auth['application_credential']['id']
except KeyError:
# NOTE(bbobrov): see bug #1878438 on launchpad.
# There is no any good fix for it, at least with the current
# architecture. Lets just return a nice message that this
# is currently not supported.
LOG.warning(
'Unsupported reauthentication attempt with a token '
'after authenticating with application credentials'
)
raise exception.Unauthorized(
_(
'Cannot reauthenticate with a token issued with '
'application credentials'
)
)

# Do MFA Rule Validation for the user
if not core.UserMFARulesValidator.check_auth_methods_against_rules(
Expand Down
8 changes: 7 additions & 1 deletion keystone/api/ec2tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from keystone.api._shared import EC2_S3_Resource
from keystone.api._shared import json_home_relations
from keystone.common import rbac_enforcer
from keystone.common import render_token
from keystone.common import utils
from keystone import exception
Expand All @@ -30,6 +31,9 @@
CRED_TYPE_EC2 = 'ec2'


ENFORCER = rbac_enforcer.RBACEnforcer


class EC2TokensResource(EC2_S3_Resource.ResourceBase):
@staticmethod
def _check_signature(creds_ref, credentials):
Expand Down Expand Up @@ -57,12 +61,14 @@ def _check_signature(creds_ref, credentials):
else:
raise exception.Unauthorized(_('EC2 signature not supplied.'))

@ks_flask.unenforced_api
def post(self):
"""Authenticate ec2 token.

POST /v3/ec2tokens
"""
# Enforce RBAC in the same way as S3 tokens
ENFORCER.enforce_call(action='identity:ec2tokens_validate')

token = self.handle_authenticate()
token_reference = render_token.render_token_response_from_model(token)
resp_body = jsonutils.dumps(token_reference)
Expand Down
13 changes: 12 additions & 1 deletion keystone/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from keystone.common import json_home
from keystone.common import provider_api
from keystone.common import rbac_enforcer
from keystone.common import utils
from keystone.common import validation as ks_validation
import keystone.conf
from keystone import exception
Expand Down Expand Up @@ -189,7 +190,17 @@ def get(self):
if t in flask.request.args:
hints.add_filter(t, flask.request.args[t])
refs = PROVIDERS.resource_api.list_projects(hints=hints)
if self.oslo_context.domain_id:

# the entries should not be filtered by domain_id if domain list was
# requested; otherwise all domains are getting filtered out
try:
is_domain_requested = utils.attr_as_boolean(
flask.request.args['is_domain']
)
except KeyError:
is_domain_requested = False

if self.oslo_context.domain_id and not is_domain_requested:
domain_id = self.oslo_context.domain_id
filtered_refs = [
ref for ref in refs if ref['domain_id'] == domain_id
Expand Down
7 changes: 6 additions & 1 deletion keystone/api/s3tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@

from keystone.api._shared import EC2_S3_Resource
from keystone.api._shared import json_home_relations
from keystone.common import rbac_enforcer
from keystone.common import render_token
from keystone.common import utils
from keystone import exception
from keystone.i18n import _
from keystone.server import flask as ks_flask

ENFORCER = rbac_enforcer.RBACEnforcer


def _calculate_signature_v1(string_to_sign, secret_key):
"""Calculate a v1 signature.
Expand Down Expand Up @@ -96,12 +99,14 @@ def _check_signature(creds_ref, credentials):
message=_('Credential signature mismatch')
)

@ks_flask.unenforced_api
def post(self):
"""Authenticate s3token.

POST /v3/s3tokens
"""
# Use standard Keystone policy enforcement for s3tokens access
ENFORCER.enforce_call(action='identity:s3tokens_validate')

token = self.handle_authenticate()
token_reference = render_token.render_token_response_from_model(token)
resp_body = jsonutils.dumps(token_reference)
Expand Down
4 changes: 3 additions & 1 deletion keystone/api/trusts.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ def get(self):
)
if not flask.request.args:
# NOTE(morgan): Admin can list all trusts.
ENFORCER.enforce_call(action='admin_required')
# ENFORCER.enforce_call(action='admin_required')
# ccloud: only allow cloud-admins to list all trusts
ENFORCER.enforce_call(action='cloud_admin')

if not flask.request.args:
trusts += PROVIDERS.trust_api.list_trusts()
Expand Down
21 changes: 15 additions & 6 deletions keystone/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,14 @@ def post(self, user_id):

POST /v3/users/{user_id}/credentials/OS-EC2
"""
tenant_id = self.request_body_json.get('tenant_id')

target = {}
target['credential'] = {'user_id': user_id}
target['credential'] = {'user_id': user_id, 'project_id': tenant_id}
ENFORCER.enforce_call(
action='identity:ec2_create_credential', target_attr=target
)
PROVIDERS.identity_api.get_user(user_id)
tenant_id = self.request_body_json.get('tenant_id')
PROVIDERS.resource_api.get_project(tenant_id)
blob = {
'access': uuid.uuid4().hex,
Expand Down Expand Up @@ -806,8 +807,12 @@ class UserAccessRuleListResource(ks_flask.ResourceBase):
collection_key = 'access_rules'
member_key = 'access_rule'

@validation.request_query_schema(app_cred_schema.index_request_query)
@validation.response_body_schema(app_cred_schema.rule_index_response_body)
@validation.request_query_schema(
app_cred_schema.access_rule_index_request_query
)
@validation.response_body_schema(
app_cred_schema.access_rule_index_response_body
)
def get(self, user_id):
"""List access rules for user.

Expand All @@ -830,8 +835,12 @@ class UserAccessRuleGetDeleteResource(ks_flask.ResourceBase):
collection_key = 'access_rules'
member_key = 'access_rule'

@validation.request_query_schema(app_cred_schema.rule_show_request_query)
@validation.response_body_schema(app_cred_schema.rule_show_response_body)
@validation.request_query_schema(
app_cred_schema.access_rule_show_request_query
)
@validation.response_body_schema(
app_cred_schema.access_rule_show_response_body
)
def get(self, user_id, access_rule_id):
"""Get access rule resource.

Expand Down
56 changes: 38 additions & 18 deletions keystone/api/validation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
import typing as ty

import flask
from oslo_log import log
from oslo_serialization import jsonutils

from keystone.api.validation import validators
import keystone.conf
from keystone import exception

CONF = keystone.conf.CONF
LOG = log.getLogger(__name__)


def validated(cls):
Expand Down Expand Up @@ -138,26 +144,40 @@ def add_validator(func):
def wrapper(*args, **kwargs):
response = func(*args, **kwargs)

if schema is not None:
# In Flask it is not uncommon that the method return a tuple of
# body and the status code. In the runtime Keystone only return
# a body, but some of the used testtools do return a tuple.
if isinstance(response, tuple):
_body = response[0]
else:
_body = response

# NOTE(stephenfin): If our response is an object, we need to
# serializer and deserialize to convert e.g. date-time
# to strings
_body = jsonutils.dump_as_bytes(_body)

if _body == b"":
body = None
if CONF.api.response_validation == 'ignore':
# don't waste our time checking anything if we're ignoring
# schema errors
return response

if schema is None:
return response

# In Flask it is not uncommon that the method return a tuple of
# body and the status code. In the runtime Keystone only return
# a body, but some of the used testtools do return a tuple.
if isinstance(response, tuple):
_body = response[0]
else:
_body = response

# NOTE(stephenfin): If our response is an object, we need to
# serializer and deserialize to convert e.g. date-time
# to strings
_body = jsonutils.dump_as_bytes(_body)

if _body == b'':
body = None
else:
body = jsonutils.loads(_body)

try:
_schema_validator(schema, body, args, kwargs, is_body=True)
except exception.SchemaValidationError:
if CONF.api.response_validation == 'warn':
LOG.exception('Schema failed to validate')
else:
body = jsonutils.loads(_body)
raise

_schema_validator(schema, body, args, kwargs, is_body=True)
return response

wrapper._response_body_schema = schema
Expand Down
Loading