From 56b514e973da13e00aadfdcc4861376ec9d8dc66 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 5 May 2023 10:41:23 +0300 Subject: feat! combine luks and cryptsetup plugins They were basically doing the same thing. Sponsored-by: author --- vmdb/plugins/cryptsetup.mdwn | 20 ++++-- vmdb/plugins/cryptsetup_plugin.py | 129 ++++++++++++++++++++++++++++++++------ vmdb/plugins/luks.mdwn | 33 ---------- vmdb/plugins/luks_plugin.py | 106 ------------------------------- 4 files changed, 127 insertions(+), 161 deletions(-) delete mode 100644 vmdb/plugins/luks.mdwn delete mode 100644 vmdb/plugins/luks_plugin.py diff --git a/vmdb/plugins/cryptsetup.mdwn b/vmdb/plugins/cryptsetup.mdwn index f438e4f..1f374d1 100644 --- a/vmdb/plugins/cryptsetup.mdwn +++ b/vmdb/plugins/cryptsetup.mdwn @@ -1,15 +1,27 @@ Step: cryptsetup ----------------------------------------------------------------------------- -Use cryptsetup to set up encryption of a block device. +Set up disk encryption using LUKS with the `cryptsetup` utility. The +encryption passphrase is read from a file or from the output of a +command. The encrypted disk gets opened and can be mounted using a +separate tag for the cleartext view. Step keys: -* `cryptsetup` — REQUIRED; tag of block device +* `cryptsetup` — REQUIRED; the tag for the encrypted block + device. This is not directly useable by users, or mountable. -* `password` — REQUIRED; the encryption password +* `name` — REQUIRED; the tag for the de-crypted block device. + This is what gets mounted and visible to users. -* `name` — REQUIRED; name of the encrypted device when opened +* `password` — OPTIONAL; the encryption password + +* `key-file` — OPTIONAL; file from where passphrase is read. + +* `key-cmd` — OPTIONAL; command to run, passphrase is the first + line of its standard output. + +One of `password`, `key-file`, or `key-cmd` is REQUIRED. Example (in the .vmdb file): diff --git a/vmdb/plugins/cryptsetup_plugin.py b/vmdb/plugins/cryptsetup_plugin.py index 238d7ab..acb9b04 100644 --- a/vmdb/plugins/cryptsetup_plugin.py +++ b/vmdb/plugins/cryptsetup_plugin.py @@ -30,34 +30,127 @@ class CryptsetupPlugin(vmdb.Plugin): class CryptsetupStepRunner(vmdb.StepRunnerInterface): def get_key_spec(self): - return {"cryptsetup": str, "password": str, "name": str} + return { + "cryptsetup": str, + "tag": str, + "password": "", + "key-file": "", + "key-cmd": "", + } def run(self, step, settings, state): - cleartext_tag = step["cryptsetup"] - password = step["password"] - name = step["name"] + underlying = values["cryptsetup"] + crypt_name = values["tag"] + password = values["password"] or None + key_file = values["key-file"] or None + key_cmd = values["key-cmd"] or None - device = state.tags.get_dev(cleartext_tag) - tmp = tempfile.mkdtemp() - key = os.path.join(tmp, "key") - with open(key, "w") as f: - f.write(password) + if not isinstance(underlying, str): + raise vmdb.NotString("cryptsetup", underlying) + + if not isinstance(crypt_name, str): + raise vmdb.NotString("cryptsetup: tag", crypt_name) + + if password is None and key_file is None and key_cmd is None: + raise Exception( + "cryptsetup step MUST define one of password, key-file, or key-cmd" + ) + + if password is not None and key_file is not None: + raise Exception( + "cryptsetup step MUST define only one of password or key-file" + ) + + if password is not None and key_cmd is not None: + raise Exception( + "cryptsetup step MUST define only one of password or key-cmd" + ) + + if key_file is not None and key_cmd is not None: + raise Exception( + "cryptsetup step MUST define only one of key_file or key-cmd" + ) + + state.tmp_key_file = None + rmtmp = False + + if password is not None: + key_file = self._write_temp(password) + rmtp = True + + if key_cmd is not None: + output = vmdb.runcmd(["sh", "-ec", key_cmd]) + output = output.decode("UTF-8") + key = output.splitlines()[0] + key_file = self._write_temp(key) + rmtp = True + + assert key_file is not None + + dev = state.tags.get_dev(underlying) + if dev is None: + for t in state.tags.get_tags(): + logging.debug( + "tag %r dev %r mp %r", + t, + state.tags.get_dev(t), + state.tags.get_builder_mount_point(t), + ) + assert 0 - vmdb.runcmd(["cryptsetup", "luksFormat", "--batch-mode", device, key]) vmdb.runcmd( - ["cryptsetup", "open", "--type=luks", "--key-file", key, device, name] + [ + "cryptsetup", + "luksFormat", + "--batch-mode", + "--type=luks2", + "--allow-discards", + dev, + key_file, + ] ) - crypt_device = f"/dev/mapper/{name}" - assert os.path.exists(crypt_device) + vmdb.runcmd( + [ + "cryptsetup", + "open", + "--key-file", + key_file, + dev, + crypt_name, + ] + ) + + crypt_dev = "/dev/mapper/{}".format(crypt_name) + assert os.path.exists(crypt_dev) uuid = vmdb.runcmd(["cryptsetup", "luksUUID", device]).decode("UTF8").strip() - state.tags.append(name) - state.tags.set_dev(name, crypt_device) + state.tags.append(crypt_name) + state.tags.set_dev(crypt_name, crypt_dev) state.tags.set_luksuuid(name, uuid) state.tags.set_dm(name, name) - vmdb.progress(f"LUKS: name={name} dev={crypt_device} luksuuid={uuid} dm={name}") + + vmdb.progress( + f"LUKS: name={crypt_name} dev={crypt_dev} luksuuid={uuid} dm={crypt_name}" + ) vmdb.progress(f"LUKS: {state.tags._tags}") - vmdb.progress("remembering LUKS device {} as {}".format(crypt_device, name)) + vmdb.progress("remembering LUKS device {} as {}".format(crypt_dev, crypt_name)) + + if rmtmp: + os.remove(key_file) + + def _write_temp(self, passord): + fd, filename = tempfile.mkstemp() + os.close(fd) + open(filename, "w").write(password) + return filename + + def teardown(self, values, settings, state): + x = state.tmp_key_file + if x is not None and os.path.exists(x): + os.remove(x) + + crypt_name = values["name"] - shutil.rmtree(tmp) + crypt_dev = "/dev/mapper/{}".format(crypt_name) + vmdb.runcmd(["cryptsetup", "close", crypt_dev]) diff --git a/vmdb/plugins/luks.mdwn b/vmdb/plugins/luks.mdwn deleted file mode 100644 index 663d7b7..0000000 --- a/vmdb/plugins/luks.mdwn +++ /dev/null @@ -1,33 +0,0 @@ -Step: luks ------------------------------------------------------------------------------ - -Set up disk encryption using LUKS with the `cryptsetup` utility. The -encryption passphrase is read from a file or from the output of a -command. The encrypted disk gets opened and can be mounted using a -separate tag for the cleartext view. - -Step keys: - -* `cryptsetup` — REQUIRED; value is the tag for the encrypted - block device. This is not directly useable by users, or mountable. - -* `tag` — REQUIRED; the tag for the de-crypted block device. - This is what gets mounted and visible to users. - -* `key-file` — OPTIONAL; file from where passphrase is read. - -* `key-cmd` — OPTIONAL; command to run, passphrase is the first - line of its standard output. - -Example (in the .vmdb file): - - - cryptsetup: root - tag: root_crypt - key-file: disk.pass - -Same, except run a command to get passphrase (in this case -[pass](https://www.passwordstore.org/)): - - - cryptsetup: root - tag: root_crypt - key-cmd: pass show disk-encryption diff --git a/vmdb/plugins/luks_plugin.py b/vmdb/plugins/luks_plugin.py deleted file mode 100644 index b736bea..0000000 --- a/vmdb/plugins/luks_plugin.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2018 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# =*= License: GPL-3+ =*= - - -import logging -import os -import tempfile - -import vmdb - - -class LuksPlugin(vmdb.Plugin): - def enable(self): - self.app.step_runners.add(CryptsetupStepRunner()) - - -class CryptsetupStepRunner(vmdb.StepRunnerInterface): - def get_key_spec(self): - return {"cryptsetup": str, "tag": str, "key-file": "", "key-cmd": ""} - - def run(self, values, settings, state): - underlying = values["cryptsetup"] - crypt_name = values["tag"] - - if not isinstance(underlying, str): - raise vmdb.NotString("cryptsetup", underlying) - if not isinstance(crypt_name, str): - raise vmdb.NotString("cryptsetup: tag", crypt_name) - - state.tmp_key_file = None - key_file = values["key-file"] or None - key_cmd = values["key-cmd"] or None - if key_file is None and key_cmd is None: - raise Exception("cryptsetup step MUST define one of key-file or key-cmd") - - if key_file is None: - output = vmdb.runcmd(["sh", "-ec", key_cmd]) - output = output.decode("UTF-8") - key = output.splitlines()[0] - fd, key_file = tempfile.mkstemp() - state.tmp_key_file = key_file - os.close(fd) - open(key_file, "w").write(key) - - dev = state.tags.get_dev(underlying) - if dev is None: - for t in state.tags.get_tags(): - logging.debug( - "tag %r dev %r mp %r", - t, - state.tags.get_dev(t), - state.tags.get_builder_mount_point(t), - ) - assert 0 - - vmdb.runcmd( - [ - "cryptsetup", - "-q", - "luksFormat", - "--type", - "luks2", - "--allow-discards", - dev, - key_file, - ] - ) - vmdb.runcmd( - [ - "cryptsetup", - "open", - "--key-file", - key_file, - dev, - crypt_name, - ] - ) - - crypt_dev = "/dev/mapper/{}".format(crypt_name) - assert os.path.exists(crypt_dev) - state.tags.append(crypt_name) - state.tags.set_dev(crypt_name, crypt_dev) - - def teardown(self, values, settings, state): - x = state.tmp_key_file - if x is not None and os.path.exists(x): - os.remove(x) - - crypt_name = values["tag"] - - crypt_dev = "/dev/mapper/{}".format(crypt_name) - vmdb.runcmd(["cryptsetup", "close", crypt_dev]) -- cgit v1.2.1