From: Redouane Kachach Date: Tue, 12 Aug 2025 14:12:27 +0000 (+0200) Subject: mgr/cepahdm: adding new filtring options and certificate ref checks X-Git-Tag: testing/wip-vshankar-testing-20250908.050731-debug~2^2~18 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=626a82d98f45728073d50d84caf6a8e54d856ae1;p=ceph-ci.git mgr/cepahdm: adding new filtring options and certificate ref checks Signed-off-by: Redouane Kachach --- diff --git a/src/pybind/mgr/cephadm/inventory.py b/src/pybind/mgr/cephadm/inventory.py index 1168fa8e199..f249fc25075 100644 --- a/src/pybind/mgr/cephadm/inventory.py +++ b/src/pybind/mgr/cephadm/inventory.py @@ -50,18 +50,18 @@ class OrchSecretNotFound(OrchestratorError): def __init__( self, message: Optional[str] = '', - entity: Optional[str] = '', + consumer: Optional[str] = '', service_name: Optional[str] = '', hostname: Optional[str] = '' ): if not message: - message = f'No secret found for entity {entity}' + message = f'No secret found for consumer {consumer}' if service_name: message += f' with service name {service_name}' if hostname: message += f' with hostname {hostname}' super().__init__(message) - self.entity = entity + self.consumer = consumer self.service_name = service_name self.hostname = hostname diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 0b011e521cc..e1983e47db0 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -42,6 +42,7 @@ from ceph.deployment.service_spec import ( TunedProfileSpec, MgmtGatewaySpec, NvmeofServiceSpec, + CertificateSource ) from ceph.deployment.drive_group import DeviceSelection from ceph.utils import str_to_datetime, datetime_to_str, datetime_now @@ -724,7 +725,8 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule, for svc in service_registry.get_all_services(): if svc.allows_user_certificates: - assert svc.SCOPE != TLSObjectScope.UNKNOWN, f"Service {svc.TYPE} requieres certificates but it has not defined its svc.SCOPE field." + if svc.SCOPE == TLSObjectScope.UNKNOWN: + OrchestratorError(f"Service {svc.TYPE} requieres certificates but it has not defined its svc.SCOPE field.") self.cert_mgr.register_cert_key_pair(svc.TYPE, svc.cert_name, svc.key_name, svc.SCOPE) self.cert_mgr.register_cert_key_pair('nvmeof', 'nvmeof_client_cert', 'nvmeof_client_key', TLSObjectScope.SERVICE) @@ -3318,8 +3320,11 @@ Then run the following: 'certificate': self.cert_mgr.get_root_ca()} @handle_orch_error - def cert_store_cert_ls(self, show_details: bool = False) -> Dict[str, Any]: - return self.cert_mgr.cert_ls(show_details) + def cert_store_cert_ls(self, + filter_by: str = '', + show_details: bool = False, + include_cephadm_signed: bool = False) -> Dict[str, Any]: + return self.cert_mgr.cert_ls(filter_by, show_details, include_cephadm_signed) @handle_orch_error def cert_store_bindings_ls(self) -> Dict[str, Dict[str, List[str]]]: @@ -3343,8 +3348,8 @@ Then run the following: return report @handle_orch_error - def cert_store_key_ls(self) -> Dict[str, Any]: - return self.cert_mgr.key_ls() + def cert_store_key_ls(self, include_cephadm_generated_keys: bool = False) -> Dict[str, Any]: + return self.cert_mgr.key_ls(include_cephadm_generated_keys) @handle_orch_error def cert_store_get_cert( @@ -3358,7 +3363,7 @@ Then run the following: if not cert: if no_exception_when_missing: return '' - raise OrchSecretNotFound(entity=cert_name, service_name=service_name, hostname=hostname) + raise OrchSecretNotFound(consumer=cert_name, service_name=service_name, hostname=hostname) return cert @handle_orch_error @@ -3373,35 +3378,35 @@ Then run the following: if not key: if no_exception_when_missing: return '' - raise OrchSecretNotFound(entity=key_name, service_name=service_name, hostname=hostname) + raise OrchSecretNotFound(consumer=key_name, service_name=service_name, hostname=hostname) return key + def _raise_non_editable_cert_error(self, cert_name: str, consumer: str, service_name: str, hostname: str) -> None: + if service_name: + context = f"service '{service_name}'" + elif hostname: + context = f"host '{hostname}'" + elif consumer: + context = f"'{consumer}'" + + raise OrchestratorError( + f"Certificate '{cert_name}' for {context} is not editable (defined as inline in the spec or generated by cephadm)." + ) + @handle_orch_error def cert_store_set_pair( self, cert: str, key: str, - entity: str, + consumer: str, cert_name: str = "", service_name: str = "", hostname: str = "", force: bool = False ) -> str: - def raise_non_editable_cert_error() -> None: - if service_name: - context = f"service '{service_name}'" - elif hostname: - context = f"host '{hostname}'" - else: - context = f"'{consumer}'" - - raise OrchestratorError( - f"Certificate '{cert_name}' for {context} is not editable (defined as inline in the spec or generated by cephadm)." - ) - if consumer not in self.cert_mgr.list_consumers(): - raise OrchestratorError(f"Invalid consumer: {consumer}. Please use 'ceph orch certmgr bindings ls' to list valid consumers.") + raise OrchestratorError(f"Invalid service: {consumer}. Please use 'ceph orch certmgr bindings ls' to list valid bindings.") # Check the certificate validity status target = service_name or hostname @@ -3416,7 +3421,7 @@ Then run the following: if len(cert_names) == 1: cert_name = cert_names[0] elif len(cert_names) > 1 and not cert_name: - raise OrchestratorError(f"Consumer '{consumer}' has many certificates, please use --cert-name argument to specify which one from the list: {cert_names}") + raise OrchestratorError(f"Service '{consumer}' has many certificates, please use the --cert-name argument to specify which one from the list: {cert_names}") # Check the certificate scope scope_errors = { @@ -3428,8 +3433,8 @@ Then run the following: if (scope == TLSObjectScope.HOST and not hostname) or (scope == TLSObjectScope.SERVICE and not service_name): raise OrchestratorError(scope_errors[scope]) - if not self.cert_mgr.is_cert_editable(cert_name, service_name or '', hostname or ''): - raise_non_editable_cert_error() + if not debug_mode and not self.cert_mgr.is_cert_editable(cert_name, service_name or '', hostname or ''): + self._raise_non_editable_cert_error(cert_name, consumer, service_name, hostname) key_name = cert_name.replace('_cert', '_key') self.cert_mgr.save_cert(cert_name, cert, service_name, hostname, user_made=True, editable=True) @@ -3443,21 +3448,13 @@ Then run the following: cert: str, service_name: str = "", hostname: str = "", + force: bool = False ) -> str: - def raise_non_editable_cert_error() -> None: - if service_name: - context = f"service '{service_name}'" - elif hostname: - context = f"host '{hostname}'" - raise OrchestratorError( - f"Certificate '{cert_name}' for {context} is not editable (defined as inline in the spec or generated by cephadm)." - ) - debug_mode = self.certificate_check_debug_mode and force if not debug_mode: if not self.cert_mgr.is_cert_editable(cert_name, service_name or '', hostname or ''): - raise_non_editable_cert_error() + self._raise_non_editable_cert_error(cert_name, '', service_name, hostname) target = service_name or hostname cert_info = self.cert_mgr.check_certificate_state(cert_name, target, cert) if not cert_info.is_operationally_valid(): @@ -3485,12 +3482,14 @@ Then run the following: hostname: Optional[str] = None, ) -> str: + cert_err = OrchestratorError("Cannot delete the certificate. Please use 'ceph orch certmgr cert ls' to list available certificates. \n" + "Note: for certificates with host/service scope use --service-name or --hostname to specify the target.") try: - self.cert_mgr.rm_cert(cert_name, service_name, hostname) + if not self.cert_mgr.rm_cert(cert_name, service_name, hostname): + raise cert_err return f'Certificate for {cert_name} removed correctly' except TLSObjectException: - raise OrchestratorError("Cannot delete the certificate. Please use 'ceph orch certmgr cert ls' to list available certificates. \n" - "Note: for certificates with host/service scope use --service-name or --hostname to specify the target.") + raise cert_err @handle_orch_error def cert_store_rm_key( @@ -3713,6 +3712,29 @@ Then run the following: results.append(self._plan(cast(ServiceSpec, spec))) return results + def _check_cert_source(self, spec: ServiceSpec) -> str: + cert_warning = '' + if spec.is_using_certificates_source(CertificateSource.REFERENCE): + svc = service_registry.get_service(spec.service_type) + if svc.SCOPE == TLSObjectScope.SERVICE: + if not self.cert_mgr.cert_exists(svc.cert_name, service_name=spec.service_name(), host=None): + raise OrchestratorError( + f"\n\nSSL is configured with '{CertificateSource.REFERENCE.value}', but cannot find an entry for the service '{spec.service_name()}'" + f"\nunder the certificate '{svc.cert_name}' within the certmgr store. To set the certificate, use:\n" + f"\n > ceph orch certmgr cert set --cert-name {svc.cert_name} --service-name {spec.service_name()} -i \n" + ) + else: + 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" + f" > ceph orch certmgr key set --key-name {svc.cert_name} --service-name {spec.service_name()} --hostname -i \n\n" + f"Once all certificates are provisioned, run:\n" + f" > ceph orch reconfig {spec.service_name()}\n" + f"to reconfigure the service with the certificates." + ) + return cert_warning + def _apply_service_spec(self, spec: ServiceSpec) -> str: if spec.placement.is_empty(): # fill in default placement @@ -3755,6 +3777,8 @@ Then run the following: host_count = len(self.inventory.keys()) max_count = self.max_count_per_host + cert_warning = self._check_cert_source(spec) + if spec.service_type == 'nvmeof': nvmeof_spec = cast(NvmeofServiceSpec, spec) assert nvmeof_spec.pool is not None, "Pool cannot be None for nvmeof services" @@ -3803,7 +3827,7 @@ Then run the following: spec.service_name(), spec.placement.pretty_str())) self.spec_store.save(spec) self._kick_serve_loop() - return "Scheduled %s update..." % spec.service_name() + return f"Scheduled {spec.service_name()} update...{cert_warning}" @handle_orch_error def apply( diff --git a/src/pybind/mgr/orchestrator/_interface.py b/src/pybind/mgr/orchestrator/_interface.py index 767c2a72015..8fff2919d8f 100644 --- a/src/pybind/mgr/orchestrator/_interface.py +++ b/src/pybind/mgr/orchestrator/_interface.py @@ -1,4 +1,3 @@ - """ ceph-mgr orchestrator interface @@ -587,7 +586,10 @@ class Orchestrator(object): """ raise NotImplementedError() - def cert_store_cert_ls(self, show_details: bool = False) -> OrchResult[Dict[str, Any]]: + def cert_store_cert_ls(self, + filter_by: str = '', + show_details: bool = False, + include_cephadm_signed: bool = False) -> OrchResult[Dict[str, Any]]: raise NotImplementedError() def cert_store_bindings_ls(self) -> OrchResult[Dict[Any, Dict[str, List[str]]]]: @@ -599,7 +601,7 @@ class Orchestrator(object): def cert_store_cert_check(self) -> OrchResult[List[str]]: raise NotImplementedError() - def cert_store_key_ls(self) -> OrchResult[Dict[str, Any]]: + def cert_store_key_ls(self, include_cephadm_generated_keys: bool = False) -> OrchResult[Dict[str, Any]]: raise NotImplementedError() def cert_store_get_cert( @@ -624,7 +626,7 @@ class Orchestrator(object): self, cert: str, key: str, - entity: str, + consumer: str, cert_name: Optional[str] = None, service_name: Optional[str] = None, hostname: Optional[str] = None, @@ -638,6 +640,7 @@ class Orchestrator(object): cert: str, service_name: Optional[str] = None, hostname: Optional[str] = None, + force: bool = False ) -> OrchResult[str]: raise NotImplementedError() diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index 8b71e0f12d1..14319a7d894 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -1166,8 +1166,12 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, return HandleCommandResult(stdout=output) @_cli_read_command('orch certmgr cert ls') - def _cert_store_cert_ls(self, show_details: bool = False, format: Format = Format.plain) -> HandleCommandResult: - completion = self.cert_store_cert_ls(show_details) + def _cert_store_cert_ls(self, + filter_by: str = '', + show_details: bool = False, + include_cephadm_signed: bool = False, + format: Format = Format.plain) -> HandleCommandResult: + completion = self.cert_store_cert_ls(filter_by, show_details, include_cephadm_signed) cert_ls = raise_if_exception(completion) if format != Format.plain: return HandleCommandResult(stdout=to_format(cert_ls, format, many=False, cls=None)) @@ -1175,14 +1179,14 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, result_str = self._process_cert_store_json(cert_ls, 0) return HandleCommandResult(stdout=result_str) - @_cli_read_command('orch certmgr entity ls') - def _cert_store_entity_ls(self, format: Format = Format.plain) -> HandleCommandResult: - completion = self.cert_store_entity_ls() - entity_ls = raise_if_exception(completion) + @_cli_read_command('orch certmgr bindings ls') + def _cert_store_bindings_ls(self, format: Format = Format.plain) -> HandleCommandResult: + completion = self.cert_store_bindings_ls() + bindings_ls = raise_if_exception(completion) if format != Format.plain: - return HandleCommandResult(stdout=to_format(entity_ls, format, many=False, cls=None)) + return HandleCommandResult(stdout=to_format(bindings_ls, format, many=False, cls=None)) else: - result_str = yaml.dump(entity_ls, default_flow_style=False, sort_keys=False) + result_str = yaml.dump(bindings_ls, default_flow_style=False, sort_keys=False) return HandleCommandResult(stdout=result_str) @_cli_read_command('orch certmgr cert check') @@ -1196,8 +1200,10 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, return HandleCommandResult(stdout=result_str) @_cli_read_command('orch certmgr key ls') - def _cert_store_key_ls(self, format: Format = Format.plain) -> HandleCommandResult: - completion = self.cert_store_key_ls() + def _cert_store_key_ls(self, + include_cephadm_generated_keys: bool = False, + format: Format = Format.plain) -> HandleCommandResult: + completion = self.cert_store_key_ls(include_cephadm_generated_keys) key_ls = raise_if_exception(completion) if format != Format.plain: return HandleCommandResult(stdout=to_format(key_ls, format, many=False, cls=None)) @@ -1244,14 +1250,14 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, @_cli_write_command('orch certmgr cert-key set') def _cert_store_cert_key_set( self, - entity: str, + consumer: str, _end_positional_: int = 0, cert: Optional[str] = None, key: Optional[str] = None, cert_name: Optional[str] = None, service_name: Optional[str] = None, hostname: Optional[str] = None, - force: Optional[bool] = False, + force: bool = False, inbuf: Optional[str] = None ) -> HandleCommandResult: """ @@ -1269,7 +1275,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, completion = self.cert_store_set_pair( cert_content, key_content, - entity, + consumer, cert_name, service_name, hostname, @@ -1286,6 +1292,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, cert: Optional[str] = None, service_name: Optional[str] = None, hostname: Optional[str] = None, + force: bool = False, inbuf: Optional[str] = None ) -> HandleCommandResult: """ @@ -1300,6 +1307,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, cert_content, service_name, hostname, + force ) output = raise_if_exception(completion) return HandleCommandResult(stdout=output)