Skip to content

Commit d74317a

Browse files
authored
Merge pull request ceph#65644 from ShwetaBhosale1/fix_issue_73035_nfs_ganesha_tls_support
mgr/cephadm: Cephadm support for NFS-Ganesha TLS configuration Reviewed-by: Adam King <[email protected]> Reviewed-by: Anthony D'Atri <[email protected]> Reviewed-by: Redouane Kachach <[email protected]>
2 parents 8f7ad11 + 46167e7 commit d74317a

File tree

22 files changed

+570
-183
lines changed

22 files changed

+570
-183
lines changed

doc/cephadm/services/nfs.rst

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,85 @@ address is not present and ``monitoring_networks`` is specified, an IP address
7979
that matches one of the specified networks will be used. If neither condition
8080
is met, the default binding will happen on all available network interfaces.
8181

82+
TLS/SSL Example
83+
---------------
84+
85+
Here's an example NFS service specification with TLS/SSL configuration:
86+
87+
.. code-block:: yaml
88+
89+
service_type: nfs
90+
service_id: mynfs
91+
placement:
92+
hosts:
93+
- ceph-node-0
94+
spec:
95+
port: 12345
96+
ssl: true
97+
certificate_source: inline|reference|cephadm-signed
98+
ssl_cert: |
99+
-----BEGIN CERTIFICATE-----
100+
(PEM cert contents here)
101+
-----END CERTIFICATE-----
102+
ssl_key: |
103+
-----BEGIN PRIVATE KEY-----
104+
(PEM key contents here)
105+
-----END PRIVATE KEY-----
106+
ssl_ca_cert:
107+
-----BEGIN PRIVATE KEY-----
108+
(PEM key contents here)
109+
-----END PRIVATE KEY-----
110+
tls_ktls: true
111+
tls_debug: true
112+
tls_min_version: TLSv1.3
113+
tls_ciphers: AES-256
114+
115+
This example configures an NFS service with TLS encryption enabled using
116+
inline certificates.
117+
118+
TLS/SSL Parameters
119+
~~~~~~~~~~~~~~~~~~
120+
121+
The following parameters can be used to configure TLS/SSL encryption for the NFS service:
122+
123+
* ``ssl`` (boolean): Enable or disable SSL/TLS encryption. Default is ``false``.
124+
125+
* ``certificate_source`` (string): Specifies the source of the TLS certificates.
126+
Options include:
127+
128+
- ``cephadm-signed``: Use certificates signed by cephadm's internal CA
129+
- ``inline``: Provide certificates directly in the specification using ``ssl_cert``, ``ssl_key``, and ``ssl_ca_cert`` fields
130+
- ``reference``: Users can register their own certificate and key with certmgr and
131+
set the ``certificate_source`` to ``reference`` in the spec.
132+
133+
* ``ssl_cert`` (string): The SSL certificate in PEM format. Required when using
134+
``inline`` certificate source.
135+
136+
* ``ssl_key`` (string): The SSL private key in PEM format. Required when using
137+
``inline`` certificate source.
138+
139+
* ``ssl_ca_cert`` (string): The SSL CA certificate in PEM format. Required when
140+
using ``inline`` certificate source.
141+
142+
* ``custom_sans`` (list): List of custom Subject Alternative Names (SANs) to
143+
include in the certificate.
144+
145+
* ``tls_ktls`` (boolean): Enable kernel TLS (kTLS) for improved performance when
146+
available. Default is ``false``.
147+
148+
* ``tls_debug`` (boolean): Enable TLS debugging output. Useful for troubleshooting
149+
TLS issues. Default is ``false``.
150+
151+
* ``tls_min_version`` (string): Specify the minimum TLS version to accept.
152+
Examples: TLSv1.3, TLSv1.2
153+
154+
* ``tls_ciphers`` (string): Specify allowed cipher suites for TLS connections.
155+
Example: :-CIPHER-ALL:+AES-256-GCM
156+
157+
.. note:: When ``ssl`` is enabled, a ``certificate_source`` must be specified.
158+
If using ``inline`` certificates, all three certificate fields (``ssl_cert``,
159+
``ssl_key``, ``ssl_ca_cert``) must be provided.
160+
82161
The specification can then be applied by running the following command:
83162

