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=0063491fa2d71419a67e184bd04974c7833eb9f0;p=ceph.git mgr/dashboard: Add namespace encryption support to NVMeoF CLI Fixes: https://tracker.ceph.com/issues/74965 Signed-off-by: Gil Bregman --- diff --git a/src/pybind/mgr/dashboard/controllers/nvmeof.py b/src/pybind/mgr/dashboard/controllers/nvmeof.py index 9ed4faf5f411..839484138dbf 100644 --- a/src/pybind/mgr/dashboard/controllers/nvmeof.py +++ b/src/pybind/mgr/dashboard/controllers/nvmeof.py @@ -492,6 +492,99 @@ else: ) ) + @empty_response + @NvmeofCLICommand( + "nvmeof subsystem add_kmip_server_endpoint", model.RequestStatus, + success_message_template=("Adding an endpoint, with address {address}:{port}, " + "to KMIP server {server_name} on subsystem {nqn}: " + "Successful") + ) + @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), + "server_address": 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, + server_address: Optional[str] = None): + ep = NVMeoFClient.pb2.kmip_server_endpoint(address=address, port=port) + return NVMeoFClient( + gw_group=gw_group, + server_address=server_address + ).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, + success_message_template=("Deleting endpoint, with address {address}:{port}, from " + "KMIP server {server_name} on subsystem {nqn}: Successful") + ) + @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), + "server_address": 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, + server_address: Optional[str] = None): + ep = NVMeoFClient.pb2.kmip_server_endpoint(address=address, port=port) + return NVMeoFClient( + gw_group=gw_group, + server_address=server_address + ).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), + "server_address": 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, server_address: Optional[str] = None): + return NVMeoFClient( + gw_group=gw_group, + server_address=server_address + ).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 @@ -742,6 +835,17 @@ else: "disable_auto_resize": Param(str, "Disable auto resize", True, None), "read_only": Param(str, "Read only namespace", True, None), "location": Param(str, "Gateway location for namespace", True, None), + "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), "gw_group": Param(str, "NVMeoF gateway group", True, None), "server_address": Param(str, "NVMeoF gateway address", True, None), }, @@ -768,7 +872,24 @@ else: gw_group: Optional[str] = None, server_address: 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, ): + 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)] return NVMeoFClient( gw_group=gw_group, server_address=server_address @@ -788,7 +909,9 @@ else: no_auto_visible=no_auto_visible, disable_auto_resize=disable_auto_resize, read_only=read_only, - location=location + location=location, + encryption_entries=enc_entries, + encryption_algorithm=encryption_algorithm ) ) @@ -822,6 +945,17 @@ else: 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), "gw_group": Param(str, "NVMeoF gateway group", True, None), "server_address": Param(str, "Target gateway address", True, None), }, @@ -848,7 +982,9 @@ else: gw_group: Optional[str] = None, server_address: 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( @@ -863,6 +999,22 @@ 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') + 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)] return NVMeoFClient( gw_group=gw_group, server_address=server_address @@ -882,7 +1034,9 @@ else: no_auto_visible=no_auto_visible, disable_auto_resize=disable_auto_resize, read_only=read_only, - location=location + location=location, + encryption_entries=enc_entries, + encryption_algorithm=encryption_algorithm ) ) diff --git a/src/pybind/mgr/dashboard/model/nvmeof.py b/src/pybind/mgr/dashboard/model/nvmeof.py index 90b9559c2e85..47125973c50b 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 @@ -157,6 +170,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")] @@ -178,6 +196,8 @@ class Namespace(NamedTuple): 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 5f6024ca9de3..2e8f79968169 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -13971,6 +13971,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 @@ -13979,6 +13988,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