]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/cephadm: add ability for cephadm to create self-signed cert for RGW
authorAdam King <adking@redhat.com>
Mon, 12 Aug 2024 14:08:32 +0000 (10:08 -0400)
committerAdam King <adking@redhat.com>
Tue, 13 Aug 2024 15:52:06 +0000 (11:52 -0400)
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 <adking@redhat.com>
src/pybind/mgr/cephadm/cert_mgr.py
src/pybind/mgr/cephadm/services/cephadmservice.py
src/pybind/mgr/cephadm/ssl_cert_utils.py
src/pybind/mgr/cephadm/tests/test_node_proxy.py
src/python-common/ceph/deployment/service_spec.py

index e1715424a95c2071fdd81e2efb40cc9aacef0878..9b68e85ca44e1f2fc627196ea396da1f1feda203 100644 (file)
@@ -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())
 
index d4b9ea262bb07b14e71edd795a4f4c84712ec55a..b0a98165d77f8fbd495d5d03fb772118bf1cada7 100644 (file)
@@ -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)
index 2a8d6fe4e3d79d129594555100d8cb2084b90fb9..930b276c8defadb4af7e25544bf4370412780be3 100644 (file)
@@ -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,
         )
index 9d3d2017d2f05832f16194de60e2b98a556ec8af..6f4ca6be1b565750bfd2cba74808ee5ee26ad1ed 100644 (file)
@@ -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:
index 7853df6b554ba6c2edda7f16385fa2e573b37949..d795986fd23bb48efff4f3de786709839d9f7020 100644 (file)
@@ -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)