]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add namespace encryption support to NVMeoF CLI. 68339/head
authorGil Bregman <gbregman@il.ibm.com>
Mon, 6 Apr 2026 22:08:15 +0000 (01:08 +0300)
committerGil Bregman <gbregman@il.ibm.com>
Wed, 29 Apr 2026 21:19:41 +0000 (00:19 +0300)
Fixes: https://tracker.ceph.com/issues/74965
Signed-off-by: Gil Bregman <gbregman@il.ibm.com>
(cherry picked from commit 0063491fa2d71419a67e184bd04974c7833eb9f0)
Signed-off-by: Gil Bregman <gbregman@il.ibm.com>
src/pybind/mgr/dashboard/controllers/nvmeof.py
src/pybind/mgr/dashboard/model/nvmeof.py
src/pybind/mgr/dashboard/openapi.yaml

index c75ccd01f9dfe38ff97a186bb046329db97d2724..727e1adf5c68cf5e6dccf9fa9e30663e64a61fee 100644 (file)
@@ -310,6 +310,94 @@ else:
                 )
             )
 
+        @empty_response
+        @NvmeofCLICommand(
+            "nvmeof subsystem add_kmip_server_endpoint", model.RequestStatus
+        )
+        @EndpointDoc(
+            "Add a KMIP server endpoint to the subsystem",
+            parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN"),
+                "server_name": Param(str, "Name of the KMIP server the endpoint points to"),
+                "address": Param(str, "KMIP server endpoint address", True, None),
+                "port": Param(int, "KMIP server endpoint port", True, 5696),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "traddr": Param(str, "NVMeoF gateway address", True, None),
+            },
+        )
+        @convert_to_model(model.RequestStatus)
+        @handle_nvmeof_error
+        def add_kmip_server_endpoint(self, nqn: str, server_name: str,
+                                     address: Optional[str] = None,
+                                     port: Optional[int] = 5696, gw_group: Optional[str] = None,
+                                     traddr: Optional[str] = None):
+            ep = NVMeoFClient.pb2.kmip_server_endpoint(address=address, port=port)
+            return NVMeoFClient(
+                gw_group=gw_group,
+                traddr=traddr
+            ).stub.add_kmip_server_endpoints(
+                NVMeoFClient.pb2.add_kmip_server_endpoints_req(
+                    subsystem_nqn=nqn, server_name=server_name,
+                    endpoints=[ep]
+                )
+            )
+
+        @empty_response
+        @NvmeofCLICommand(
+            "nvmeof subsystem del_kmip_server_endpoint", model.RequestStatus
+        )
+        @EndpointDoc(
+            "Delete a KMIP server endpoint from the subsystem",
+            parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN"),
+                "server_name": Param(str, "Name of the KMIP server the endpoint points to"),
+                "address": Param(str, "KMIP server endpoint address", True, None),
+                "port": Param(int, "KMIP server endpoint port", True, 5696),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "traddr": Param(str, "NVMeoF gateway address", True, None),
+            },
+        )
+        @convert_to_model(model.RequestStatus)
+        @handle_nvmeof_error
+        def del_kmip_server_endpoint(self, nqn: str, server_name: str,
+                                     address: Optional[str] = None,
+                                     port: Optional[int] = 5696, gw_group: Optional[str] = None,
+                                     traddr: Optional[str] = None):
+            ep = NVMeoFClient.pb2.kmip_server_endpoint(address=address, port=port)
+            return NVMeoFClient(
+                gw_group=gw_group,
+                traddr=traddr
+            ).stub.del_kmip_server_endpoints(
+                NVMeoFClient.pb2.del_kmip_server_endpoints_req(
+                    subsystem_nqn=nqn, server_name=server_name,
+                    endpoints=[ep]
+                )
+            )
+
+        @NvmeofCLICommand("nvmeof subsystem list_kmip_server_endpoints",
+                          model.SubsystemListKMIPEndpoints)
+        @EndpointDoc(
+            "List KMIP server endpoints for a subsystem or all subsystems",
+            parameters={
+                "nqn": Param(str, "Only show endpoints for this subsystem NQN", True, None),
+                "server_name": Param(str, "Only show endpoints for this KMIP server name",
+                                     True, None),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "traddr": Param(str, "NVMeoF gateway address", True, None),
+            },
+        )
+        @convert_to_model(model.SubsystemListKMIPEndpoints)
+        @handle_nvmeof_error
+        def list_eps(self, nqn: Optional[str] = None, server_name: Optional[str] = None,
+                     gw_group: Optional[str] = None, traddr: Optional[str] = None):
+            return NVMeoFClient(
+                gw_group=gw_group,
+                traddr=traddr
+            ).stub.list_kmip_server_endpoints(
+                NVMeoFClient.pb2.list_kmip_server_endpoints_req(subsystem_nqn=nqn,
+                                                                server_name=server_name)
+            )
+
         @NvmeofCLICommand("nvmeof get_subsystems", model.GetSubsystems)
         @convert_to_model(model.GetSubsystems)
         @handle_nvmeof_error
@@ -527,7 +615,18 @@ else:
                 "no_auto_visible": Param(
                     bool,
                     "Namespace will be visible only for the allowed hosts"
-                )
+                ),
+                "encryption_format": Param([str],
+                                           "Encryption format(s) to use, LUKS1 or LUKS2, "
+                                           "separated by commas",
+                                           True, None),
+                "encryption_algorithm": Param(str,
+                                              "Algorithm to use for encryption",
+                                              True, None),
+                "key_id": Param([str],
+                                "Key ID(s) to use for encryption pass phrases, "
+                                "separated by commas",
+                                True, None),
             },
         )
         @convert_to_model(model.NamespaceCreation)
@@ -551,8 +650,29 @@ else:
             gw_group: Optional[str] = None,
             traddr: Optional[str] = None,
             rados_namespace: Optional[str] = None,
+            encryption_format: Optional[List[str]] = None,
+            encryption_algorithm: Optional[str] = None,
+            key_id: Optional[List[str]] = None,
         ):
-            return NVMeoFClient(gw_group=gw_group, traddr=traddr).stub.namespace_add(
+            encryption_format = encryption_format or []
+            key_id = key_id or []
+            if len(encryption_format) != len(key_id):
+                raise DashboardException(
+                    msg="The number of key IDs should match the number of encryption formats",
+                    code="key_ids_encryption_formats_mismatch",
+                    http_status_code=400,
+                    component="nvmeof",
+                )
+            enc_entries = [
+                NVMeoFClient.pb2.encryption_entry(
+                    format=f.strip().lower(),
+                    key_id=k.strip()
+                ) for f, k in zip(encryption_format, key_id)]
+            enc_alg = encryption_algorithm.strip().lower() if encryption_algorithm else None
+            return NVMeoFClient(
+                gw_group=gw_group,
+                traddr=traddr
+            ).stub.namespace_add(
                 NVMeoFClient.pb2.namespace_add_req(
                     subsystem_nqn=nqn,
                     nsid=int(nsid) if nsid else None,
@@ -567,7 +687,9 @@ else:
                     force=force,
                     no_auto_visible=no_auto_visible,
                     disable_auto_resize=disable_auto_resize,
-                    read_only=read_only
+                    read_only=read_only,
+                    encryption_entries=enc_entries,
+                    encryption_algorithm=enc_alg
                 )
             )
 
@@ -598,7 +720,18 @@ else:
                 "no_auto_visible": Param(
                     bool,
                     "Namespace will be visible only for the allowed hosts"
-                )
+                ),
+                "encryption_format": Param([str],
+                                           "Encryption format(s) to use, LUKS1 or LUKS2, "
+                                           "separated by commas",
+                                           True, None),
+                "encryption_algorithm": Param(str,
+                                              "Algorithm to use for encryption",
+                                              True, None),
+                "key_id": Param([str],
+                                "Key ID(s) to use for encryption pass phrases, "
+                                "separated by commas",
+                                True, None),
             },
         )
         @convert_to_model(model.NamespaceCreation)
@@ -622,7 +755,9 @@ else:
             gw_group: Optional[str] = None,
             traddr: Optional[str] = None,
             rados_namespace: Optional[str] = None,
