]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: use secure_channel for grpc requests
authorNizamudeen A <nia@redhat.com>
Mon, 27 May 2024 04:35:21 +0000 (10:05 +0530)
committerNizamudeen A <nia@redhat.com>
Tue, 9 Jul 2024 09:50:17 +0000 (15:20 +0530)
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 <nia@redhat.com>
(cherry picked from commit 0a393ca9bde714524da369caaef1c097472f791c)

src/pybind/mgr/cephadm/services/nvmeof.py
src/pybind/mgr/dashboard/services/nvmeof_client.py
src/pybind/mgr/dashboard/services/nvmeof_conf.py
src/pybind/mgr/dashboard/services/orchestrator.py

index 1bad7447b03700a4edb2bb6960425f1597fb1ad6..ac258887f6a510174e9ba7ed59e7ba26132aefff 100644 (file)
@@ -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')
index 5dee7dfcfbc3b1d3a8ce17885fe83117cc5e3717..019ecf0267c81d65e86dcda644246eae796049bc 100644 (file)
@@ -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:
index 901098ea5665aa106ebe993e2c6006294208d341..5777d33750f14a57bfa452602bd927d38d3af619 100644 (file)
@@ -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}',
+            )
index 2feeaecc5c79a0af54c0e5ce0dc04a433ab95c32..1f77b3c0ab566140662493fe28525700fe3a670e 100644 (file)
@@ -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']