By moving the self-signed cert generation into a separate module and consequently running it via `subprocess.run`--so, as a new, independent Python process--the PyO3 ImportError is successfully avoided.
Inspired by an upstream PR [0]. [0]: https://github.com/ceph/ceph/pull/62951 Signed-off-by: Max R. Carrara <m.carr...@proxmox.com> --- ...ul-provide-workaround-for-PyO3-Impor.patch | 152 ++++++++++++++++++ patches/series | 1 + 2 files changed, 153 insertions(+) create mode 100644 patches/0058-pybind-mgr-restful-provide-workaround-for-PyO3-Impor.patch diff --git a/patches/0058-pybind-mgr-restful-provide-workaround-for-PyO3-Impor.patch b/patches/0058-pybind-mgr-restful-provide-workaround-for-PyO3-Impor.patch new file mode 100644 index 0000000000..25fb720410 --- /dev/null +++ b/patches/0058-pybind-mgr-restful-provide-workaround-for-PyO3-Impor.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Max R. Carrara" <m.carr...@proxmox.com> +Date: Wed, 16 Jul 2025 13:14:39 +0200 +Subject: [PATCH 58/59] pybind/mgr/restful: provide workaround for PyO3 + ImportError + +Move the self-signed cert generation into a separate module +inside python-common/ceph and run the module in a separate Python +process. + +This provides a workaround for the ImportError thrown by PyO3 when +the `restful` module is loaded in the context of multiple Python +sub-interpreters being present. In particular, the ImportError is +thrown by the `crypto` module of the `OpenSSL` package. + +Inspired by an upstream PR [0]. + +[0]: https://github.com/ceph/ceph/pull/62951 + +Signed-off-by: Max R. Carrara <m.carr...@proxmox.com> +--- + src/pybind/mgr/restful/module.py | 24 +++------ + src/python-common/ceph/_crypto_wrap.py | 69 ++++++++++++++++++++++++++ + 2 files changed, 76 insertions(+), 17 deletions(-) + create mode 100644 src/python-common/ceph/_crypto_wrap.py + +diff --git a/src/pybind/mgr/restful/module.py b/src/pybind/mgr/restful/module.py +index 0f8c78e0bd8..7f93c41f1e6 100644 +--- a/src/pybind/mgr/restful/module.py ++++ b/src/pybind/mgr/restful/module.py +@@ -7,6 +7,7 @@ import json + import time + import errno + import inspect ++import subprocess + import tempfile + import threading + import traceback +@@ -19,7 +20,6 @@ from . import context + + from uuid import uuid4 + from pecan import jsonify, make_app +-from OpenSSL import crypto + from pecan.rest import RestController + from werkzeug.serving import make_server, make_ssl_devcert + +@@ -401,24 +401,14 @@ class Module(MgrModule): + + + def create_self_signed_cert(self): +- # create a key pair +- pkey = crypto.PKey() +- pkey.generate_key(crypto.TYPE_RSA, 2048) +- +- # create a self-signed cert +- cert = crypto.X509() +- cert.get_subject().O = "IT" +- cert.get_subject().CN = "ceph-restful" +- cert.set_serial_number(int(uuid4())) +- cert.gmtime_adj_notBefore(0) +- cert.gmtime_adj_notAfter(10*365*24*60*60) +- cert.set_issuer(cert.get_subject()) +- cert.set_pubkey(pkey) +- cert.sign(pkey, 'sha512') ++ cmd = ["python3", "-m", "ceph._crypto_wrap", "create_self_signed_cert"] ++ ++ response = subprocess.run(cmd, capture_output=True, check=True) ++ response_obj = json.loads(response.stdout) + + return ( +- crypto.dump_certificate(crypto.FILETYPE_PEM, cert), +- crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) ++ response_obj["cert"].encode("utf-8"), ++ response_obj["key"].encode("utf-8"), + ) + + +diff --git a/src/python-common/ceph/_crypto_wrap.py b/src/python-common/ceph/_crypto_wrap.py +new file mode 100644 +index 00000000000..16a19a5345e +--- /dev/null ++++ b/src/python-common/ceph/_crypto_wrap.py +@@ -0,0 +1,69 @@ ++"""CLI wrapper for cryptographic functions of the :mod:`restful` module. ++ ++To be called via :func:`subprocess.run()` as a workaround for ++:class:`ImportError`s related to PyO3's current lack of sub-interpreter ++support. ++ ++Note: ++ Since this module is installed as part of the ``ceph`` package, ++ it should be called like so:: ++ ++ python3 -m ceph._crypto_wrap create_self_signed_cert ++""" ++ ++import argparse ++import sys ++import json ++ ++from argparse import Namespace ++from typing import Any ++from uuid import uuid4 ++ ++from OpenSSL import crypto ++ ++ ++def _respond(data: dict[str, Any]) -> None: ++ json.dump(data, sys.stdout) ++ sys.stdout.flush() ++ ++ ++def create_self_signed_cert(args: Namespace) -> None: ++ cert_key_pair = _create_self_signed_cert() ++ _respond(cert_key_pair) ++ ++ ++def _create_self_signed_cert() -> dict[str, str]: ++ # create a key pair ++ pubkey = crypto.PKey() ++ pubkey.generate_key(crypto.TYPE_RSA, 2048) ++ ++ # create a self-signed cert ++ cert = crypto.X509() ++ cert.get_subject().O = "IT" ++ cert.get_subject().CN = "ceph-restful" ++ cert.set_serial_number(int(uuid4())) ++ cert.gmtime_adj_notBefore(0) ++ cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) ++ cert.set_issuer(cert.get_subject()) ++ cert.set_pubkey(pubkey) ++ cert.sign(pubkey, "sha512") ++ ++ return { ++ "cert": crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode(), ++ "key": crypto.dump_privatekey(crypto.FILETYPE_PEM, pubkey).decode(), ++ } ++ ++ ++def main() -> None: ++ parser = argparse.ArgumentParser(prog="_crypto_wrap.py") ++ subparsers = parser.add_subparsers(required=True) ++ ++ parser_cssc = subparsers.add_parser("create_self_signed_cert") ++ parser_cssc.set_defaults(func=create_self_signed_cert) ++ ++ args = parser.parse_args() ++ args.func(args) ++ ++ ++if __name__ == "__main__": ++ main() diff --git a/patches/series b/patches/series index 728a9f935e..ff23f8b640 100644 --- a/patches/series +++ b/patches/series @@ -51,3 +51,4 @@ 0055-python-common-cryptotools-catch-all-failures-to-read.patch 0056-mgr-cephadm-always-use-the-internal-cryptocaller.patch 0057-mgr-dashboard-add-an-option-to-control-the-dashboard.patch +0058-pybind-mgr-restful-provide-workaround-for-PyO3-Impor.patch -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel