From: Gil Bregman Date: Mon, 6 Apr 2026 22:08:15 +0000 (+0300) Subject: mgr/dashboard: Add namespace encryption support to NVMeoF CLI. X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=9328fd62facfa26fbc961e1f5b2c3de6e2d93d45;p=ceph.git mgr/dashboard: Add namespace encryption support to NVMeoF CLI. Fixes: https://tracker.ceph.com/issues/74965 Signed-off-by: Gil Bregman (cherry picked from commit 0063491fa2d71419a67e184bd04974c7833eb9f0) Signed-off-by: Gil Bregman --- diff --git a/src/pybind/mgr/dashboard/controllers/nvmeof.py b/src/pybind/mgr/dashboard/controllers/nvmeof.py index c75ccd01f9df..727e1adf5c68 100644 --- a/src/pybind/mgr/dashboard/controllers/nvmeof.py +++ b/src/pybind/mgr/dashboard/controllers/nvmeof.py @@ -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 ) ) diff --git a/src/pybind/mgr/dashboard/model/nvmeof.py b/src/pybind/mgr/dashboard/model/nvmeof.py index e0f12a39e565..1dcd6babf2c6 100644 --- a/src/pybind/mgr/dashboard/model/nvmeof.py +++ b/src/pybind/mgr/dashboard/model/nvmeof.py @@ -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): diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 3076c0ca8836..e86feb03541c 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -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