84163
.. prompt:: bash #

doc/mgr/nfs.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ In order to modify cluster parameters (for example, the port or the placement),
182182
use the orchestrator interface to update the NFS service spec. The safest way
183183
to do that is to export the current spec, modify it, and then re-apply it. For
184184
example, to modify the ``nfs.foo`` service, run commands of the following
185-
forms:
185+
forms:
186186

187187
.. prompt:: bash #
188188

@@ -318,7 +318,7 @@ value is ``no_root_squash``. See the `NFS-Ganesha Export Sample`_ for
318318
permissible values.
319319

320320
``<sectype>`` specifies which authentication methods will be used when
321-
connecting to the export. Valid values include "krb5p", "krb5i", "krb5", "sys",
321+
connecting to the export. Valid values include "krb5p", "krb5i", "krb5", "sys", "tls", "mtls"
322322
and "none". More than one value can be supplied. The flag may be specified
323323
multiple times (example: ``--sectype=krb5p --sectype=krb5i``) or multiple
324324
values may be separated by a comma (example: ``--sectype krb5p,krb5i``). The
@@ -350,7 +350,7 @@ There are two kinds of RGW exports:
350350

351351
RGW bucket export
352352
^^^^^^^^^^^^^^^^^
353-
353+
354354
To export a *bucket*:
355355

356356
.. prompt:: bash #

src/cephadm/cephadmlib/daemons/nfs.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,23 @@ def create_daemon_dirs(self, data_dir, uid, gid):
177177

178178
# create the ganesha conf dir
179179
config_dir = os.path.join(data_dir, 'etc/ganesha')
180+
tls_dir = os.path.join(data_dir, 'etc/ganesha/tls')
180181
makedirs(config_dir, uid, gid, 0o755)
181-
182+
makedirs(tls_dir, uid, gid, 0o755)
183+
184+
config_files = {
185+
fname: content
186+
for fname, content in self.files.items()
187+
if fname in ['ganesha.conf', 'idmap.conf']
188+
}
189+
tls_files = {
190+
fname: content
191+
for fname, content in self.files.items()
192+
if fname.startswith('tls')
193+
}
182194
# populate files from the config-json
183-
populate_files(config_dir, self.files, uid, gid)
195+
populate_files(config_dir, config_files, uid, gid)
196+
populate_files(tls_dir, tls_files, uid, gid)
184197

185198
# write the RGW keyring
186199
if self.rgw:

src/pybind/mgr/cephadm/agent.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Server: # type: ignore
2424
import tempfile
2525
from cephadm.services.service_registry import service_registry
2626
from cephadm.services.cephadmservice import CephadmAgent
27-
from cephadm.tlsobject_types import CertKeyPair
27+
from cephadm.tlsobject_types import TLSCredentials
2828

2929
from urllib.error import HTTPError, URLError
3030
from typing import Any, Dict, List, Set, TYPE_CHECKING, Optional, MutableMapping, IO
@@ -77,13 +77,13 @@ def configure_tls(self, server: Server) -> None:
7777
verify_tls_files(self.cert_file.name, self.key_file.name)
7878
server.ssl_certificate, server.ssl_private_key = self.cert_file.name, self.key_file.name
7979

80-
def _get_agent_certificates(self) -> CertKeyPair:
80+
def _get_agent_certificates(self) -> TLSCredentials:
8181
host = self.mgr.get_hostname()
82-
tls_pair = self.mgr.cert_mgr.get_self_signed_cert_key_pair(CephadmAgent.TYPE, host)
83-
if not tls_pair:
84-
tls_pair = self.mgr.cert_mgr.generate_cert(host, self.mgr.get_mgr_ip(), duration_in_days=CEPHADM_AGENT_CERT_DURATION)
85-
self.mgr.cert_mgr.save_self_signed_cert_key_pair(CephadmAgent.TYPE, tls_pair, host=host)
86-
return tls_pair
82+
tls_creds = self.mgr.cert_mgr.get_self_signed_tls_credentials(CephadmAgent.TYPE, host)
83+
if not tls_creds:
84+
tls_creds = self.mgr.cert_mgr.generate_cert(host, self.mgr.get_mgr_ip(), duration_in_days=CEPHADM_AGENT_CERT_DURATION)
85+
self.mgr.cert_mgr.save_self_signed_cert_key_pair(CephadmAgent.TYPE, tls_creds, host=host)
86+
return tls_creds
8787