-
+            encryption_format: Optional[List[str]] = None,
+            encryption_algorithm: Optional[str] = None,
+            key_id: Optional[List[str]] = None,
         ):
             if size and rbd_image_size:
                 raise DashboardException(
@@ -637,7 +772,27 @@ else:
                 size_b = convert_to_bytes(size, default_unit='MB')
             if rbd_image_size:
                 rbd_image_size_b = convert_to_bytes(rbd_image_size, default_unit='MB')
-            return NVMeoFClient(gw_group=gw_group, traddr=traddr).stub.namespace_add(
+            if not encryption_format:
+                encryption_format = []
+            if not key_id:
+                key_id = []
+            if len(encryption_format) != len(key_id):
+                raise DashboardException(
+                    msg="The number of key IDs should match the number of encryption formats",
+                    code="key_ids_encryption_formats_mismatch",
+                    http_status_code=400,
+                    component="nvmeof",
+                )
+            enc_entries = [
+                NVMeoFClient.pb2.encryption_entry(
+                    format=f.strip().lower(),
+                    key_id=k.strip()
+                ) for f, k in zip(encryption_format, key_id)]
+            enc_alg = encryption_algorithm.strip().lower() if encryption_algorithm else None
+            return NVMeoFClient(
+                gw_group=gw_group,
+                traddr=traddr
+            ).stub.namespace_add(
                 NVMeoFClient.pb2.namespace_add_req(
                     subsystem_nqn=nqn,
                     nsid=int(nsid) if nsid else None,
@@ -652,7 +807,9 @@ else:
                     force=force,
                     no_auto_visible=no_auto_visible,
                     disable_auto_resize=disable_auto_resize,
-                    read_only=read_only
+                    read_only=read_only,
+                    encryption_entries=enc_entries,
+                    encryption_algorithm=enc_alg
                 )
             )
 
index e0f12a39e565699ab3642ec6dbcd02e07b208ad9..1dcd6babf2c60b07833d68a69f099e42810b4922 100644 (file)
@@ -99,6 +99,19 @@ class SubsystemStatus(NamedTuple):
     nqn: str
 
 
+class KMIPServerEndpoint(NamedTuple):
+    subsystem_nqn: str
+    server_name: str
+    address: str
+    port: int
+
+
+class SubsystemListKMIPEndpoints(NamedTuple):
+    status: int
+    error_message: str
+    endpoints: Annotated[List[KMIPServerEndpoint], CliFlags.EXCLUSIVE_LIST]
+
+
 class Connection(NamedTuple):
     nqn: str
     traddr: str
@@ -128,6 +141,11 @@ class NamespaceCreation(NamedTuple):
     nsid: int
 
 
+class EncryptionEntry(NamedTuple):
+    format: str
+    key_id: str
+
+
 class Namespace(NamedTuple):
     bdev_name: str
     rbd_image_name: Annotated[str, CliHeader("RBD Image")]
@@ -148,6 +166,9 @@ class Namespace(NamedTuple):
     trash_image: Optional[bool]
     disable_auto_resize: Optional[bool]
     read_only: Optional[bool]
+    location: Optional[str]
+    encryption_algorithm: Optional[str]
+    encryption_entries: Annotated[List[EncryptionEntry], CliFlags.EXCLUSIVE_LIST]
 
 
 class NamespaceList(NamedTuple):
index 3076c0ca8836bbcda66d0d7bbf07a13bdd3c307d..e86feb03541c8e7158c57a66c461908b0699f06d 100644 (file)
@@ -10431,6 +10431,15 @@ paths:
                   default: false
                   description: Disable auto resize
                   type: string
+                encryption_algorithm:
+                  description: Algorithm to use for encryption
+                  type: string
+                encryption_format:
+                  description: Encryption format(s) to use, LUKS1 or LUKS2, separated
+                    by commas
+                  items:
+                    type: string
+                  type: array
                 force:
                   default: false
                   description: Force create namespace even it image is used by other
@@ -10439,6 +10448,12 @@ paths:
                 gw_group:
                   description: NVMeoF gateway group
                   type: string
+                key_id:
+                  description: Key ID(s) to use for encryption pass phrases, separated
+                    by commas
+                  items:
+                    type: string
+                  type: array
                 load_balancing_group:
                   description: Load balancing group
                   type: integer