]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/nfs: Cephadm support for NFS-Ganesha TLS configuration adding new option xprtsec 66194/head
authorShweta Bhosale <Shweta.Bhosale1@ibm.com>
Tue, 11 Nov 2025 07:47:31 +0000 (13:17 +0530)
committerShweta Bhosale <Shweta.Bhosale1@ibm.com>
Mon, 20 Apr 2026 08:33:25 +0000 (14:03 +0530)
Fixes: https://tracker.ceph.com/issues/73774
Signed-off-by: Shweta Bhosale <Shweta.Bhosale1@ibm.com>
doc/mgr/nfs.rst
src/pybind/mgr/nfs/export.py
src/pybind/mgr/nfs/ganesha_conf.py
src/pybind/mgr/nfs/module.py
src/pybind/mgr/nfs/tests/test_nfs.py

index acbc6395c6923f3096ae0727ed74e1d1faf33475..ae3626f90548bece7a4baa88f122842db5fb4879 100644 (file)
@@ -290,7 +290,7 @@ Create CephFS Export
 
 .. prompt:: bash #
 
-   ceph nfs export create cephfs --cluster-id <cluster_id> --pseudo-path <pseudo_path> --fsname <fsname> [--readonly] [--path=/path/in/cephfs] [--client_addr <value>...] [--squash <value>] [--sectype <value>...] [--cmount_path <value>]
+   ceph nfs export create cephfs --cluster-id <cluster_id> --pseudo-path <pseudo_path> --fsname <fsname> [--readonly] [--path=/path/in/cephfs] [--client_addr <value>...] [--squash <value>] [--sectype <value>...] [--cmount_path <value>] [--xprtsec <value>]
 
 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.
 
 ``<sectype>`` 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.
 
+``<xprtsec>`` defines how traffic is secured at the transport layer.
+Valid values are ``tls``, ``mtls`` and ``none``.
+
 ``<cmount_path>`` 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 <cluster_id> --pseudo-path <pseudo_path> --bucket <bucket_name> [--user-id <user-id>] [--readonly] [--client_addr <value>...] [--squash <value>] [--sectype <value>...]
+   ceph nfs export create rgw --cluster-id <cluster_id> --pseudo-path <pseudo_path> --bucket <bucket_name> [--user-id <user-id>] [--readonly] [--client_addr <value>...] [--squash <value>] [--sectype <value>...] [--xprtsec <value>]
 
 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.
 
 ``<sectype>`` 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.
 
+``<xprtsec>`` defines how traffic is secured at the transport layer.
+Valid values are ``tls``, ``mtls`` and ``none``.
+
+
 RGW user export
 ^^^^^^^^^^^^^^^
 
index aff6779bb16e4c11e502c31837723aff609192cc..172012ed62e406017e99f7592756bd5a67106692 100644 (file)
@@ -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)
index b612f278d5ca59c8b762bacc28edddb3f6b7d189..c4072a01d331d603d5d2c9fb0125e4696a2a30de 100644 (file)
@@ -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):
index 68557d255568c8b34544c3880c6c5255e49befde..ef9860dfffeeb67559c1f7ab321f101bde683399 100644 (file)
@@ -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')
index ab8e3c528f01eafb8130f489e06100137808b0fc..edcbc49df94b4424ed874dc7c057fd42d3c1f28d 100644 (file)
@@ -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)