]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: add SSL support to ceph-exporter
authorGuillaume Abrioux <gabrioux@ibm.com>
Wed, 10 Jul 2024 13:50:59 +0000 (15:50 +0200)
committerRedouane Kachach <rkachach@ibm.com>
Wed, 31 Jul 2024 17:37:18 +0000 (19:37 +0200)
This commit adds SSL support to the ceph-exporter deployment
made by cephadm. When `secure_monitoring_stack` is set to `True`,
the `ceph-exporter` container is restarted with SSL enabled.

Signed-off-by: Guillaume Abrioux <gabrioux@ibm.com>
Signed-off-by: Redouane Kachach <rkachach@ibm.com>
src/cephadm/cephadm.py
src/cephadm/cephadmlib/daemons/ceph.py
src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/serve.py
src/pybind/mgr/cephadm/services/cephadmservice.py
src/pybind/mgr/cephadm/tests/test_services.py

index 75ac3045c1e3115555eafbda7623dd44d03ef2a6..5c0762f8bf3316b6e8620208ade0debf6ce4ebca 100755 (executable)
@@ -167,6 +167,7 @@ from cephadmlib import templating
 from cephadmlib.daemons.ceph import get_ceph_mounts_for_type, ceph_daemons
 from cephadmlib.daemons import (
     Ceph,
+    CephExporter,
     CephIscsi,
     CephNvmeof,
     CustomContainer,
@@ -867,6 +868,10 @@ def create_daemon_dirs(
         node_proxy = NodeProxy.init(ctx, fsid, ident.daemon_id)
         node_proxy.create_daemon_dirs(data_dir, uid, gid)
 
+    elif daemon_type == CephExporter.daemon_type:
+        ceph_exporter = CephExporter.init(ctx, fsid, ident.daemon_id)
+        ceph_exporter.create_daemon_dirs(data_dir, uid, gid)
+
     else:
         daemon = daemon_form_create(ctx, ident)
         if isinstance(daemon, ContainerDaemonForm):
index d5e87ad94847b5e84df9c5512c7c1128f471d5e0..efb013c7e09c11cbebe6f149346d58067433500f 100644 (file)
@@ -16,7 +16,14 @@ from ..constants import DEFAULT_IMAGE
 from ..context import CephadmContext
 from ..deployment_utils import to_deployment_container
 from ..exceptions import Error
-from ..file_utils import make_run_dir, pathify
+from ..file_utils import (
+    make_run_dir,
+    pathify,
+    populate_files,
+    makedirs,
+    recursive_chown,
+)
+from ..data_utils import dict_get
 from ..host_facts import HostFacts
 from ..logging import Highlight
 from ..net_utils import get_hostname, get_ip_addresses
@@ -298,6 +305,8 @@ class CephExporter(ContainerDaemonForm):
         self.port = config_json.get('port', self.DEFAULT_PORT)
         self.prio_limit = config_json.get('prio-limit', 5)
         self.stats_period = config_json.get('stats-period', 5)
+        self.https_enabled: bool = config_json.get('https_enabled', False)
+        self.files = dict_get(config_json, 'files', {})
 
     @classmethod
     def init(
@@ -323,6 +332,15 @@ class CephExporter(ContainerDaemonForm):
             f'--prio-limit={self.prio_limit}',
             f'--stats-period={self.stats_period}',
         ]
+        if self.https_enabled:
+            args.extend(
+                [
+                    '--cert-file',
+                    '/etc/certs/ceph-exporter.crt',
+                    '--key-file',
+                    '/etc/certs/ceph-exporter.key',
+                ]
+            )
         return args
 
     def validate(self) -> None:
@@ -348,6 +366,9 @@ class CephExporter(ContainerDaemonForm):
     ) -> None:
         cm = Ceph.get_ceph_mounts(ctx, self.identity)
         mounts.update(cm)
+        if self.https_enabled:
+            data_dir = self.identity.data_dir(ctx.data_dir)
+            mounts.update({os.path.join(data_dir, 'etc/certs'): '/etc/certs'})
 
     def customize_process_args(
         self, ctx: CephadmContext, args: List[str]
@@ -376,6 +397,23 @@ class CephExporter(ContainerDaemonForm):
         # it until now
         self.validate()
 
+    def create_daemon_dirs(self, data_dir: str, uid: int, gid: int) -> None:
+        """Create files under the container data dir"""
+        if not os.path.isdir(data_dir):
+            raise OSError('data_dir is not a directory: %s' % (data_dir))
+        logger.info('Writing ceph-exporter config...')
+        config_dir = os.path.join(data_dir, 'etc/')
+        ssl_dir = os.path.join(data_dir, 'etc/certs')
+        for ddir in [config_dir, ssl_dir]:
+            makedirs(ddir, uid, gid, 0o755)
+            recursive_chown(ddir, uid, gid)
+        cert_files = {
+            fname: content
+            for fname, content in self.files.items()
+            if fname.endswith('.crt') or fname.endswith('.key')
+        }
+        populate_files(ssl_dir, cert_files, uid, gid)
+
 
 def get_ceph_mounts_for_type(
     ctx: CephadmContext, fsid: str, daemon_type: str
index 51d677ec6cd6d36fcb8ca5cc948ad3713763272c..392c62ae9acd99e1bc9c04f643c1c278467d5187 100644 (file)
@@ -2992,7 +2992,8 @@ Then run the following:
             # this daemon type doesn't need deps mgmt
             pass
 
-        if daemon_type in ['prometheus', 'node-exporter', 'alertmanager', 'grafana']:
+        if daemon_type in ['prometheus', 'node-exporter', 'alertmanager', 'grafana',
+                           'ceph-exporter']:
             deps.append(f'secure_monitoring_stack:{self.secure_monitoring_stack}')
 
         return sorted(deps)
index 440cc81ec44b9af94e803138a628ef32986e67ca..dffd80b979b1eff8fdedb6dc0955cd92b675f47f 100644 (file)
@@ -1095,7 +1095,7 @@ class CephadmServe:
                 action = 'reconfig'
                 # we need only redeploy if secure_monitoring_stack or mgmt-gateway value has changed:
                 # TODO(redo): check if we should just go always with redeploy (it's fast enough)
-                if dd.daemon_type in ['prometheus', 'node-exporter', 'alertmanager']:
+                if dd.daemon_type in ['prometheus', 'node-exporter', 'alertmanager', 'ceph-exporter']:
                     diff = list(set(last_deps).symmetric_difference(set(deps)))
                     REDEPLOY_TRIGGERS = ['secure_monitoring_stack', 'mgmt-gateway']
                     if any(svc in e for e in diff for svc in REDEPLOY_TRIGGERS):
index 2964a44e2c3b188aa5d854f7b3ca1471dde25709..d4b9ea262bb07b14e71edd795a4f4c84712ec55a 100644 (file)
@@ -1263,7 +1263,7 @@ class CephExporterService(CephService):
                                               'mon', 'allow r',
                                               'mgr', 'allow r',
                                               'osd', 'allow r'])
-        exporter_config = {}
+        exporter_config: Dict[str, Any] = {}
         if spec.sock_dir:
             exporter_config.update({'sock-dir': spec.sock_dir})
         if spec.port:
@@ -1297,6 +1297,7 @@ class CephExporterService(CephService):
         host_fqdn = self.mgr.get_fqdn(daemon_spec.host)
         return self.mgr.cert_mgr.generate_cert(host_fqdn, node_ip)
 
+
 class CephfsMirrorService(CephService):
     TYPE = 'cephfs-mirror'
 
index 93768ff1f8fda828770e339ad698eeb0c62ba4b7..179f11760595824e20c1fc919372b298e4d1b642 100644 (file)
@@ -6,7 +6,7 @@ from mgr_util import build_url
 
 import pytest
 
-from unittest.mock import MagicMock, call, patch, ANY
+from unittest.mock import Mock, MagicMock, call, patch, ANY
 
 from cephadm.serve import CephadmServe
 from cephadm.services.cephadmservice import MonService, MgrService, MdsService, RgwService, \
@@ -693,6 +693,50 @@ class TestMonitoring:
                     use_current_daemon_image=False,
                 )
 
