From: Adam King Date: Mon, 12 Aug 2024 14:08:32 +0000 (-0400) Subject: mgr/cephadm: add ability for cephadm to create self-signed cert for RGW X-Git-Tag: v20.0.0~1035^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ce28c8ecefb636556b67062b51941ee0197a112f;p=ceph.git mgr/cephadm: add ability for cephadm to create self-signed cert for RGW If users are okay with a self-signed cert and would prefer to just let cephadm generate it for them rather than creating it themselves. Additionally, if the zonegroup_hostnames field is set, add those entries to the SANs entries for the cert in order to facilitate virtual host bucket access Signed-off-by: Adam King --- diff --git a/src/pybind/mgr/cephadm/cert_mgr.py b/src/pybind/mgr/cephadm/cert_mgr.py index e1715424a95..9b68e85ca44 100644 --- a/src/pybind/mgr/cephadm/cert_mgr.py +++ b/src/pybind/mgr/cephadm/cert_mgr.py @@ -21,7 +21,7 @@ class CertMgr: except SSLConfigException: raise Exception("Cannot load cephadm root CA certificates.") else: - self.ssl_certs.generate_root_cert(ip) + self.ssl_certs.generate_root_cert(addr=ip) mgr.cert_key_store.save_cert(self.CEPHADM_ROOT_CA_CERT, self.ssl_certs.get_root_cert()) mgr.cert_key_store.save_key(self.CEPHADM_ROOT_CA_KEY, self.ssl_certs.get_root_key()) diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index d4b9ea262bb..b0a98165d77 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -1000,6 +1000,12 @@ class RgwService(CephService): 'value': spec.rgw_zone, }) + if spec.generate_cert and not spec.rgw_frontend_ssl_certificate: + # generate a self-signed cert for the rgw service + cert, key = self.mgr.cert_mgr.ssl_certs.generate_root_cert(custom_san_list=spec.zonegroup_hostnames) + spec.rgw_frontend_ssl_certificate = ''.join([key, cert]) + self.mgr.spec_store.save(spec) + if spec.rgw_frontend_ssl_certificate: if isinstance(spec.rgw_frontend_ssl_certificate, list): cert_data = '\n'.join(spec.rgw_frontend_ssl_certificate) diff --git a/src/pybind/mgr/cephadm/ssl_cert_utils.py b/src/pybind/mgr/cephadm/ssl_cert_utils.py index 2a8d6fe4e3d..930b276c8de 100644 --- a/src/pybind/mgr/cephadm/ssl_cert_utils.py +++ b/src/pybind/mgr/cephadm/ssl_cert_utils.py @@ -1,5 +1,5 @@ -from typing import Any, Tuple, IO, List, Union +from typing import Any, Tuple, IO, List, Union, Optional import ipaddress from datetime import datetime, timedelta @@ -21,7 +21,11 @@ class SSLCerts: self.key_file: IO[bytes] self.cert_file: IO[bytes] - def generate_root_cert(self, addr: str) -> Tuple[str, str]: + def generate_root_cert( + self, + addr: Optional[str] = None, + custom_san_list: Optional[List[str]] = None + ) -> Tuple[str, str]: self.root_key = rsa.generate_private_key( public_exponent=65537, key_size=4096, backend=default_backend()) root_public_key = self.root_key.public_key() @@ -36,12 +40,19 @@ class SSLCerts: root_builder = root_builder.not_valid_after(datetime.now() + timedelta(days=(365 * 10 + 3))) root_builder = root_builder.serial_number(x509.random_serial_number()) root_builder = root_builder.public_key(root_public_key) + + san_list: List[x509.GeneralName] = [] + if addr: + san_list.extend([x509.IPAddress(ipaddress.ip_address(addr))]) + if custom_san_list: + san_list.extend([x509.DNSName(n) for n in custom_san_list]) root_builder = root_builder.add_extension( x509.SubjectAlternativeName( - [x509.IPAddress(ipaddress.ip_address(addr))] + san_list ), critical=False ) + root_builder = root_builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True, ) diff --git a/src/pybind/mgr/cephadm/tests/test_node_proxy.py b/src/pybind/mgr/cephadm/tests/test_node_proxy.py index 9d3d2017d2f..6f4ca6be1b5 100644 --- a/src/pybind/mgr/cephadm/tests/test_node_proxy.py +++ b/src/pybind/mgr/cephadm/tests/test_node_proxy.py @@ -37,7 +37,7 @@ class FakeMgr: self.http_server = MagicMock() self.http_server.agent = MagicMock() self.http_server.agent.ssl_certs = SSLCerts() - self.http_server.agent.ssl_certs.generate_root_cert(self.get_mgr_ip()) + self.http_server.agent.ssl_certs.generate_root_cert(addr=self.get_mgr_ip()) self.cert_mgr = FakeCertMgr() def get_mgr_ip(self) -> str: diff --git a/src/python-common/ceph/deployment/service_spec.py b/src/python-common/ceph/deployment/service_spec.py index 7853df6b554..d795986fd23 100644 --- a/src/python-common/ceph/deployment/service_spec.py +++ b/src/python-common/ceph/deployment/service_spec.py @@ -1206,7 +1206,7 @@ class RGWSpec(ServiceSpec): rgw_zonegroup: Optional[str] = None, rgw_zone: Optional[str] = None, rgw_frontend_port: Optional[int] = None, - rgw_frontend_ssl_certificate: Optional[List[str]] = None, + rgw_frontend_ssl_certificate: Optional[Union[str, List[str]]] = None, rgw_frontend_type: Optional[str] = None, rgw_frontend_extra_args: Optional[List[str]] = None, unmanaged: bool = False, @@ -1226,6 +1226,7 @@ class RGWSpec(ServiceSpec): rgw_user_counters_cache_size: Optional[int] = None, rgw_bucket_counters_cache: Optional[bool] = False, rgw_bucket_counters_cache_size: Optional[int] = None, + generate_cert: bool = False, ): assert service_type == 'rgw', service_type @@ -1255,7 +1256,8 @@ class RGWSpec(ServiceSpec): #: Port of the RGW daemons self.rgw_frontend_port: Optional[int] = rgw_frontend_port #: List of SSL certificates - self.rgw_frontend_ssl_certificate: Optional[List[str]] = rgw_frontend_ssl_certificate + self.rgw_frontend_ssl_certificate: Optional[Union[str, List[str]]] \ + = rgw_frontend_ssl_certificate #: civetweb or beast (default: beast). See :ref:`rgw_frontends` self.rgw_frontend_type: Optional[str] = rgw_frontend_type #: List of extra arguments for rgw_frontend in the form opt=value. See :ref:`rgw_frontends` @@ -1275,6 +1277,8 @@ class RGWSpec(ServiceSpec): self.rgw_bucket_counters_cache = rgw_bucket_counters_cache #: Used to set number of entries in each cache of bucket counters self.rgw_bucket_counters_cache_size = rgw_bucket_counters_cache_size + #: Whether we should generate a cert/key for the user if not provided + self.generate_cert = generate_cert def get_port_start(self) -> List[int]: return [self.get_port()] @@ -1303,6 +1307,10 @@ class RGWSpec(ServiceSpec): 'Additional rgw type parameters can be passed using rgw_frontend_extra_args.' ) + if self.generate_cert and not self.ssl: + raise SpecValidationError('"ssl" field must be set to true when "generate_cert" ' + 'is set to true') + yaml.add_representer(RGWSpec, ServiceSpec.yaml_representer)