From 9f2f08f4b664848836a2b3c223fe0161136952f7 Mon Sep 17 00:00:00 2001 From: Ernesto Puerta Date: Fri, 7 Jun 2024 11:41:26 +0200 Subject: [PATCH] mgr: fix pyO3 import issues The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see https://github.com/PyO3/pyo3/pull/4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta --- .../mgr/dashboard/services/access_control.py | 46 ++++++++----------- src/pybind/mgr/mgr_util.py | 5 +- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/pybind/mgr/dashboard/services/access_control.py b/src/pybind/mgr/dashboard/services/access_control.py index b45f81fb9b1dd..ea1ec1f2f0ab2 100644 --- a/src/pybind/mgr/dashboard/services/access_control.py +++ b/src/pybind/mgr/dashboard/services/access_control.py @@ -2,9 +2,13 @@ # pylint: disable=too-many-arguments,too-many-return-statements # pylint: disable=too-many-branches, too-many-locals, too-many-statements +import base64 import errno +import hashlib +import hmac import json import logging +import os import re import threading import time @@ -12,9 +16,7 @@ from string import ascii_lowercase, ascii_uppercase, digits, punctuation from typing import List, Optional, Sequence -import bcrypt from mgr_module import CLICheckNonemptyFileInput, CLIReadCommand, CLIWriteCommand -from mgr_util import password_hash from .. import mgr from ..exceptions import PasswordPolicyException, PermissionNotValid, \ @@ -26,7 +28,7 @@ logger = logging.getLogger('access_control') DEFAULT_FILE_DESC = 'password/secret' - +SCRYPT_SALT_LEN = 29 _P = Permission # short alias @@ -327,8 +329,17 @@ def enabled(self, value): self._enabled = value self.refresh_last_update() + @staticmethod + def calculate_password_hash(password: str, input_salt: Optional[str] = None) -> str: + if input_salt is None: + salt = os.urandom(SCRYPT_SALT_LEN) + else: + salt = base64.b64decode(salt)[:SCRYPT_SALT_LEN] + hash = hashlib.scrypt(password.encode('utf8'), salt=salt, n=2**14, r=8, p=1) + return base64.b64encode(salt + hash).decode('utf8') + def set_password(self, password): - self.set_password_hash(password_hash(password)) + self.set_password_hash(self.calculate_password_hash(password)) def set_password_hash(self, hashed_password): self.invalid_auth_attempt = 0 @@ -345,8 +356,8 @@ def compare_password(self, password): :return: `True` if the passwords are equal, otherwise `False`. :rtype: bool """ - pass_hash = password_hash(password, salt_password=self.password) - return pass_hash == self.password + pass_hash = self.calculate_password_hash(password, salt_password=self.password) + return hmac.compare_digest(pass_hash, self.password) def is_pwd_expired(self): if self.pwd_expiration_date: @@ -475,7 +486,7 @@ def create_user(self, username, password, name, email, enabled=True, if pwd_expiration_date and \ (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))): raise PwdExpirationDateNotValid() - user = User(username, password_hash(password), name, email, enabled=enabled, + user = User(username, User.calculate_password_hash(password), name, email, enabled=enabled, pwd_expiration_date=pwd_expiration_date, pwd_update_required=pwd_update_required) self.users[username] = user @@ -880,27 +891,6 @@ def ac_user_set_password(_, username: str, inbuf: str, return -errno.ENOENT, '', str(ex) -@CLIWriteCommand('dashboard ac-user-set-password-hash') -@CLICheckNonemptyFileInput(desc=DEFAULT_FILE_DESC) -def ac_user_set_password_hash(_, username: str, inbuf: str): - ''' - Set user password bcrypt hash from -i - ''' - hashed_password = inbuf - try: - # make sure the hashed_password is actually a bcrypt hash - bcrypt.checkpw(b'', hashed_password.encode('utf-8')) - user = mgr.ACCESS_CTRL_DB.get_user(username) - user.set_password_hash(hashed_password) - - mgr.ACCESS_CTRL_DB.save() - return 0, json.dumps(user.to_dict()), '' - except ValueError: - return -errno.EINVAL, '', 'Invalid password hash' - except UserDoesNotExist as ex: - return -errno.ENOENT, '', str(ex) - - @CLIWriteCommand('dashboard ac-user-set-info') def ac_user_set_info(_, username: str, name: str, email: str): ''' diff --git a/src/pybind/mgr/mgr_util.py b/src/pybind/mgr/mgr_util.py index 05ec6496682f4..45779de38df6b 100644 --- a/src/pybind/mgr/mgr_util.py +++ b/src/pybind/mgr/mgr_util.py @@ -3,7 +3,6 @@ if 'UNITTEST' in os.environ: import tests -import bcrypt import cephfs import contextlib import datetime @@ -516,7 +515,7 @@ def create_self_signed_cert(organisation: str = 'Ceph', :param organisation: String representing the Organisation(O) RDN (default='Ceph') :param common_name: String representing the Common Name(CN) RDN (default='mgr') - :param dname: Optional dictionary containing RDNs to use for crt/key generation + :param dname: Optional dictionary containing RDNs to use for crt/key generation :return: ssl crt and key in utf-8 format @@ -870,6 +869,8 @@ def wrapper(*args: Any, **kwargs: Any) -> T: def password_hash(password: Optional[str], salt_password: Optional[str] = None) -> Optional[str]: + import bcrypt + if not password: return None if not salt_password: