From: Redouane Kachach Date: Wed, 11 Feb 2026 11:17:55 +0000 (+0100) Subject: mgr/cephadm: adding tls fields as deps for services with TLS support X-Git-Tag: v21.0.1~168^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d3f3bcbcf42b3d4add612c77081b6219cc69f40a;p=ceph.git mgr/cephadm: adding tls fields as deps for services with TLS support 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 --- diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 720e88343f1..11e66b01d46 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -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 \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 -i \n" diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index d39c8d73fdd..d22c8e9d28f 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -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 diff --git a/src/pybind/mgr/cephadm/services/ingress.py b/src/pybind/mgr/cephadm/services/ingress.py index 1d133f80055..41f70e7ba7d 100644 --- a/src/pybind/mgr/cephadm/services/ingress.py +++ b/src/pybind/mgr/cephadm/services/ingress.py @@ -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, diff --git a/src/pybind/mgr/cephadm/services/iscsi.py b/src/pybind/mgr/cephadm/services/iscsi.py index 9814164b485..b33c88b52b4 100644 --- a/src/pybind/mgr/cephadm/services/iscsi.py +++ b/src/pybind/mgr/cephadm/services/iscsi.py @@ -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 diff --git a/src/pybind/mgr/cephadm/services/mgmt_gateway.py b/src/pybind/mgr/cephadm/services/mgmt_gateway.py index 1088b7b7250..dc68eb465cb 100644 --- a/src/pybind/mgr/cephadm/services/mgmt_gateway.py +++ b/src/pybind/mgr/cephadm/services/mgmt_gateway.py @@ -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 diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py index 438315a4118..2cedc7a9495 100644 --- a/src/pybind/mgr/cephadm/services/monitoring.py +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -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 diff --git a/src/pybind/mgr/cephadm/services/nfs.py b/src/pybind/mgr/cephadm/services/nfs.py index ac01900eb02..566d936a6ed 100644 --- a/src/pybind/mgr/cephadm/services/nfs.py +++ b/src/pybind/mgr/cephadm/services/nfs.py @@ -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 diff --git a/src/pybind/mgr/cephadm/services/oauth2_proxy.py b/src/pybind/mgr/cephadm/services/oauth2_proxy.py index 3cbc03e6d11..3cc74f95238 100644 --- a/src/pybind/mgr/cephadm/services/oauth2_proxy.py +++ b/src/pybind/mgr/cephadm/services/oauth2_proxy.py @@ -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()