]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: adding tls fields as deps for services with TLS support
authorRedouane Kachach <rkachach@ibm.com>
Wed, 11 Feb 2026 11:17:55 +0000 (12:17 +0100)
committerRedouane Kachach <rkachach@ibm.com>
Thu, 14 May 2026 09:08:12 +0000 (11:08 +0200)
This is especially important for inline certificates, so the certmgr
store is updated automatically whenever the user changes the values in
the spec and reapplies it.

Fixes: https://tracker.ceph.com/issues/75009
Signed-off-by: Redouane Kachach <rkachach@ibm.com>
src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/services/cephadmservice.py
src/pybind/mgr/cephadm/services/ingress.py
src/pybind/mgr/cephadm/services/iscsi.py
src/pybind/mgr/cephadm/services/mgmt_gateway.py
src/pybind/mgr/cephadm/services/monitoring.py
src/pybind/mgr/cephadm/services/nfs.py
src/pybind/mgr/cephadm/services/oauth2_proxy.py

index 720e88343f1f8fb2ee8de8b9f0107bbf7a6926c3..11e66b01d462d0d3764597ec87301914f9cdac8b 100644 (file)
@@ -3889,6 +3889,26 @@ Then run the following:
 
     def _check_cert_source(self, spec: ServiceSpec) -> str:
         cert_warning = ''
+        # Warn the user when certificate_source is changing, as this will
+        # trigger a service reconfiguration that may cause client disconnections,
+        # CA trust chain changes, or temporary TLS downtime.
+        if spec.service_name() in self.spec_store:
+            old_spec = self.spec_store[spec.service_name()].spec
+            old_source = getattr(old_spec, 'certificate_source', None)
+            new_source = getattr(spec, 'certificate_source', None)
+            if old_source and new_source and old_source != new_source:
+                cert_warning = (
+                    f"\n\nWarning: 'certificate_source' changed from '{old_source}' to "
+                    f"'{new_source}' for service '{spec.service_name()}'.\n"
+                    f"This will trigger a service reconfiguration on the next reconciliation cycle, which may cause\n"
+                    f"temporary client disconnections and/or a change in the TLS certificate authority trust chain.\n"
+                )
+                self.log.warning(
+                    f"certificate_source changed from '{old_source}' to '{new_source}' "
+                    f"for service '{spec.service_name()}'. This will trigger a service "
+                    f"reconfiguration."
+                )
+
         if spec.is_using_certificates_source(CertificateSource.REFERENCE):
             svc = service_registry.get_service(spec.service_type)
             if svc.SCOPE == TLSObjectScope.SERVICE:
@@ -3899,7 +3919,7 @@ Then run the following:
                         f"\n  > ceph orch certmgr cert set --cert-name {svc.cert_name} --service-name {spec.service_name()} -i <cert-key-pem-file> \n"
                     )
             else:
-                cert_warning = (
+                cert_warning += (
                     f"\n\n\nWarning: SSL is configured with '{CertificateSource.REFERENCE.value}', and this service uses per-host certificates.\n\n"
                     f"To configure keys/certificates, run the following commands for each host daemons are deployed on:\n"
                     f"  > ceph orch certmgr cert set --cert-name {svc.cert_name} --service-name {spec.service_name()} --hostname <host>  -i <cert-file>\n"
index d39c8d73fddb6ead9b67e5836c4a972c4bcaf640..d22c8e9d28f07bce5df0a570d39927a6a3dd1cdf 100644 (file)
@@ -331,7 +331,22 @@ class CephadmService(metaclass=ABCMeta):
         spec: Optional[ServiceSpec] = None,
         daemon_type: Optional[str] = None,
     ) -> List[str]:
-        return []
+
+        ssl_enabled = getattr(spec, 'ssl', False)
+        if not spec or not ssl_enabled:
+            return []
+
+        deps = []
+        cert_source = getattr(spec, 'certificate_source', None)
+        if cert_source:
+            deps.append(f'certificate_source: {cert_source}')
+        if spec.ssl_cert and spec.ssl_key:
+            deps.append(f'ssl_cert: {str(utils.md5_hash(spec.ssl_cert))}')
+            deps.append(f'ssl_key: {str(utils.md5_hash(spec.ssl_key))}')
+        if spec.ssl_ca_cert:
+            deps.append(f'ssl_ca_cert: {str(utils.md5_hash(spec.ssl_ca_cert))}')
+
+        return sorted(deps)
 
     @classmethod
     def sorted_dependencies(
@@ -1341,6 +1356,9 @@ class RgwService(CephService):
                          spec: Optional[ServiceSpec] = None,
                          daemon_type: Optional[str] = None) -> List[str]:
         deps = []
+        # we keep the following deps calculation for backward compatibility
+        # as old RGW specs use rgw_frontend_ssl_certificate instead of modern
+        # ssl_cert/ssl_key fields
         rgw_spec = cast(RGWSpec, spec)
         ssl_cert = getattr(rgw_spec, 'rgw_frontend_ssl_certificate', None)
         if ssl_cert:
@@ -1348,7 +1366,8 @@ class RgwService(CephService):
                 ssl_cert = '\n'.join(ssl_cert)
             deps.append(f'ssl-cert:{utils.config_hash(ssl_cert)}')
 
-        return sorted(deps)
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def set_realm_zg_zone(self, spec: RGWSpec) -> None:
         assert self.TYPE == spec.service_type
index 1d133f80055b9133f088eb38b327ae878d995530..41f70e7ba7dd768e81417273363e806def3bd86e 100644 (file)
@@ -118,16 +118,14 @@ class IngressService(CephService):
         assert ingress_spec.backend_service
         daemons = mgr.cache.get_daemons_by_service(ingress_spec.backend_service)
         deps = [d.name() for d in daemons]
-        for attr in ['ssl_cert', 'ssl_key']:
-            ssl_cert_key = getattr(ingress_spec, attr, None)
-            if ssl_cert_key:
-                assert isinstance(ssl_cert_key, str)
-                deps.append(f'ssl-cert-key:{utils.config_hash(ssl_cert_key)}')
         backend_spec = mgr.spec_store[ingress_spec.backend_service].spec
         if backend_spec.service_type == 'nfs':
             hosts = get_placement_hosts(spec, mgr.cache.get_schedulable_hosts(), mgr.cache.get_draining_hosts())
             deps.append(f'placement_hosts:{",".join(sorted(h.hostname for h in hosts))}')
-        return sorted(deps)
+
+        from cephadm.services.cephadmservice import CephadmService
+        parent_deps = CephadmService.get_dependencies(mgr, spec)
+        return sorted(deps + parent_deps)
 
     def haproxy_generate_config(
             self,
index 9814164b48521b0cecaf84077e07816fe1c6d91a..b33c88b52b46294fe2d416ac80844a400fc67e78 100644 (file)
@@ -43,11 +43,15 @@ class IscsiService(CephService):
     def get_dependencies(cls, mgr: "CephadmOrchestrator",
                          spec: Optional[ServiceSpec] = None,
                          daemon_type: Optional[str] = None) -> List[str]:
+        deps = []
         if spec:
             iscsi_spec = cast(IscsiServiceSpec, spec)
-            return [get_trusted_ips(mgr, iscsi_spec)]
+            deps = [get_trusted_ips(mgr, iscsi_spec)]
         else:
-            return [mgr.get_mgr_ip()]
+            deps = [mgr.get_mgr_ip()]
+
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec:
         assert self.TYPE == daemon_spec.daemon_type
index 1088b7b725011179c46463515d00eb471d4c693f..dc68eb465cbdc0b7224ae7d6d63b4d77d01c6e03 100644 (file)
@@ -86,7 +86,8 @@ class MgmtGatewayService(CephadmService):
             for service in ['mgr']
             for d in mgr.cache.get_daemons_by_service(service)
         ]
-        return deps
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]:
         assert self.TYPE == daemon_spec.daemon_type
