From: Shweta Bhosale Date: Tue, 11 Nov 2025 07:47:31 +0000 (+0530) Subject: mgr/nfs: Cephadm support for NFS-Ganesha TLS configuration adding new option xprtsec X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F66194%2Fhead;p=ceph.git mgr/nfs: Cephadm support for NFS-Ganesha TLS configuration adding new option xprtsec Fixes: https://tracker.ceph.com/issues/73774 Signed-off-by: Shweta Bhosale --- diff --git a/doc/mgr/nfs.rst b/doc/mgr/nfs.rst index acbc6395c692..ae3626f90548 100644 --- a/doc/mgr/nfs.rst +++ b/doc/mgr/nfs.rst @@ -290,7 +290,7 @@ Create CephFS Export .. prompt:: bash # - ceph nfs export create cephfs --cluster-id --pseudo-path --fsname [--readonly] [--path=/path/in/cephfs] [--client_addr ...] [--squash ] [--sectype ...] [--cmount_path ] + ceph nfs export create cephfs --cluster-id --pseudo-path --fsname [--readonly] [--path=/path/in/cephfs] [--client_addr ...] [--squash ] [--sectype ...] [--cmount_path ] [--xprtsec ] This creates export RADOS objects containing the export block, where @@ -318,13 +318,16 @@ value is ``no_root_squash``. See the `NFS-Ganesha Export Sample`_ for permissible values. ```` specifies which authentication methods will be used when -connecting to the export. Valid values include "krb5p", "krb5i", "krb5", "sys", "tls", "mtls" -and "none". More than one value can be supplied. The flag may be specified +connecting to the export. Valid values include ``krb5p``, ``krb5i``, ``krb5``, ``sys`` +and ``none``. More than one value can be supplied. The flag may be specified multiple times (example: ``--sectype=krb5p --sectype=krb5i``) or multiple values may be separated by a comma (example: ``--sectype krb5p,krb5i``). The server will negotatiate a supported security type with the client preferring the supplied methods left-to-right. +```` defines how traffic is secured at the transport layer. +Valid values are ``tls``, ``mtls`` and ``none``. + ```` specifies the path within the CephFS to mount this export on. It is allowed to be any complete path hierarchy between ``/`` and the ``EXPORT {path}``. (i.e. if ``EXPORT { Path }`` parameter is ``/foo/bar`` then cmount_path could be ``/``, ``/foo`` or ``/foo/bar``). @@ -355,7 +358,7 @@ To export a *bucket*: .. prompt:: bash # - ceph nfs export create rgw --cluster-id --pseudo-path --bucket [--user-id ] [--readonly] [--client_addr ...] [--squash ] [--sectype ...] + ceph nfs export create rgw --cluster-id --pseudo-path --bucket [--user-id ] [--readonly] [--client_addr ...] [--squash ] [--sectype ...] [--xprtsec ] For example, to export ``mybucket`` via NFS cluster ``mynfs`` at the pseudo-path ``/bucketdata`` to any host in the ``192.168.10.0/24`` network @@ -392,8 +395,8 @@ default value is ``no_root_squash``. See the `NFS-Ganesha Export Sample`_ for permissible values. ```` specifies which authentication methods will be used when -connecting to the export. Valid values include "krb5p", "krb5i", "krb5", -"sys", and "none". More than one value can be supplied. The flag may be +connecting to the export. Valid values include ``krb5p``, ``krb5i``, ``krb5``, +``sys`` and ``none``. More than one value can be supplied. The flag may be specified multiple times (example: ``--sectype=krb5p --sectype=krb5i``) or multiple values may be separated by a comma (example: ``--sectype krb5p,krb5i``). The server will negotatiate a supported security type with the @@ -403,6 +406,10 @@ client preferring the supplied methods left-to-right. function on servers that are configured to support Kerberos. Setting up NFS-Ganesha to support Kerberos is outside the scope of this document. +```` defines how traffic is secured at the transport layer. +Valid values are ``tls``, ``mtls`` and ``none``. + + RGW user export ^^^^^^^^^^^^^^^ diff --git a/src/pybind/mgr/nfs/export.py b/src/pybind/mgr/nfs/export.py index aff6779bb16e..172012ed62e4 100644 --- a/src/pybind/mgr/nfs/export.py +++ b/src/pybind/mgr/nfs/export.py @@ -740,6 +740,7 @@ class ExportMgr: access_type: str, clients: list = [], sectype: Optional[List[str]] = None, + xprtsec: Optional[str] = None, cmount_path: Optional[str] = "/", earmark_resolver: Optional[CephFSEarmarkResolver] = None ) -> Dict[str, Any]: @@ -766,6 +767,7 @@ class ExportMgr: }, "clients": clients, "sectype": sectype, + "XprtSec": xprtsec, }, earmark_resolver ) @@ -791,7 +793,8 @@ class ExportMgr: bucket: Optional[str] = None, user_id: Optional[str] = None, clients: list = [], - sectype: Optional[List[str]] = None) -> Dict[str, Any]: + sectype: Optional[List[str]] = None, + xprtsec: Optional[str] = None) -> Dict[str, Any]: pseudo_path = normalize_path(pseudo_path) if not bucket and not user_id: @@ -812,6 +815,7 @@ class ExportMgr: }, "clients": clients, "sectype": sectype, + "XprtSec": xprtsec, } ) log.debug("creating rgw export %s", export) diff --git a/src/pybind/mgr/nfs/ganesha_conf.py b/src/pybind/mgr/nfs/ganesha_conf.py index b612f278d5ca..c4072a01d331 100644 --- a/src/pybind/mgr/nfs/ganesha_conf.py +++ b/src/pybind/mgr/nfs/ganesha_conf.py @@ -47,12 +47,19 @@ def _validate_access_type(access_type: str) -> None: def _validate_sec_type(sec_type: str) -> None: - valid_sec_types = ["none", "sys", "krb5", "krb5i", "krb5p", "tls", "mtls"] + valid_sec_types = ["none", "sys", "krb5", "krb5i", "krb5p"] if not isinstance(sec_type, str) or sec_type not in valid_sec_types: raise NFSInvalidOperation( f"SecType {sec_type} invalid, valid types are {valid_sec_types}") +def _validate_xprtsec_type(xprtsec: str) -> None: + valid_xprtsec_types = ['none', 'tls', 'mtls'] + if not isinstance(xprtsec, str) or xprtsec not in valid_xprtsec_types: + raise NFSInvalidOperation( + f"XprtSec {xprtsec} invalid, valid types are {valid_xprtsec_types}") + + class RawBlock(): def __init__(self, block_name: str, blocks: List['RawBlock'] = [], values: Dict[str, Any] = {}): if not values: # workaround mutable default argument @@ -375,7 +382,8 @@ class Export: transports: List[str], fsal: FSAL, clients: Optional[List[Client]] = None, - sectype: Optional[List[str]] = None) -> None: + sectype: Optional[List[str]] = None, + xprtsec: Optional[str] = None) -> None: self.export_id = export_id self.path = path self.fsal = fsal @@ -389,6 +397,7 @@ class Export: self.transports = transports self.clients: List[Client] = clients or [] self.sectype = sectype + self.xprtsec = xprtsec @classmethod def from_export_block(cls, export_block: RawBlock, cluster_id: str) -> 'Export': @@ -418,6 +427,9 @@ class Export: # https://github.com/ceph/go-ceph/issues/1097 if sectype is not None and not isinstance(sectype, list): sectype = [sectype] + + xprtsec = export_block.values.get('XprtSec') + return cls(export_block.values['export_id'], export_block.values['path'], cluster_id, @@ -430,7 +442,8 @@ class Export: FSAL.from_fsal_block(fsal_blocks[0]), [Client.from_client_block(client) for client in client_blocks], - sectype=sectype) + sectype=sectype, + xprtsec=xprtsec) def to_export_block(self) -> RawBlock: values = { @@ -446,6 +459,8 @@ class Export: } if self.sectype: values['SecType'] = self.sectype + if self.xprtsec: + values['XprtSec'] = self.xprtsec result = RawBlock("EXPORT", values=values) result.blocks = [ self.fsal.to_fsal_block() @@ -468,7 +483,8 @@ class Export: ex_dict.get('transports', ['TCP']), FSAL.from_dict(ex_dict.get('fsal', {})), [Client.from_dict(client) for client in ex_dict.get('clients', [])], - sectype=ex_dict.get("sectype")) + sectype=ex_dict.get("sectype"), + xprtsec=ex_dict.get('XprtSec')) def to_dict(self) -> Dict[str, Any]: values = { @@ -486,6 +502,8 @@ class Export: } if self.sectype: values['sectype'] = self.sectype + if self.xprtsec: + values['XprtSec'] = self.xprtsec return values def validate(self, mgr: 'Module') -> None: @@ -528,6 +546,8 @@ class Export: for st in (self.sectype or []): _validate_sec_type(st) + if self.xprtsec: + _validate_xprtsec_type(self.xprtsec) def __eq__(self, other: Any) -> bool: if not isinstance(other, Export): diff --git a/src/pybind/mgr/nfs/module.py b/src/pybind/mgr/nfs/module.py index 68557d255568..ef9860dfffee 100644 --- a/src/pybind/mgr/nfs/module.py +++ b/src/pybind/mgr/nfs/module.py @@ -43,6 +43,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): client_addr: Optional[List[str]] = None, squash: str = 'none', sectype: Optional[List[str]] = None, + xprtsec: Optional[str] = None, cmount_path: Optional[str] = "/" ) -> Dict[str, Any]: """Create a CephFS export""" @@ -57,6 +58,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): squash=squash, addr=client_addr, sectype=sectype, + xprtsec=xprtsec, cmount_path=cmount_path, earmark_resolver=earmark_resolver ) @@ -73,6 +75,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): client_addr: Optional[List[str]] = None, squash: str = 'none', sectype: Optional[List[str]] = None, + xprtsec: Optional[str] = None, ) -> Dict[str, Any]: """Create an RGW export""" return self.export_mgr.create_export( @@ -85,6 +88,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): squash=squash, addr=client_addr, sectype=sectype, + xprtsec=xprtsec ) @NFSCLICommand('nfs export rm', perm='rw') diff --git a/src/pybind/mgr/nfs/tests/test_nfs.py b/src/pybind/mgr/nfs/tests/test_nfs.py index ab8e3c528f01..edcbc49df94b 100644 --- a/src/pybind/mgr/nfs/tests/test_nfs.py +++ b/src/pybind/mgr/nfs/tests/test_nfs.py @@ -787,6 +787,7 @@ NFS_CORE_PARAM { assert info["export_id"] == 2 assert info["path"] == "bucket" assert "sectype" not in info + assert 'XprtSec' not in info r = conf.apply_export(self.cluster_id, json.dumps({ 'export_id': 2, @@ -803,7 +804,8 @@ NFS_CORE_PARAM { 'access_type': None, 'squash': None }], - 'sectype': ["krb5p", "krb5i", "sys", "mtls", "tls"], + 'sectype': ["krb5p", "krb5i", "sys"], + 'XprtSec': 'tls', 'fsal': { 'name': 'RGW', 'user_id': 'nfs.foo.bucket', @@ -817,7 +819,8 @@ NFS_CORE_PARAM { info = conf._get_export_dict(self.cluster_id, "/rgw/bucket") assert info["export_id"] == 2 assert info["path"] == "bucket" - assert info["sectype"] == ["krb5p", "krb5i", "sys", "mtls", "tls"] + assert info["sectype"] == ["krb5p", "krb5i", "sys"] + assert info['XprtSec'] == 'tls' def test_update_export_with_ganesha_conf(self): self._do_mock_test(self._do_test_update_export_with_ganesha_conf) @@ -1004,7 +1007,8 @@ NFS_CORE_PARAM { pseudo_path='/mybucket', read_only=False, squash='root', - addr=["192.168.0.0/16"] + addr=["192.168.0.0/16"], + xprtsec='tls' ) assert r["bind"] == "/mybucket" @@ -1028,6 +1032,7 @@ NFS_CORE_PARAM { assert export.clients[0].access_type == 'rw' assert export.clients[0].addresses == ["192.168.0.0/16"] assert export.cluster_id == self.cluster_id + assert export.xprtsec == 'tls' def test_create_export_rgw_bucket_user(self): self._do_mock_test(self._do_test_create_export_rgw_bucket_user)