]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/cephadm: allow passing client/server cert/key in nvmeof spec
authorAdam King <adking@redhat.com>
Thu, 23 May 2024 16:54:25 +0000 (12:54 -0400)
committerAlexander Indenbaum <aindenba@redhat.com>
Thu, 20 Nov 2025 08:55:27 +0000 (10:55 +0200)
Before this patch the client/server cert/key fields were
just filepaths that told the nvmeof gw daemon where to look
for the cert/key. There's not much reason why users would
care where in the nvmeof gw container the cert goes. It's more
useful to use these fields as a way to pass the certs/keys
to the daemon and then just hardcode where in the container
we'll place the certs/keys

Signed-off-by: Adam King <adking@redhat.com>
(cherry picked from commit e9fca39092348e6c08022341116875e831c175f0)
(cherry picked from commit e2e6aeb40acc98070e0e2c4a0056e42458e9f4f1)

Resolves: rhbz#2282825

Conflicts:
src/cephadm/cephadmlib/daemons/nvmeof.py

src/cephadm/cephadm.py
src/pybind/mgr/cephadm/services/nvmeof.py
src/pybind/mgr/cephadm/templates/services/nvmeof/ceph-nvmeof.conf.j2
src/python-common/ceph/deployment/service_spec.py

index 2dfc1b05f994da7b6c901df2a66244b9d4d83125..d7f5bed0b77570630a404feba55c305565c80d29 100755 (executable)
@@ -1210,6 +1210,17 @@ class CephNvmeof(object):
         return cls(ctx, fsid, daemon_id,
                    fetch_configs(ctx), ctx.image)
 
+    def _get_tls_cert_key_mounts(
+        self, data_dir: str, files: Dict[str, str]
+    ) -> Dict[str, str]:
+        mounts = dict()
+        for fn in ['server_cert', 'server_key', 'client_cert', 'client_key']:
+            if fn in files:
+                mounts[
+                    os.path.join(data_dir, fn)
+                ] = f'/{fn.replace("_", ".")}'
+        return mounts
+
     @staticmethod
     def get_container_mounts(data_dir: str, log_dir: str, mtls_dir: Optional[str] = None) -> Dict[str, str]:
         mounts = dict()