index 438315a4118c2199d946057163bfb9d7444966d2..2cedc7a9495246d3b2096c6c6908a3d016a5b8bb 100644 (file)
@@ -120,7 +120,8 @@ class GrafanaService(CephadmService):
         for service in ['prometheus', 'loki', 'mgmt-gateway', 'oauth2-proxy']:
             deps += [d.name() for d in mgr.cache.get_daemons_by_service(service)]
 
-        return sorted(deps)
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def generate_prom_services(self, security_enabled: bool, mgmt_gw_enabled: bool) -> List[str]:
 
@@ -199,7 +200,7 @@ class GrafanaService(CephadmService):
                     dashboard = f.read()
                     config_file['files'][f'/etc/grafana/provisioning/dashboards/{file_name}'] = dashboard
 
-        return config_file, self.get_dependencies(self.mgr)
+        return config_file, self.get_dependencies(self.mgr, spec)
 
     def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription:
         # Use the least-created one as the active daemon
index ac01900eb0231ba3f10d9b55b362a516b305ff40..566d936a6ed2778b75c5708f8e36937e52fcfd62 100644 (file)
@@ -16,7 +16,6 @@ from ceph.deployment.service_spec import ServiceSpec, NFSServiceSpec
 from .service_registry import register_cephadm_service
 
 from orchestrator import DaemonDescription, OrchestratorError
-from cephadm import utils
 from cephadm.services.cephadmservice import AuthEntity, CephadmDaemonDeploySpec, CephService
 from cephadm.schedule import get_placement_hosts
 if TYPE_CHECKING:
@@ -131,18 +130,14 @@ class NFSService(CephService):
         assert spec
         deps: List[str] = []
         nfs_spec = cast(NFSServiceSpec, spec)
-        # add dependency of tls fields
-        if (spec.ssl and spec.ssl_cert and spec.ssl_key and spec.ssl_ca_cert):
-            deps.append(f'ssl_cert: {utils.config_hash(spec.ssl_cert)}')
-            deps.append(f'ssl_key: {utils.config_hash(spec.ssl_key)}')
-            deps.append(f'ssl_ca_cert: {utils.config_hash(spec.ssl_ca_cert)}')
+        deps.append(f'enable_rdma: {nfs_spec.enable_rdma}')
+        deps.append(f'rdma_port: {nfs_spec.rdma_port}')
         deps.append(f'tls_ktls: {nfs_spec.tls_ktls}')
         deps.append(f'tls_debug: {nfs_spec.tls_debug}')
         deps.append(f'tls_min_version: {nfs_spec.tls_min_version}')
         deps.append(f'tls_ciphers: {nfs_spec.tls_ciphers}')
-        deps.append(f'enable_rdma: {nfs_spec.enable_rdma}')
-        deps.append(f'rdma_port: {nfs_spec.rdma_port}')
-        return sorted(deps)
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec:
         assert self.TYPE == daemon_spec.daemon_type
index 3cbc03e6d11c585f61623faba33ddea5befbef9c..3cc74f95238383b8428468bcc4a02068a961081a 100644 (file)
@@ -34,7 +34,8 @@ class OAuth2ProxyService(CephadmService):
             for service in ['mgmt-gateway']
             for d in mgr.cache.get_daemons_by_service(service)
         ]
-        return deps
+        parent_deps = super().get_dependencies(mgr, spec, daemon_type)
+        return sorted(deps + parent_deps)
 
     def get_service_ips_and_hosts(self, service_name: str) -> List[str]:
         entries = set()