From 5407f1cef128db438092fdfe49f3a21d78387e69 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 7 Nov 2023 20:21:09 +0100 Subject: [PATCH] stages(kickstart): support autopart This commit implements the `autopart` kickstart option and adds matching tests. --- stages/org.osbuild.kickstart | 84 ++++++++++++++++++++++++++++++++++- stages/test/test_kickstart.py | 54 ++++++++++++++++++++++ 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/stages/org.osbuild.kickstart b/stages/org.osbuild.kickstart index c3286a1f..22434350 100755 --- a/stages/org.osbuild.kickstart +++ b/stages/org.osbuild.kickstart @@ -202,6 +202,66 @@ SCHEMA = r""" "display_mode": { "description": "Perform the Kickstart installation in the given display mode", "enum": ["text", "graphical", "cmdline"] + }, + "autopart": { + "description": "Automatically creates partitions", + "type": "object", + "not": {"required": ["pbkdf-iterations", "pbkdf-time"]}, + "properties": { + "type": { + "description": "Selects one of the predefined automatic partitioning schemes you want to use", + "type": "string", + "enum": ["lvm", "btrfs", "plain", "thinp"] + }, + "fstype": { + "description": "Specify a supported file system (such as ext4 or xfs) to replace the default when doing automatic partitioning", + "type": "string" + }, + "nolvm": { + "description": "Do not use LVM or Btrfs for automatic partitioning. This option is equal to --type=plain", + "type": "boolean" + }, + "encrypted": { + "description": "Encrypts all partitions", + "type": "boolean" + }, + "passphrase": { + "description": "Provides a default system-wide passphrase for all encrypted devices", + "type": "string" + }, + "escrowcert": { + "description": "Stores data encryption keys of all encrypted volumes as files in /root, encrypted using the X.509 certificate from the URL specified", + "type": "string" + }, + "backuppassphrase": { + "description": "Adds a randomly-generated passphrase to each encrypted volume", + "type": "boolean" + }, + "cipher": { + "description": "Specifies which type of encryption will be used if the Anaconda default aes-xts-plain64 is not satisfactory", + "type": "string" + }, + "luks-version": { + "description": "Specifies which version of LUKS should be used to encrypt the system", + "type": "string" + }, + "pbkdf": { + "description": "Sets Password-Based Key Derivation Function (PBKDF) algorithm for the LUKS keyslot", + "type": "string" + }, + "pbkdf-memory": { + "description": "Sets the memory cost for PBKDF", + "type": "integer" + }, + "pbkdf-time": { + "description": "Sets the number of milliseconds to spend with PBKDF passphrase processing", + "type": "integer" + }, + "pbkdf-iterations": { + "description": "Sets the number of iterations for passphrase processing directly", + "type": "integer" + } + } } } """ @@ -304,7 +364,25 @@ def make_reboot(options): return cmd -def main(tree, options): +def make_autopart(options: Dict) -> str: + autopart = options.get("autopart") + if autopart is None: + return "" + cmd = "autopart" + for key in ["type", "fstype", "nolvm", "encrypted", "passphrase", + "escrowcert", "backuppassphrase", "cipher", "luks-version", + "pbkdf", "pbkdf-memory", "pbkdf-time", "pbkdf-iterations"]: + if key not in autopart: + continue + val = autopart[key] + if isinstance(val, bool): + cmd += f" --{key}" + else: + cmd += f" --{key}={val}" + return cmd + + +def main(tree, options): # pylint: disable=too-many-branches path = options["path"].lstrip("/") ostree = options.get("ostree") @@ -349,7 +427,9 @@ def main(tree, options): display_mode = options.get("display_mode") if display_mode: config += [display_mode] - + autopart = make_autopart(options) + if autopart: + config += [autopart] reboot = make_reboot(options) if reboot: config += [reboot] diff --git a/stages/test/test_kickstart.py b/stages/test/test_kickstart.py index ff2d3856..6accce71 100644 --- a/stages/test/test_kickstart.py +++ b/stages/test/test_kickstart.py @@ -107,6 +107,56 @@ TEST_INPUT = [ ({"display_mode": "text"}, "text"), ({"display_mode": "graphical"}, "graphical"), ({"display_mode": "cmdline"}, "cmdline"), + # autopart + ({"autopart": {}}, "autopart"), + ({"autopart": {"type": "plain"}}, "autopart --type=plain"), + ({"autopart": {"fstype": "ext4"}}, "autopart --fstype=ext4"), + ({"autopart": {"nolvm": True}}, "autopart --nolvm"), + ({"autopart": {"encrypted": True}}, "autopart --encrypted"), + ({"autopart": {"passphrase": "secret"}}, "autopart --passphrase=secret"), + ({"autopart": {"escrowcert": "http://escrow"}}, "autopart --escrowcert=http://escrow"), + ({"autopart": {"backuppassphrase": True}}, "autopart --backuppassphrase"), + ({"autopart": {"cipher": "aes-xts-plain2048"}}, "autopart --cipher=aes-xts-plain2048"), + ({"autopart": {"luks-version": "42"}}, "autopart --luks-version=42"), + ({"autopart": {"pbkdf": "scrypt"}}, "autopart --pbkdf=scrypt"), + ({"autopart": {"pbkdf-memory": 64}}, "autopart --pbkdf-memory=64"), + ({"autopart": {"pbkdf-time": 128}}, "autopart --pbkdf-time=128"), + ({"autopart": {"pbkdf-iterations": 256}}, "autopart --pbkdf-iterations=256"), + ({ + "lang": "en_US.UTF-8", + "keyboard": "us", + "timezone": "UTC", + "zerombr": True, + "clearpart": { + "all": True, + "drives": [ + "sd*|hd*|vda", + "/dev/vdc" + ] + }, + "autopart": { + "type": "lvm", + "fstype": "zfs", + "nolvm": True, + "encrypted": True, + "passphrase": "secret2", + "escrowcert": "http://some-url", + "backuppassphrase": True, + "cipher": "twofish-cbc", + "luks-version": "2", + "pbkdf": "scrypt", + "pbkdf-memory": 256, + "pbkdf-time": 512, + # pbkdf-iterations cannot be used together with time + }, + }, + "lang en_US.UTF-8\nkeyboard us\ntimezone UTC\nzerombr\n" + + "clearpart --all --drives=sd*|hd*|vda,/dev/vdc\n" + + "autopart --type=lvm --fstype=zfs --nolvm --encrypted" + + " --passphrase=secret2 --escrowcert=http://some-url" + + " --backuppassphrase --cipher=twofish-cbc --luks-version=2" + + " --pbkdf=scrypt --pbkdf-memory=256 --pbkdf-time=512" + ), ] @@ -186,6 +236,10 @@ def test_kickstart_valid(tmp_path, test_input, expected): # pylint: disable=unu ({"reboot": "random-string"}, "'random-string' is not valid "), ({"reboot": {"random": "option"}}, "{'random': 'option'} is not valid "), ({"display_mode": "invalid-mode"}, "'invalid-mode' is not one of "), + # autopart + ({"autopart": {"type": "not-valid"}}, "'not-valid' is not one of ["), + # Only one of --pbkdf-{time,iterations} can be specified at the same time + ({"autopart": {"pbkdf-time": 1, "pbkdf-iterations": 2}}, " should not be valid under "), ], ) def test_schema_validation_bad_apples(test_data, expected_err):