@@ -3701,6 +3712,8 @@ def get_container_mounts(ctx, fsid, daemon_type, daemon_id,
             mounts.update(CephNvmeof.get_container_mounts(data_dir, log_dir, mtls_dir=mtls_dir))
         else:
             mounts.update(CephNvmeof.get_container_mounts(data_dir, log_dir))
+        ceph_nvmeof = CephNvmeof.init(ctx, fsid, daemon_id)
+        mounts.update(ceph_nvmeof._get_tls_cert_key_mounts(data_dir, ceph_nvmeof.files))
 
     if daemon_type == CephIscsi.daemon_type:
         assert daemon_id
@@ -10065,6 +10078,99 @@ def command_gather_facts(ctx: CephadmContext) -> None:
 
 ##################################
 
+@infer_config
+def command_sos(ctx: CephadmContext):
+    """
+    Execute the sos command
+    returns the pattern of the sos report part files generated
+    in the LOG_DIR folder
+
+    To avoid fill the disk, each execution deletes all previous sos report files
+
+    The original compressed file can be rebuild using:
+    # cat /var/log/ceph/<cluster_fsid>/sosreport* > /tmp/sosreport_case_<xx>.tar.xz
+    """
+    def remove_files(folder: str, pattern: str) -> None:
+        file_path = ""
+        try:
+            for file_path in filter(os.path.isfile, glob(os.path.join(folder, pattern))):
+                os.remove(file_path)
+        except Exception as ex:
+            logger.error(f'Error removing file {file_path}: {ex}')
+
+    result = 1  # error by default, will be changed if everything ok
+    try:
+        # get silently the cluster fsid
+        cp = read_config(ctx.config)
+        if cp.has_option('global', 'fsid'):
+            fsid = cp.get('global', 'fsid')
+        else:
+            raise Exception('Cannot infer Ceph cluster fsid .SOS report command'
+                            ' aborted')
+
+        # sos report execution
+        cmd_sos = ['sos'] + ctx.parameters
+        out, err, code = call(ctx, cmd_sos, verbosity=CallVerbosity.DEBUG)
+        if (err or code):
+            if out:
+                # even with errors a sos file can be generated
+                # inform about the issue and continue trying to get the file
+                print(f'Issue executing <{cmd_sos}>: {code}:{err}')
+            else:
+                raise Exception(f'Error executing command <{cmd_sos}>:{code}-{err}')
+
+        file_path_pattern = r'Your sosreport has been generated and saved in:\s+(\S+)'
+        match = re.search(file_path_pattern, out)
+        if not match:
+            raise Exception(f'Cannot locate sos report file in sos command <{cmd_sos}> output: {out}')
+
+        sos_file = match.group(1)
+        sos_file_extension = os.path.splitext(sos_file)[1]
+        timestamp_sos_file = int(time.time() * 1000)
+        if '--case-id' in ctx.parameters:
+            case_id = ctx.parameters[ctx.parameters.index('--case-id') + 1]
+        else:
+            case_id = 'unknown_case_id'
+        prefix = f'sosreport_case_{case_id}_{timestamp_sos_file}_'
+        suffix = f'{sos_file_extension}_part'
+        sos_report_parts_folder = f'{LOG_DIR}/{fsid}'
+
+        # remove previous part files
+        remove_files(sos_report_parts_folder, 'sosreport*')
+
+        # split sos report file in <sos_files_number> parts (10 by default)
+        os.chdir(sos_report_parts_folder)
+        split_cmd = ['split', '-n', f'{ctx.sos_files_number}', sos_file, prefix, '--additional-suffix', suffix]
+        out, err, code = call(ctx, split_cmd, verbosity=CallVerbosity.DEBUG)
+        if err or code:
+            raise Exception(f'Error splitting sos report file: <{out}>')
+
+        # remove source sos report (because we already have the
+        # parts in the logs folder)
+        remove_files(os.path.dirname(sos_file), 'sosreport*')
+        # show the info needed to know what are the last sos files
+        print(f'New sos report files can be found in {LOG_DIR}/<fsid>/{prefix}*')
+
+        # If a target provided copy the sos report files to the
+        # destination host
+        if ctx.mgr_target:
+            scp_cmd = f'scp {sos_report_parts_folder}/{prefix}* {ctx.mgr_target}:{sos_report_parts_folder} > /dev/null 2>&1'
+            try:
+                os.system(scp_cmd)
+            except Exception as ex:
+                logger.error(f'Error copying sos files to target: {ex}')
+                return 1
+
+        # everything was ok
+        result = 0
+
+    except Exception as ex:
+        logger.error(f'Failed to execute sos command <{cmd_sos}>:\n {ex}')
+
+    return result
+
+##################################
+
 
 def systemd_target_state(ctx: CephadmContext, target_name: str, subsystem: str = 'ceph') -> bool:
     # TODO: UNITTEST
index 068219e559832ab017e484ff86ffe03caf9c7ab3..2f023185aeb12b343d88882a186f90f885f72de0 100644 (file)
@@ -60,6 +60,23 @@ class NvmeofService(CephService):
 
         daemon_spec.keyring = keyring
         daemon_spec.extra_files = {'ceph-nvmeof.conf': gw_conf}
+
+        if spec.enable_auth:
+            if (
+                not spec.client_cert
+                or not spec.client_key
+                or not spec.server_cert
+                or not spec.server_key
+            ):
+                self.mgr.log.error(f'enable_auth set for {spec.service_name()} spec, but at '
+                                   'least one of server/client cert/key fields missing. TLS '
+                                   f'not being set up for {daemon_spec.name()}')
+            else:
+                daemon_spec.extra_files['server_cert'] = spec.server_cert
+                daemon_spec.extra_files['client_cert'] = spec.client_cert
+                daemon_spec.extra_files['server_key'] = spec.server_key
+                daemon_spec.extra_files['client_key'] = spec.client_key
+
         daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec)
         daemon_spec.deps = []
         if not hasattr(self, 'gws'):
index 17290f5041dc85fa856576bf44b66f3f7bab5657..5000efa5d5459e46b605bd239a671cb899cae9e9 100644 (file)
@@ -17,10 +17,10 @@ config_file = /etc/ceph/ceph.conf
 id = {{ rados_id }}
 
 [mtls]
-server_key = {{ spec.server_key }}
-client_key = {{ spec.client_key }}
-server_cert = {{ spec.server_cert }}
-client_cert = {{ spec.client_cert }}
+server_key = /server.key
+client_key = /client.key
+server_cert = /server.cert
+client_cert = /client.cert
 
 [spdk]
 tgt_path = {{ spec.tgt_path }}
index 45e858fe20fb9f478ea40e1de6ea4d52d3154572..4310118a5ebc97b1c90c327e0453124444784952 100644 (file)
@@ -1273,13 +1273,13 @@ class NvmeofServiceSpec(ServiceSpec):
         #: ``enable_auth`` enables user authentication on nvmeof gateway
         self.enable_auth = enable_auth
         #: ``server_key`` gateway server key
-        self.server_key = server_key or './server.key'
+        self.server_key = server_key
         #: ``server_cert`` gateway server certificate
-        self.server_cert = server_cert or './server.crt'
+        self.server_cert = server_cert
         #: ``client_key`` client key
-        self.client_key = client_key or './client.key'
+        self.client_key = client_key
         #: ``client_cert`` client certificate
-        self.client_cert = client_cert or './client.crt'
+        self.client_cert = client_cert
         #: ``spdk_path`` path to SPDK
         self.spdk_path = spdk_path or '/usr/local/bin/nvmf_tgt'
         #: ``tgt_path`` nvmeof target path
@@ -1306,7 +1306,7 @@ class NvmeofServiceSpec(ServiceSpec):
             raise SpecValidationError('Cannot add NVMEOF: No Pool specified')
 
         if self.enable_auth:
-            if not any([self.server_key, self.server_cert, self.client_key, self.client_cert]):
+            if not all([self.server_key, self.server_cert, self.client_key, self.client_cert]):
                 raise SpecValidationError(
                     'enable_auth is true but client/server certificates are missing')