8888
def find_free_port(self) -> None:
8989
max_port = self.server_port + 150

src/pybind/mgr/cephadm/cert_mgr.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from cephadm.ssl_cert_utils import SSLCerts, SSLConfigException
77
from mgr_util import verify_tls, certificate_days_to_expire, ServerConfigException
88
from cephadm.ssl_cert_utils import get_certificate_info, get_private_key_info
9-
from cephadm.tlsobject_types import Cert, PrivKey, TLSObjectScope, TLSObjectException, CertKeyPair
9+
from cephadm.tlsobject_types import Cert, PrivKey, TLSObjectScope, TLSObjectException, TLSCredentials
1010
from cephadm.tlsobject_store import TLSObjectStore
1111

1212
if TYPE_CHECKING:
@@ -245,7 +245,14 @@ def register_self_signed_cert_key_pair(self, service_name: str, label: Optional[
245245
self.cert_store.register_object_name(self.self_signed_cert(service_name, label), TLSObjectScope.HOST)
246246
self.key_store.register_object_name(self.self_signed_key(service_name, label), TLSObjectScope.HOST)
247247

248-
def register_cert_key_pair(self, consumer: str, cert_name: str, key_name: str, scope: TLSObjectScope) -> None:
248+
def register_cert_key_pair(
249+
self,
250+
consumer: str,
251+
cert_name: str,
252+
key_name: str,
253+
scope: TLSObjectScope,
254+
ca_cert_name: Optional[str] = None
255+
) -> None:
249256
"""
250257
Registers a certificate/key for a given consumer under a specific scope.
251258
@@ -256,6 +263,8 @@ def register_cert_key_pair(self, consumer: str, cert_name: str, key_name: str, s
256263
"""
257264
self.register_cert(consumer, cert_name, scope)
258265
self.register_key(consumer, key_name, scope)
266+
if ca_cert_name:
267+
self.register_cert(consumer, ca_cert_name, scope)
259268

260269
def register_cert(self, consumer: str, cert_name: str, scope: TLSObjectScope) -> None:
261270
self._register_tls_object(consumer, cert_name, scope, "certs")
@@ -305,9 +314,10 @@ def generate_cert(
305314
node_ip: Union[str, List[str]],
306315
custom_san_list: Optional[List[str]] = None,
307316
duration_in_days: Optional[int] = None,
308-
) -> CertKeyPair:
317+
) -> TLSCredentials:
309318
cert, key = self.ssl_certs.generate_cert(host_fqdn, node_ip, custom_san_list=custom_san_list, duration_in_days=duration_in_days)
310-
return CertKeyPair(cert=cert, key=key)
319+
ca_cert = self.mgr.cert_mgr.get_root_ca()
320+
return TLSCredentials(cert=cert, key=key, ca_cert=ca_cert)
311321

312322
def cert_exists(self, cert_name: str, service_name: Optional[str] = None, host: Optional[str] = None) -> bool:
313323
cert_obj = self.cert_store.get_tlsobject(cert_name, service_name, host)
@@ -325,24 +335,25 @@ def get_key(self, key_name: str, service_name: Optional[str] = None, host: Optio
325335
key_obj = cast(PrivKey, self.key_store.get_tlsobject(key_name, service_name, host))
326336
return key_obj.key if key_obj else None
327337

328-
def get_self_signed_cert_key_pair(self, service_name: str, hostname: str, label: Optional[str] = None) -> CertKeyPair:
338+
def get_self_signed_tls_credentials(self, service_name: str, hostname: str, label: Optional[str] = None) -> TLSCredentials:
329339
cert_obj = cast(Cert, self.cert_store.get_tlsobject(self.self_signed_cert(service_name, label), host=hostname))
330340
key_obj = cast(PrivKey, self.key_store.get_tlsobject(self.self_signed_key(service_name, label), host=hostname))
331341
cert = cert_obj.cert if cert_obj else ''
332342
key = key_obj.key if key_obj else ''
333-
return CertKeyPair(cert=cert, key=key)
343+
ca_cert = self.mgr.cert_mgr.get_root_ca()
344+
return TLSCredentials(cert=cert, key=key, ca_cert=ca_cert)
334345

335346
def save_cert(self, cert_name: str, cert: str, service_name: Optional[str] = None, host: Optional[str] = None, user_made: bool = False, editable: bool = False) -> None:
336347
self.cert_store.save_tlsobject(cert_name, cert, service_name, host, user_made, editable)
337348

338349
def save_key(self, key_name: str, key: str, service_name: Optional[str] = None, host: Optional[str] = None, user_made: bool = False, editable: bool = False) -> None:
339350
self.key_store.save_tlsobject(key_name, key, service_name, host, user_made, editable)
340351

341-
def save_self_signed_cert_key_pair(self, service_name: str, tls_pair: CertKeyPair, host: str, label: Optional[str] = None) -> None:
352+
def save_self_signed_cert_key_pair(self, service_name: str, tls_creds: TLSCredentials, host: str, label: Optional[str] = None) -> None:
342353
ss_cert_name = self.self_signed_cert(service_name, label)
343354
ss_key_name = self.self_signed_key(service_name, label)
344-
self.cert_store.save_tlsobject(ss_cert_name, tls_pair.cert, host=host, user_made=False)
345-
self.key_store.save_tlsobject(ss_key_name, tls_pair.key, host=host, user_made=False)
355+
self.cert_store.save_tlsobject(ss_cert_name, tls_creds.cert, host=host, user_made=False)
356+
self.key_store.save_tlsobject(ss_key_name, tls_creds.key, host=host, user_made=False)
346357

347358
def rm_cert(self, cert_name: str, service_name: Optional[str] = None, host: Optional[str] = None) -> bool:
348359
return self.cert_store.rm_tlsobject(cert_name, service_name, host)

src/pybind/mgr/cephadm/module.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ def _init_cert_mgr(self) -> None:
735735
if svc.allows_user_certificates:
736736
if svc.SCOPE == TLSObjectScope.UNKNOWN:
737737
OrchestratorError(f"Service {svc.TYPE} requieres certificates but it has not defined its svc.SCOPE field.")
738-
self.cert_mgr.register_cert_key_pair(svc.TYPE, svc.cert_name, svc.key_name, svc.SCOPE)
738+
self.cert_mgr.register_cert_key_pair(svc.TYPE, svc.cert_name, svc.key_name, svc.SCOPE, svc.ca_cert_name)
739739

740740
self.cert_mgr.register_cert_key_pair('nvmeof', 'nvmeof_client_cert', 'nvmeof_client_key', TLSObjectScope.SERVICE)
741741
self.cert_mgr.register_cert('nvmeof', 'nvmeof_root_ca_cert', TLSObjectScope.SERVICE)
@@ -3282,8 +3282,8 @@ def generate_certificates(self, module_name: str) -> Optional[Dict[str, str]]:
32823282
if module_name == 'dashboard':
32833283
host_fqdns.append('dashboard_servers')
32843284

3285-
cert, key = self.cert_mgr.generate_cert(host_fqdns, self.get_mgr_ip())
3286-
return {'cert': cert, 'key': key}
3285+
tls_creds = self.cert_mgr.generate_cert(host_fqdns, self.get_mgr_ip())
3286+
return {'cert': tls_creds.cert, 'key': tls_creds.key}
32873287

32883288
@handle_orch_error
32893289
def set_prometheus_access_info(self, user: str, password: str) -> str:

src/pybind/mgr/cephadm/serve.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,11 @@ def _check_daemons(self) -> None:
11561156
# the daemon is written, which we rewrite on redeploy, but not
11571157
# on reconfig.
11581158
action = 'redeploy'
1159-
1159+
elif dd.daemon_type == 'nfs':
1160+
# check what has changed, based on that decide action
1161+
only_kmip_updated = all(s.startswith('kmip') for s in list(sym_diff))
1162+
if not only_kmip_updated:
1163+
action = 'redeploy'
11601164
elif spec is not None and hasattr(spec, 'extra_container_args') and dd.extra_container_args != spec.extra_container_args:
11611165
self.log.debug(
11621166
f'{dd.name()} container cli args {dd.extra_container_args} -> {spec.extra_container_args}')

0 commit comments

Comments
 (0)