+    @patch("cephadm.serve.CephadmServe._run_cephadm")
+    @patch("socket.getfqdn")
+    @patch("cephadm.module.CephadmOrchestrator.get_mgr_ip", lambda _: '::1')
+    @patch('cephadm.cert_mgr.CertMgr.get_root_ca', lambda instance: 'cephadm_root_cert')
+    @patch('cephadm.cert_mgr.CertMgr.generate_cert', lambda instance, fqdn, ip: ('mycert', 'mykey'))
+    @patch('cephadm.services.cephadmservice.CephExporterService.get_keyring_with_caps', Mock(return_value='[client.ceph-exporter.test]\nkey = fake-secret\n'))
+    def test_ceph_exporter_config_security_enabled(self, _get_fqdn, _run_cephadm, cephadm_module: CephadmOrchestrator):
+        _run_cephadm.side_effect = async_side_effect(('{}', '', 0))
+
+        fqdn = 'host1.test'
+        _get_fqdn.return_value = fqdn
+
+        with with_host(cephadm_module, 'test'):
+            cephadm_module.secure_monitoring_stack = True
+            with with_service(cephadm_module, CephExporterSpec()):
+                _run_cephadm.assert_called_with('test', 'ceph-exporter.test',
+                                                ['_orch', 'deploy'], [],
+                                                stdin=json.dumps({
+                                                    "fsid": "fsid",
+                                                    "name": "ceph-exporter.test",
+                                                    "image": "",
+                                                    "deploy_arguments": [],
+                                                    "params": {},
+                                                    "meta": {
+                                                        "service_name": "ceph-exporter",
+                                                        "ports": [],
+                                                        "ip": None,
+                                                        "deployed_by": [],
+                                                        "rank": None,
+                                                        "rank_generation": None,
+                                                        "extra_container_args": None,
+                                                        "extra_entrypoint_args": None
+                                                    },
+                                                    "config_blobs": {
+                                                        "config": "",
+                                                        "keyring": "[client.ceph-exporter.test]\nkey = fake-secret\n",
+                                                        "prio-limit": "5",
+                                                        "stats-period": "5",
+                                                        "https_enabled": True,
+                                                        "files": {
+                                                            "ceph-exporter.crt": "mycert",
+                                                            "ceph-exporter.key": "mykey"}}}),
+                                                use_current_daemon_image=False)
+
     @patch("cephadm.serve.CephadmServe._run_cephadm")
     @patch("cephadm.module.CephadmOrchestrator.get_mgr_ip", lambda _: '::1')
     def test_prometheus_config_security_disabled(self, _run_cephadm, cephadm_module: CephadmOrchestrator):