From: Nizamudeen A Date: Mon, 27 May 2024 04:35:21 +0000 (+0530) Subject: mgr/dashboard: use secure_channel for grpc requests X-Git-Tag: v20.0.0~1544^2~2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=ebcb198894347d1a21ab4cd81fea985aca454b29;p=ceph.git mgr/dashboard: use secure_channel for grpc requests Store the certificates to config-key stores and then later on used by dashboard to set-up the secure_channel for grpc nvmeof requests By storing the certificates we can ensure that the dashboard nvmeof apis will be configurable even if the deployments are not cephadm based Signed-off-by: Nizamudeen A (cherry picked from commit 0a393ca9bde714524da369caaef1c097472f791c) --- diff --git a/src/pybind/mgr/cephadm/services/nvmeof.py b/src/pybind/mgr/cephadm/services/nvmeof.py index 1bad7447b0370..ac258887f6a51 100644 --- a/src/pybind/mgr/cephadm/services/nvmeof.py +++ b/src/pybind/mgr/cephadm/services/nvmeof.py @@ -89,9 +89,10 @@ class NvmeofService(CephService): for dd in daemon_descrs: assert dd.hostname is not None + service_name = dd.service_name() if not spec: - logger.warning(f'No ServiceSpec found for {dd.service_name()}') + logger.warning(f'No ServiceSpec found for {service_name}') continue ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname)) @@ -104,7 +105,7 @@ class NvmeofService(CephService): cmd_dicts.append({ 'prefix': 'dashboard nvmeof-gateway-add', 'inbuf': service_url, - 'name': dd.hostname + 'name': service_name }) return cmd_dicts @@ -140,11 +141,12 @@ class NvmeofService(CephService): """ # to clean the keyring up super().post_remove(daemon, is_failed_deploy=is_failed_deploy) + service_name = daemon.service_name() # remove config for dashboard nvmeof gateways if any ret, out, err = self.mgr.mon_command({ 'prefix': 'dashboard nvmeof-gateway-rm', - 'name': daemon.hostname, + 'name': service_name, }) if not ret: logger.info(f'{daemon.hostname} removed from nvmeof gateways dashboard config') diff --git a/src/pybind/mgr/dashboard/services/nvmeof_client.py b/src/pybind/mgr/dashboard/services/nvmeof_client.py index 5dee7dfcfbc3b..019ecf0267c81 100644 --- a/src/pybind/mgr/dashboard/services/nvmeof_client.py +++ b/src/pybind/mgr/dashboard/services/nvmeof_client.py @@ -24,12 +24,23 @@ else: def __init__(self): logger.info("Initiating nvmeof gateway connection...") - - self.gateway_addr = list( - NvmeofGatewaysConfig.get_gateways_config()["gateways"].values() - )[0]["service_url"] - self.channel = grpc.insecure_channel("{}".format(self.gateway_addr)) - logger.info("Found nvmeof gateway at %s", self.gateway_addr) + service_name, self.gateway_addr = NvmeofGatewaysConfig.get_service_info() + + root_ca_cert = NvmeofGatewaysConfig.get_root_ca_cert(service_name) + client_key = NvmeofGatewaysConfig.get_client_key(service_name) + client_cert = NvmeofGatewaysConfig.get_client_cert(service_name) + + if root_ca_cert and client_key and client_cert: + logger.info('Securely connecting to: %s', self.gateway_addr) + credentials = grpc.ssl_channel_credentials( + root_certificates=root_ca_cert, + private_key=client_key, + certificate_chain=client_cert, + ) + self.channel = grpc.secure_channel(self.gateway_addr, credentials) + else: + logger.info("Insecurely connecting to: %s", self.gateway_addr) + self.channel = grpc.insecure_channel(self.gateway_addr) self.stub = pb2_grpc.GatewayStub(self.channel) def make_namedtuple_from_object(cls: Type[NamedTuple], obj: Any) -> NamedTuple: diff --git a/src/pybind/mgr/dashboard/services/nvmeof_conf.py b/src/pybind/mgr/dashboard/services/nvmeof_conf.py index 901098ea5665a..5777d33750f14 100644 --- a/src/pybind/mgr/dashboard/services/nvmeof_conf.py +++ b/src/pybind/mgr/dashboard/services/nvmeof_conf.py @@ -2,7 +2,11 @@ import json +from orchestrator import OrchestratorError + from .. import mgr +from ..exceptions import DashboardException +from ..services.orchestrator import OrchClient class NvmeofGatewayAlreadyExists(Exception): @@ -58,3 +62,44 @@ class NvmeofGatewaysConfig(object): raise NvmeofGatewayDoesNotExist(name) del config['gateways'][name] cls._save_config(config) + + @classmethod + def get_service_info(cls): + try: + config = cls.get_gateways_config() + service_name = list(config['gateways'].keys())[0] + addr = config['gateways'][service_name]['service_url'] + return service_name, addr + except (KeyError, IndexError) as e: + raise DashboardException( + msg=f'NVMe-oF configuration is not set: {e}', + ) + + @classmethod + def get_client_cert(cls, service_name: str): + client_cert = cls.from_cert_store('nvmeof_client_cert', service_name) + return client_cert.encode() if client_cert else None + + @classmethod + def get_client_key(cls, service_name: str): + client_key = cls.from_cert_store('nvmeof_client_key', service_name, key=True) + return client_key.encode() if client_key else None + + @classmethod + def get_root_ca_cert(cls, service_name: str): + root_ca_cert = cls.from_cert_store('nvmeof_root_ca_cert', service_name) + return root_ca_cert.encode() if root_ca_cert else None + + @classmethod + def from_cert_store(cls, entity: str, service_name: str, key=False): + try: + orch = OrchClient.instance() + if orch.available(): + if key: + return orch.cert_store.get_key(entity, service_name) + return orch.cert_store.get_cert(entity, service_name) + return None + except OrchestratorError as e: + raise DashboardException( + msg=f'Failed to get {entity} for {service_name}: {e}', + ) diff --git a/src/pybind/mgr/dashboard/services/orchestrator.py b/src/pybind/mgr/dashboard/services/orchestrator.py index 2feeaecc5c79a..1f77b3c0ab566 100644 --- a/src/pybind/mgr/dashboard/services/orchestrator.py +++ b/src/pybind/mgr/dashboard/services/orchestrator.py @@ -205,6 +205,19 @@ class HardwareManager(ResourceManager): return self.api.node_proxy_common(category, hostname=hostname) +class CertStoreManager(ResourceManager): + + @wait_api_result + def get_cert(self, entity: str, service_name: Optional[str] = None, + hostname: Optional[str] = None) -> str: + return self.api.cert_store_get_cert(entity, service_name, hostname) + + @wait_api_result + def get_key(self, entity: str, service_name: Optional[str] = None, + hostname: Optional[str] = None) -> str: + return self.api.cert_store_get_key(entity, service_name, hostname) + + class OrchClient(object): _instance = None @@ -226,6 +239,7 @@ class OrchClient(object): self.daemons = DaemonManager(self.api) self.upgrades = UpgradeManager(self.api) self.hardware = HardwareManager(self.api) + self.cert_store = CertStoreManager(self.api) def available(self, features: Optional[List[str]] = None) -> bool: available = self.status()['available']