Browse Source

osmorphing: Register LUKS replica first boot script

A previous commit introduced the ability to register replica first boot
scripts, which is something we also need for LUKS.
Claudiu Belu 2 weeks ago
parent
commit
512459abaf

+ 2 - 1
coriolis/osmorphing/manager.py

@@ -320,4 +320,5 @@ def _morph_image(origin_provider, destination_provider, connection_info,
         event_manager.progress_update('No first-boot user script specified')
 
     os_mount_tools.remove_encryption_artifacts(os_root_dir)
-    os_mount_tools.install_encryption_firstboot_setup(os_root_dir)
+    os_mount_tools.install_encryption_firstboot_setup(
+        os_root_dir, import_os_morphing_tools)

+ 2 - 1
coriolis/osmorphing/osmount/base.py

@@ -63,7 +63,8 @@ class BaseOSMountTools(object, with_metaclass(abc.ABCMeta)):
     def remove_encryption_artifacts(self, os_root_dir):
         pass
 
-    def install_encryption_firstboot_setup(self, os_root_dir):
+    def install_encryption_firstboot_setup(
+            self, os_root_dir, os_morphing_tools):
         pass
 
     def get_environment(self):

+ 13 - 80
coriolis/osmorphing/osmount/luks_mixin.py

@@ -27,9 +27,6 @@ _CRYPTSETUP_TPM2_PLUGIN_PATHS = [
     "/usr/lib/x86_64-linux-gnu/cryptsetup/libcryptsetup-token-systemd-tpm2.so",
 ]
 
-_FIRSTBOOT_SCRIPT_PATH = "/usr/local/sbin/coriolis-luks-firstboot.sh"
-_SYSTEMD_UNIT_PATH = "/etc/systemd/system/coriolis-luks-firstboot.service"
-
 _RESOURCES_DIR = os.path.join(os.path.dirname(__file__), "resources")
 
 
@@ -43,23 +40,6 @@ _LUKS_FIRSTBOOT_SCRIPTS = {
     "dracut": _load_script("luks_firstboot_dracut.sh"),
 }
 
-_SYSTEMD_UNIT = """\
-[Unit]
-Description=Coriolis LUKS migration firstboot cleanup
-After=local-fs.target
-ConditionPathExists=/etc/luks
-
-[Service]
-Type=oneshot
-ExecStart=/usr/local/sbin/coriolis-luks-firstboot.sh
-RemainAfterExit=yes
-StandardOutput=journal+console
-StandardError=journal+console
-
-[Install]
-WantedBy=multi-user.target
-"""
-
 
 class LinuxLUKSMixin:
     """Mixin providing LUKS-related methods for BaseLinuxOSMountTools.
@@ -525,65 +505,8 @@ class LinuxLUKSMixin:
                 "No initramfs tool found in OS at '%s'; cannot rebuild "
                 "initramfs for LUKS auto-unlock." % os_root_dir)
 
-    def _detect_init_system(self, os_root_dir):
-        path = os.path.join(os_root_dir, "lib/systemd/systemd")
-        if utils.test_ssh_path(self._ssh, path):
-            return "systemd"
-
-        path = os.path.join(os_root_dir, "sbin/openrc")
-        if utils.test_ssh_path(self._ssh, path):
-            return "openrc"
-
-        path = os.path.join(os_root_dir, "sbin/initctl")
-        if utils.test_ssh_path(self._ssh, path):
-            return "upstart"
-
-        return "sysvinit"
-
-    def _register_firstboot_script_systemd(self, os_root_dir):
-        unit_abs = os.path.join(os_root_dir, _SYSTEMD_UNIT_PATH.lstrip("/"))
-        self._write_remote_file(unit_abs, _SYSTEMD_UNIT)
-        self._exec_cmd(
-            "sudo chown root:root %s && sudo chmod 644 %s" % (
-                unit_abs, unit_abs))
-
-        wants_dir = os.path.join(
-            os_root_dir, "etc/systemd/system/multi-user.target.wants")
-        self._exec_cmd("sudo mkdir -p %s" % wants_dir)
-        self._exec_cmd(
-            "sudo ln -sf %s %s/coriolis-luks-firstboot.service" % (
-                _SYSTEMD_UNIT_PATH, wants_dir))
-
-    def _install_luks_firstboot_script(self, os_root_dir):
-        """Write firstboot cleanup script and register with the init system."""
-        initramfs_tool = self._detect_initramfs_tool(os_root_dir)
-        script_content = _LUKS_FIRSTBOOT_SCRIPTS.get(initramfs_tool)
-        if script_content is None:
-            raise exception.CoriolisException(
-                "No initramfs tool found in OS at '%s'; cannot install "
-                "LUKS firstboot cleanup script." % os_root_dir)
-
-        script_abs = os.path.join(
-            os_root_dir, _FIRSTBOOT_SCRIPT_PATH.lstrip("/"))
-        self._exec_cmd("sudo mkdir -p %s" % os.path.dirname(script_abs))
-        self._write_remote_file(script_abs, script_content)
-        self._exec_cmd(
-            "sudo chown root:root %s && sudo chmod 500 %s" % (
-                script_abs, script_abs))
-
-        init_system = self._detect_init_system(os_root_dir)
-        LOG.info(
-            "Detected init system '%s'; installing LUKS firstboot script",
-            init_system)
-
-        if init_system == "systemd":
-            self._register_firstboot_script_systemd(os_root_dir)
-        else:
-            raise exception.CoriolisException(
-                "For VMs with LUKS-encrypted devices, only systemd-based VMs "
-                "are supported.")
-
-    def install_encryption_firstboot_setup(self, os_root_dir):
+    def install_encryption_firstboot_setup(
+            self, os_root_dir, os_morphing_tools):
         """Install a firstboot script to re-enroll TPM2."""
         if not self._luks_opened:
             return
@@ -595,7 +518,17 @@ class LinuxLUKSMixin:
         self._write_migration_keyfiles(os_root_dir)
         self._fix_grub_luks_root(os_root_dir)
         self._rebuild_initramfs(os_root_dir)
-        self._install_luks_firstboot_script(os_root_dir)
+
+        initramfs_tool = self._detect_initramfs_tool(os_root_dir)
+        script_content = _LUKS_FIRSTBOOT_SCRIPTS.get(initramfs_tool)
+        if script_content is None:
+            raise exception.CoriolisException(
+                "No initramfs tool found in OS at '%s'; cannot install "
+                "LUKS firstboot cleanup script." % os_root_dir)
+
+        os_morphing_tools.register_firstboot_script(
+            script_content, user_provided=False,
+            script_filename="luks-firstboot.sh")
 
     def _fix_grub_luks_root(self, os_root_dir):
         """Patch grub.cfg to use crypttab mapper names for LUKS root devices.

+ 0 - 9
coriolis/osmorphing/osmount/resources/luks_firstboot_dracut.sh

@@ -108,13 +108,6 @@ remove_migration_keyslots() {
     done
 }
 
-deregister_service() {
-    # Only disable, do NOT delete the unit file, or daemon-reload while the
-    # service is still running. systemd would detect "Current command vanished"
-    # and kill this process immediately.
-    systemctl disable coriolis-luks-firstboot.service 2>/dev/null || true
-}
-
 # main
 
 shopt -s nullglob
@@ -158,8 +151,6 @@ echo "Rebuilding initramfs."
 dracut --force --include /etc/crypttab /etc/crypttab
 rm -f "$DRACUT_CONF"
 
-deregister_service
-
 echo "Firstboot LUKS cleanup complete."
 rm -f "$0"
 

+ 0 - 9
coriolis/osmorphing/osmount/resources/luks_firstboot_initramfs_tools.sh

@@ -88,13 +88,6 @@ remove_migration_keyslots() {
     done
 }
 
-deregister_service() {
-    # Only disable, do NOT delete the unit file, or daemon-reload while the
-    # service is still running. systemd would detect "Current command vanished"
-    # and kill this process immediately.
-    systemctl disable coriolis-luks-firstboot.service 2>/dev/null || true
-}
-
 # main
 
 shopt -s nullglob
@@ -138,8 +131,6 @@ echo "Rebuilding initramfs."
 # allow us to continue with the rest of the script.
 NEEDRESTART_SUSPEND=1 DEBIAN_FRONTEND=noninteractive update-initramfs -u -k all
 
-deregister_service
-
 echo "Firstboot LUKS cleanup complete."
 rm -f "$0"
 

+ 4 - 3
coriolis/tests/integration/deployments/test_luks_osmorphing.py

@@ -72,10 +72,11 @@ class _LUKSOSMorphingMixin:
     def _assert_luks_common_firstboot_files(self):
         dst_basename = os.path.basename(self._dst_device)
         for path in [
-            "usr/local/sbin/coriolis-luks-firstboot.sh",
-            "etc/systemd/system/coriolis-luks-firstboot.service",
+            "usr/lib/coriolis/firstboot/service/luks-firstboot.sh",
+            "usr/lib/coriolis/firstboot/run-firstboot.sh",
+            "etc/systemd/system/coriolis-firstboot.service",
             "etc/systemd/system/multi-user.target.wants/"
-            "coriolis-luks-firstboot.service",
+            "coriolis-firstboot.service",
             "etc/luks/coriolis_%s.key" % dst_basename,
         ]:
             self.assertTrue(

+ 25 - 118
coriolis/tests/osmorphing/osmount/test_luks_mixin.py

@@ -602,139 +602,46 @@ class LinuxLUKSMixinTestCase(test_base.CoriolisBaseTestCase):
             _OS_ROOT_DIR,
         )
 
-    @mock.patch.object(luks_mixin.utils, 'test_ssh_path')
-    def test__detect_init_system(self, mock_test):
-        mock_test.side_effect = lambda _ssh, path: 'systemd/systemd' in path
-        self.assertEqual(
-            self.mixin._detect_init_system(_OS_ROOT_DIR), 'systemd'
-        )
-
-        mock_test.side_effect = lambda _ssh, path: path.endswith('openrc')
-        self.assertEqual(
-            self.mixin._detect_init_system(_OS_ROOT_DIR), 'openrc'
-        )
-
-        mock_test.side_effect = lambda _ssh, path: path.endswith('initctl')
-        self.assertEqual(
-            self.mixin._detect_init_system(_OS_ROOT_DIR), 'upstart'
-        )
-
-        # sysvinit fallback.
-        mock_test.side_effect = None
-        mock_test.return_value = False
-        self.assertEqual(
-            self.mixin._detect_init_system(_OS_ROOT_DIR), 'sysvinit'
-        )
-
-    @mock.patch.object(base.BaseSSHOSMountTools, "_exec_cmd")
-    @mock.patch.object(luks_mixin.LinuxLUKSMixin, "_write_remote_file")
-    def test__register_firstboot_script_systemd(self, mock_write, mock_exec):
-        self.mixin._register_firstboot_script_systemd(_OS_ROOT_DIR)
-
-        unit_abs = os.path.join(
-            _OS_ROOT_DIR, luks_mixin._SYSTEMD_UNIT_PATH.lstrip("/")
-        )
-        unit = luks_mixin._SYSTEMD_UNIT
-        mock_write.assert_called_once_with(unit_abs, unit)
-        wants_dir = os.path.join(
-            _OS_ROOT_DIR, 'etc/systemd/system/multi-user.target.wants'
-        )
-        mock_exec.assert_any_call('sudo mkdir -p %s' % wants_dir)
-        mock_exec.assert_has_calls(
-            [
-                mock.call(
-                    "sudo chown root:root %s && sudo chmod 644 %s"
-                    % (unit_abs, unit_abs)
-                ),
-                mock.call('sudo mkdir -p %s' % wants_dir),
-                mock.call(
-                    "sudo ln -sf %s %s/coriolis-luks-firstboot.service"
-                    % (luks_mixin._SYSTEMD_UNIT_PATH, wants_dir)
-                ),
-            ]
-        )
-
-    @mock.patch.object(
-        luks_mixin.LinuxLUKSMixin, '_register_firstboot_script_systemd'
-    )
-    @mock.patch.object(
-        luks_mixin.LinuxLUKSMixin,
-        '_detect_init_system',
-        return_value='systemd',
-    )
-    @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd')
-    @mock.patch.object(luks_mixin.LinuxLUKSMixin, "_write_remote_file")
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_detect_initramfs_tool')
-    def test__install_luks_firstboot_script(
-        self,
-        mock_detect_tool,
-        mock_write,
-        mock_exec,
-        mock_detect_init,
-        mock_reg_systemd,
-    ):
-        # no initramfs tool found.
-        mock_detect_tool.return_value = None
-        self.assertRaises(
-            exception.CoriolisException,
-            self.mixin._install_luks_firstboot_script,
-            _OS_ROOT_DIR,
-        )
-
-        # update-initramfs.
-        mock_detect_tool.return_value = 'update-initramfs'
-        mock_detect_init.return_value = "systemd"
-
-        self.mixin._install_luks_firstboot_script(_OS_ROOT_DIR)
-
-        script_abs = os.path.join(
-            _OS_ROOT_DIR, luks_mixin._FIRSTBOOT_SCRIPT_PATH.lstrip("/")
-        )
-        mock_exec.assert_has_calls(
-            [
-                mock.call("sudo mkdir -p %s" % os.path.dirname(script_abs)),
-                mock.call(
-                    "sudo chown root:root %s && sudo chmod 500 %s"
-                    % (script_abs, script_abs)
-                ),
-            ]
-        )
-        mock_reg_systemd.assert_called_once_with(_OS_ROOT_DIR)
-        script = luks_mixin._LUKS_FIRSTBOOT_SCRIPTS['update-initramfs']
-        mock_write.assert_called_once_with(script_abs, script)
-
-        # dracut.
-        mock_detect_tool.return_value = 'dracut'
-        mock_write.reset_mock()
-
-        self.mixin._install_luks_firstboot_script(_OS_ROOT_DIR)
-
-        script = luks_mixin._LUKS_FIRSTBOOT_SCRIPTS['dracut']
-        mock_write.assert_called_once_with(script_abs, script)
-
-    @mock.patch.object(
-        luks_mixin.LinuxLUKSMixin, '_install_luks_firstboot_script'
-    )
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_rebuild_initramfs')
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_fix_grub_luks_root')
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_write_migration_keyfiles')
     def test_install_encryption_firstboot_setup(
-        self, mock_write_keyfiles, mock_grub, mock_rebuild, mock_install
+        self, mock_write_keyfiles, mock_grub, mock_rebuild, mock_detect_tool
     ):
-        # No LUKS opened.
+        mock_morphing_tools = mock.MagicMock()
+
+        # No LUKS opened: early return, nothing called.
         self.mixin._luks_opened = []
-        self.mixin.install_encryption_firstboot_setup(_OS_ROOT_DIR)
+        self.mixin.install_encryption_firstboot_setup(
+            _OS_ROOT_DIR, mock_morphing_tools)
         mock_write_keyfiles.assert_not_called()
+        mock_morphing_tools.register_firstboot_script.assert_not_called()
 
-        # LUKS opened.
+        # LUKS opened, dracut.
         self.mixin._luks_opened = [("coriolis_sda", _DEV)]
+        mock_detect_tool.return_value = "dracut"
 
-        self.mixin.install_encryption_firstboot_setup(_OS_ROOT_DIR)
+        self.mixin.install_encryption_firstboot_setup(
+            _OS_ROOT_DIR, mock_morphing_tools)
 
         mock_write_keyfiles.assert_called_once_with(_OS_ROOT_DIR)
         mock_grub.assert_called_once_with(_OS_ROOT_DIR)
         mock_rebuild.assert_called_once_with(_OS_ROOT_DIR)
-        mock_install.assert_called_once_with(_OS_ROOT_DIR)
+        mock_morphing_tools.register_firstboot_script.assert_called_once_with(
+            luks_mixin._LUKS_FIRSTBOOT_SCRIPTS["dracut"],
+            user_provided=False,
+            script_filename="luks-firstboot.sh",
+        )
+
+        # No initramfs tool found: CoriolisException.
+        mock_detect_tool.return_value = None
+        self.assertRaises(
+            exception.CoriolisException,
+            self.mixin.install_encryption_firstboot_setup,
+            _OS_ROOT_DIR,
+            mock_morphing_tools,
+        )
 
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_write_remote_file')
     @mock.patch.object(luks_mixin.LinuxLUKSMixin, '_get_luks_uuid')