#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)## Copyright (C) 2015 Red Hat, Inc.# SPDX-License-Identifier: LGPL-2.1-or-laterimport jsonimport osimport subprocessimport timeimport packagelibimport storagelibimport testlibdef console_screenshot(machine, name):subprocess.run(f"virsh -c qemu:///session screenshot {machine._domain.ID()!s} '{name}'",shell=True)testlib.attach(name, move=True)print("Wrote screenshot to " + name)@testlib.nondestructive@testlib.skipImage("cryptsetup uses too much memory, OOM on our test VMs", "rhel-8-*")class TestStorageLuks(storagelib.StorageCase):@testlib.skipImage("cryptsetup uses too much memory, OOM on our test VMs", "fedora-45")def testLuks(self):self.allow_journal_messages("Device is not initialized.*", ".*could not be opened.")m = self.machineb = self.browsermount_point_secret = "/run/secret"self.login_and_go("/storage")# Add a disk and partition itdisk = self.add_ram_disk(100)self.click_card_row("Storage", name=disk)self.click_card_dropdown("Solid State Drive", "Create partition table")self.dialog({"type": "gpt"})b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")self.assertEqual(m.execute("grep -v ^# /etc/crypttab || true").strip(), "")# Format it with luksself.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")self.dialog_wait_open()self.dialog_set_val("size", 60)self.dialog_set_val("type", "ext4")self.dialog_set_val("crypto", self.default_crypto_type)self.dialog_set_val("name", "ENCRYPTED")self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_set_val("passphrase2", "vainu-reku-toma-rolle-kaja")self.dialog_set_val("store_passphrase.on", val=True)self.dialog_set_val("crypto_options", "crypto,options")self.dialog_set_val("mount_point", mount_point_secret)b.assert_pixels("#dialog", "format")self.dialog_apply()self.dialog_wait_close()b.wait_text(self.card_row_col("GPT partitions", 1, 2), "ext4 filesystem (encrypted)")b.wait_text(self.card_row_col("GPT partitions", 1, 3), mount_point_secret)dev = "/dev/" + b.text(self.card_row_col("GPT partitions", 1, 1))self.click_card_row("GPT partitions", 1)if self.default_crypto_type == "luks1":b.wait_text(self.card_desc("Encryption", "Encryption type"), "LUKS1")elif self.default_crypto_type == "luks2":b.wait_text(self.card_desc("Encryption", "Encryption type"), "LUKS2")uuid = m.execute(f"cryptsetup luksUUID {dev}").strip()cleartext_dev = "/dev/mapper/luks-" + uuidpassphrase_path = "/etc/luks-keys/luks-" + uuidself.assert_in_configuration(dev, "crypttab", "options", "crypto,options")self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_configuration(dev, "crypttab", "passphrase-path", passphrase_path)self.assertEqual(m.execute(f"cat {passphrase_path}"), "vainu-reku-toma-rolle-kaja")self.assert_in_configuration(cleartext_dev, "fstab", "dir", mount_point_secret)self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")# cut off minutes, to avoid a too wide race condition in the testdate_no_mins = b.eval_js("Intl.DateTimeFormat('en', { dateStyle: 'medium', timeStyle: 'short' }).format()").split(':')[0]b.wait_in_text(self.card_desc("Encryption", "Stored passphrase"),f"Last modified: {date_no_mins}:")b.assert_pixels(self.card("Encryption"), "card",ignore=["[data-test-desc-title='Stored passphrase']","[data-test-desc-title='Cleartext device']"])# Unmount. This locks itb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.assert_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")self.wait_not_mounted("Filesystem")# Mount, this uses the stored passphrase for unlockingb.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_wait_val("mount_point", mount_point_secret)self.dialog_apply()self.dialog_wait_close()self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")self.wait_mounted("ext4 filesystem")b.click(self.card_desc_action("Encryption", "Options"))self.dialog({"options": "weird,options"})self.assert_in_configuration(dev, "crypttab", "options", "weird,options")# Change stored passphraseb.click(self.card_desc_action("Encryption", "Stored passphrase"))self.dialog({"passphrase": "wrong-passphrase"})self.assert_in_configuration(dev, "crypttab", "passphrase-path", passphrase_path)self.assertEqual(m.execute(f"cat {passphrase_path}"), "wrong-passphrase")# Unmount itb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.assert_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")self.wait_not_mounted("Filesystem")# Mount, this tries the wrong passphrase but eventually prompts.b.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_apply()b.wait_in_text("#dialog", "Failed to activate device:")self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")self.wait_mounted("ext4 filesystem")# Remove passphraseb.click(self.card_desc_action("Encryption", "Stored passphrase"))self.dialog({"passphrase": ""})self.assert_in_configuration(dev, "crypttab", "passphrase-path", "")# Unmount itb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.assert_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")self.wait_not_mounted("Filesystem")# Mount it readonly. This asks for a passphrase.b.click(self.card_button("Filesystem", "Mount"))self.dialog({"mount_options.ro": True,"passphrase": "vainu-reku-toma-rolle-kaja"})self.wait_mounted("ext4 filesystem")self.assertIn("ro", m.execute(f"findmnt -s -n -o OPTIONS {mount_point_secret}"))self.assert_in_configuration(dev, "crypttab", "options", "readonly")self.assert_in_configuration(cleartext_dev, "fstab", "opts", "ro")# Check that the clear text device is actually readonlyself.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "1")# Change "read only" mount option. This should reopen LUKS as# necessary and ask for a passphrase.b.click(self.card_desc("ext4 filesystem", "Mount point") + " button")self.dialog_wait_open()self.dialog_set_val("mount_options.ro", val=False)self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()self.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "0")# Delete the partition.self.click_card_dropdown("Partition", "Delete")self.confirm()b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")# luksmeta-monitor-hack.py races with the partition deletionself.allow_journal_messages('Unknown device .*: No such file or directory')self.assertEqual(m.execute("grep -v ^# /etc/crypttab || true").strip(), "")self.assertEqual(m.execute(f"grep {mount_point_secret} /etc/fstab || true"), "")# luksmeta-monitor-hack.py might leave a udevadm process# behind, so let's check that the session goes away cleanly# after a logout.b.logout()testlib.wait(lambda: m.execute("(loginctl list-users | grep admin) || true") == "")# FIXME: race condition after unmounting; hard to investigate, re-check after storage redesignself.allow_browser_errors("validateDOMNesting.*cannot appear as a child.*<tr> ul")def testFormatReadOnly(self):m = self.machineb = self.browserself.login_and_go("/storage")disk = self.add_loopback_disk(320) # xfs minimum size is ~300MBself.click_card_row("Storage", name=disk)# Create a encrypted filesystem and mount it readonly. This# needs to reopen the LUKS layer after formatting.self.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "ext4","mount_point": f"{self.mnt_dir}/foo","mount_options.ro": True,"crypto": "luks1","passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja"})b.wait_visible(self.card("ext4 filesystem"))uuid = m.execute(f"cryptsetup luksUUID {disk}").strip()cleartext_dev = "/dev/mapper/luks-" + uuidself.assert_in_configuration(disk, "crypttab", "options", "readonly")self.assert_in_configuration(cleartext_dev, "fstab", "opts", "ro")self.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "1")# Reformat it, while keeping the encryption layer and keeping# it read-only. This needs to reopen LUKS twice, once to make# the cleartext device writable for formatting, and once to# make it read-only again, as requested.self.click_card_dropdown("ext4 filesystem", "Format")self.dialog_wait_open()self.dialog_wait_val("mount_point", f"{self.mnt_dir}/foo")self.dialog_wait_val("type", "ext4")self.dialog_wait_val("crypto", " keep")self.dialog_wait_val("crypto_options", "")b.wait_visible(self.dialog_field("mount_options.ro") + ":checked")self.dialog_set_val("type", "xfs")self.dialog_set_val("old_passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card("xfs filesystem"))self.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "1")def testNoFsys(self):m = self.machineb = self.browserself.login_and_go("/storage")# Add a disk and format it with luks, but without filesystemdisk = self.add_ram_disk()self.click_card_row("Storage", name=disk)self.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "empty","crypto": self.default_crypto_type,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja"})b.wait_visible(self.card("Unformatted data"))# Lock itb.click(self.card_button("Unformatted data", "Lock"))self.confirm()b.wait_visible(self.card("Locked data"))# Make it readonly# The locking above changes crypttab asynchronously, and there# is no indication in the UI when cockpit has caught up with# that. So we retry the dialog when it looks like Cockpit is# out of synch...self.dialog_with_error_retry(trigger=lambda: b.click(self.card_desc_action("Encryption", "Options")),values={"options": "readonly"},errors=["Didn't find entry to remove"])self.assertNotEqual(m.execute("grep readonly /etc/crypttab"), "")# Unlock itb.click(self.card_button("Locked data", "Unlock"))self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja"})# Should be unlocked readonlyuuid = m.execute(f"cryptsetup luksUUID {disk}").strip()cleartext_dev = "/dev/mapper/luks-" + uuidself.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "1")# Now create a empty, encrypted partitionself.click_card_dropdown("Solid State Drive", "Create partition table")self.dialog({"type": "gpt"})b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")self.dialog({"type": "empty","crypto": self.default_crypto_type,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja"})b.wait_text(self.card_row_col("GPT partitions", 1, 2), "Unformatted data (encrypted)")def testKeepKeys(self):b = self.browserself.login_and_go("/storage")# Add a disk and format it with luks and a filesystemdisk = self.add_ram_disk()self.click_card_row("Storage", name=disk)self.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "ext4","crypto": self.default_crypto_type,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja","mount_point": f"{self.mnt_dir}/foo"})b.wait_visible(self.card("ext4 filesystem"))# Format it again but keep the keysself.click_card_dropdown("ext4 filesystem", "Format")self.dialog({"type": "ext4","crypto": " keep","mount_point": f"{self.mnt_dir}/foo"})b.wait_visible(self.card("ext4 filesystem"))# Unmount (and lock) itb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.wait_not_mounted("Filesystem")# Format it again and keep the keys. Because it's locked, we# need the old passphrase.self.click_card_dropdown("Filesystem", "Format")self.dialog({"type": "ext4","crypto": " keep","old_passphrase": "vainu-reku-toma-rolle-kaja","mount_point": f"{self.mnt_dir}/foo"})self.wait_mounted("ext4 filesystem")@testlib.skipImage("no Clevis TPM2 support", "debian-*", "ubuntu-2604")@testlib.nondestructivedef testClevisTPM(self):m = self.machineb = self.browserdisk = self.add_ram_disk()m.execute(f"echo einszweidrei | cryptsetup luksFormat --pbkdf-memory 32768 {disk}")# Two TPM2 variants..m.execute(f"""echo einszweidrei | clevis luks bind -d {disk} tpm2 '{{"hash":"sha256","key":"ecc"}}'""")m.execute(f"""echo einszweidrei | clevis luks bind -d {disk} tpm2 '{{"hash":"sha256","key":"ecc","pcr_ids":"7,11"}}'""")self.login_and_go("/storage")self.click_card_row("Storage", name=disk)panel = "#encryption-keys "b.wait_in_text(f"{panel} tr[data-row-slot='0']", "Passphrase")b.wait_in_text(f"{panel} tr[data-row-slot='1']", "TPM2")b.wait_in_text(f"{panel} tr[data-row-slot='2']", "TPM2")@testlib.skipImage('/usr is immutable', 'centos-9-bootc')class TestStorageLuksDestructive(storagelib.StorageCase):# on RHEL 8, provide enough RAM for cryptsetup's PBKDF (https://issues.redhat.com/browse/RHEL-8258)# On any newer OS, cryptsetup gets along with less RAMprovision = {"0": {"memory_mb": 1400 if os.environ.get("TEST_OS", "").startswith("rhel-8-") else None}}def rhel_8_courtesy_wait(self):if self.machine.image == "rhel-8-10":# Udisks2 2.9.0 in rhel-8-10 is missing commit# 13d3f34fbe21bba95e0eb1e9f3c2e5122ec694d3 and thus method# calls like UpdateConfigurationItem return too early,# before the changes that they have made are reflected in# the D-Bus properties. Let's be polite and wait a bit.time.sleep(5)def testLuks1Slots(self):self.allow_journal_messages("Device is not initialized.*", ".*could not be opened.")m = self.machineb = self.browser# This should work without any of the Clevis stuff.m.execute("rm -f /usr/bin/luksmeta /usr/bin/clevis*")mount_point_secret = "/run/secret"error_base = "Error unlocking /dev/sda: Failed to activate device: "error_messages = [error_base + "Operation not permitted",error_base + "Incorrect passphrase."]self.login_and_go("/storage")m.add_disk("50M", serial="MYDISK")dev = "/dev/sda"self.click_card_row("Storage", name=dev)# create volume and passphraseself.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "ext4","crypto": "luks1","name": "ENCRYPTED","mount_point": mount_point_secret,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja"},secondary=True)self.wait_not_mounted("Filesystem")b.wait_text(self.card_desc("Encryption", "Encryption type"), "LUKS1")b.wait_in_text(self.card_desc("Encryption", "Options"), "nofail")uuid = m.execute(f"cryptsetup luksUUID {dev}").strip()cleartext_dev = "/dev/mapper/luks-" + uuid# add one more passphrasepanel = "#encryption-keys "b.wait_visible(panel)b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("new_passphrase", "vainu-reku-toma-rolle-kaja-1")self.dialog_set_val("new_passphrase2", "vainu-reku-toma-rolle-kaja-1")self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()# unlock with first passphraseb.click(self.card_button("Filesystem", "Mount"))self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja"})self.wait_mounted("ext4 filesystem")self.rhel_8_courtesy_wait()self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")# unlock with second passphraseb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.rhel_8_courtesy_wait()self.assert_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")self.wait_not_mounted("Filesystem")b.click(self.card_button("Filesystem", "Mount"))self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja-1"})self.rhel_8_courtesy_wait()self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")self.wait_mounted("ext4 filesystem")# delete second key slotb.click(panel + "tr:nth-child(2) button[aria-label='Remove']")# do not accept the same passphraseb.set_input_text("#remove-passphrase", "vainu-reku-toma-rolle-kaja-1")b.click("button:contains('Remove')")b.wait_in_text(".pf-v6-c-alert__title", "No key available with this passphrase.")# delete with passphrase from slot 0b.set_input_text("#remove-passphrase", "vainu-reku-toma-rolle-kaja")b.click("button:contains('Remove')")with b.wait_timeout(30):b.wait_not_present("#remove-passphrase")# check that it is not possible to unlock with deleted passphraseb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()self.rhel_8_courtesy_wait()self.assert_in_configuration(dev, "crypttab", "options", "noauto")self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")self.wait_not_mounted("Filesystem")b.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja-1")self.dialog_apply()b.wait_visible(".pf-v6-c-alert")self.assertIn(b.text("h4.pf-v6-c-alert__title:not(span)").split("Danger alert:", 1).pop(), error_messages)self.dialog_cancel()# add more passphrases, seven exactly, to reach the limit of eight for LUKSv1for i in range(1, 8):b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("new_passphrase", f"vainu-reku-toma-rolle-kaja-{i}")self.dialog_set_val("new_passphrase2", f"vainu-reku-toma-rolle-kaja-{i}")self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()# check if add button is inactiveb.wait_visible(self.card("Encryption") + " [aria-label='Add']:disabled")# check if edit button is inactiveslots_row = panel + " tr:first-child"b.wait_visible(slots_row + " button:disabled")# remove one slotslots_list = panelb.wait_visible(slots_list + " td:contains('Slot 7')")b.click(slots_list + "tr:last-child button[aria-label='Remove']")b.set_input_text("#remove-passphrase", "vainu-reku-toma-rolle-kaja-6")b.click("button:contains('Remove')")with b.wait_timeout(30):b.wait_not_present("#remove-passphrase")# check if buttons have become enabled after removing last slotb.wait_not_present(slots_list + ":disabled")b.wait_not_present(panel + ":disabled")# remove slot 0, with the original passphraseb.click(slots_list + "tr:nth-child(1) button[aria-label='Remove']")b.click("#force-remove-passphrase")b.click("button:contains('Remove')")with b.wait_timeout(30):b.wait_not_present("#remove-passphrase")# check that it is not possible to unlock with deleted passphraseb.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()b.wait_visible(".pf-v6-c-alert")self.assertIn(b.text("h4.pf-v6-c-alert__title:not(span)").split("Danger alert:", 1).pop(), error_messages)self.dialog_cancel()# change one of the passphrasesb.click(slots_list + "tr:last-child [aria-label='Edit']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("old_passphrase", "vainu-reku-toma-rolle-kaja-6")self.dialog_set_val("new_passphrase", "vainu-reku-toma-rolle-kaja-8")self.dialog_set_val("new_passphrase2", "vainu-reku-toma-rolle-kaja-8")self.dialog_apply()self.dialog_wait_close()# unlock volume with the negwly created passphraseb.click(self.card_button("Filesystem", "Mount"))self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja-8"})self.wait_mounted("ext4 filesystem")self.rhel_8_courtesy_wait()self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")def testReboot(self):m = self.machineb = self.browserself.login_and_go("/storage")# Add a disk and format it with luks and a filesystem, then# reboot and check that the filesystem is mounted.dev = "/dev/sda"m.add_disk("50M", serial="MYDISK")self.click_card_row("Storage", name=dev)self.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "ext4","crypto": self.default_crypto_type,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja","mount_point": f"{self.mnt_dir}/foo"})self.wait_mounted("ext4 filesystem")self.setup_systemd_password_agent("vainu-reku-toma-rolle-kaja")self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")self.wait_mounted("ext4 filesystem")@testlib.skipImage("clevis/tang not installed", "*-bootc")class TestStorageNBDE(storagelib.StorageCase, packagelib.PackageCase):provision = {"0": {"address": "10.111.112.1/20", "memory_mb": 2048},"tang": {"address": "10.111.112.5/20"}}def testBasic(self):m = self.machineb = self.browser# Only Arch gets it right...need_fixing = (m.image != "arch")mount_point_secret = "/run/secret"tang_m = self.machines["tang"]tang_m.execute("systemctl start tangd.socket")tang_m.execute("firewall-cmd --add-port 80/tcp")if need_fixing:self.addPackageSet("clevis")self.enableRepo()self.login_and_go("/storage")# Add a disk and format it with luksdev = "/dev/sda"m.add_disk("50M", serial="MYDISK")self.click_card_row("Storage", name=dev)self.click_card_dropdown("Unformatted data", "Format")self.dialog({"type": "ext4","crypto": self.default_crypto_type,"name": "ENCRYPTED","mount_point": mount_point_secret,"passphrase": "vainu-reku-toma-rolle-kaja","passphrase2": "vainu-reku-toma-rolle-kaja"},secondary=True)self.wait_not_mounted("Filesystem")b.wait_in_text(self.card_desc("Encryption", "Options"), "nofail")panel = "#encryption-keys "b.wait_in_text(panel + "tr:nth-child(1)", "Passphrase")# Add a key#b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("type", "tang")self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_set_val("passphrase", "wrong-passphrase")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash with the Tang server")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()with b.wait_timeout(60):if need_fixing:b.wait_in_text("#dialog", "Add Network Bound Disk Encryption")b.wait_in_text("#dialog", "The clevis-systemd package must be installed")self.dialog_apply()b.wait_in_text("#dialog", "No key available with this passphrase.")self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")self.dialog_apply()self.dialog_wait_close()b.wait_in_text(panel + "tr:nth-child(2)", "10.111.112.5")# Adding the key should add "_netdev" options#b.wait_in_text(self.card_desc("Filesystem", "Mount point"), "after network")b.wait_in_text(self.card_desc("Encryption", "Options"), "_netdev")# Add a clevis config that Cockpit doesn't understand, make# sure it can't be edited but can be removed.#thp = tang_m.execute("tang-show-keys").strip()m.execute(f"""echo vainu-reku-toma-rolle-kaja | clevis luks bind -d /dev/sda sss '{{"t":1,"pins":{{"tang":[{{"url":"10.111.112.5","thp":"{thp}"}}]}}}}'""")b.wait_in_text(panel + "tr:nth-child(3)", "Unknown type")b.wait_in_text(panel + "tr:nth-child(3)", "sss")b.wait_visible(panel + "tr:nth-child(3) [aria-label='Edit']:disabled")b.click(panel + 'tr:nth-child(3) button[aria-label="Remove"]')b.wait_in_text("#dialog", "Remove key in slot 2?")self.confirm()b.wait_not_present(panel + "tr:nth-child(3)")# Mount it. This should succeed without passphrase.#b.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_wait_val("mount_point", mount_point_secret)self.dialog_apply()self.dialog_wait_close()self.wait_mounted("ext4 filesystem")# LUKS should be read/writeuuid = m.execute(f"cryptsetup luksUUID {dev}").strip()cleartext_dev = "/dev/mapper/luks-" + uuidself.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "0")# Remount "readonly", LUKS should be readonlyb.click(self.card_button("ext4 filesystem", "Unmount"))self.confirm()b.click(self.card_button("Filesystem", "Mount"))self.dialog_wait_open()self.dialog_set_val("mount_options.ro", val=True)self.dialog_apply()self.dialog_wait_close()self.wait_mounted("ext4 filesystem")# Now LUKS should be readonlyself.assertEqual(m.execute(f"lsblk -no RO {cleartext_dev}").strip(), "1")# Edit the key, without providing an existing passphrase#b.click(panel + "tr:nth-child(2) [aria-label='Edit']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_wait_val("tang_url", "10.111.112.5")self.dialog_set_val("tang_url", "http://10.111.112.5/")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash with the Tang server")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()self.dialog_wait_close()b.wait_in_text(panel + "tr:nth-child(2)", "http://10.111.112.5/")# Reset the options so that we can check that they get added# also when a second key is added.#b.click(self.card_desc("ext4 filesystem", "Mount point") + " button")self.dialog({"at_boot": "nofail"})b.wait_in_text(self.card_desc("Encryption", "Options"), "nofail")if need_fixing:# Break things again, including PackageKitm.execute("if type dnf; then dnf remove -y clevis-systemd; else apt-get purge -y clevis-systemd; fi")m.execute("systemctl mask --now packagekit")if m.image.startswith("fedora-"):m.execute("systemctl mask --now dnf5daemon-server")# Add a second key, this should try to fix things again, but# we have to help with the package install#b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("type", "tang")self.dialog_set_val("tang_url", "http://10.111.112.5")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash with the Tang server")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()with b.wait_timeout(60):if need_fixing:b.wait_in_text("#dialog", "Add Network Bound Disk Encryption")b.wait_in_text("#dialog", "The clevis-systemd package must be installed")self.dialog_apply()b.wait_in_text("#dialog", "PackageKit or dnf5daemon-server is not installed")self.dialog_cancel()self.dialog_wait_close()# Manually install the missing package, Cockpit should# be happy with that even when PackageKit is not# available. Also disable the unit, for variety.# Cockpit will enable it without needing explicit# confirmation.#m.execute("if type dnf; then dnf install -y clevis-systemd; else apt-get install -y clevis-systemd; fi")m.execute("systemctl disable --now clevis-luks-askpass.path")b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("type", "tang")self.dialog_set_val("tang_url", "http://10.111.112.5")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash with the Tang server")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()self.dialog_wait_close()b.wait_in_text(panel + "tr:nth-child(3)", "http://10.111.112.5")# This should bring the options back.#b.wait_in_text(self.card_desc("ext4 filesystem", "Mount point"), "after network")b.wait_in_text(self.card_desc("Encryption", "Options"), "_netdev")# Reboot#self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")self.wait_mounted("ext4 filesystem")# Remove one key on client#b.click(panel + 'tr[data-row-slot="1"] button[aria-label="Remove"]')self.confirm()b.wait_not_present(panel + 'tr[data-row-slot="1"]')@testlib.skipImage("TODO: don't know how to encrypt the rootfs", "debian-*", "ubuntu-*", "arch")# this doesn't work in ostree either, but we don't run storage tests there@testlib.skipWsContainer("newly built root doesn't contain ws container")@testlib.timeout(1200)def testRootReboot(self):m = self.machineb = self.browsertang_m = self.machines["tang"]tang_m.execute("systemctl start tangd.socket")tang_m.execute("firewall-cmd --add-port 80/tcp")try:self.modify_rootfs(passphrase="einszweidrei")except Exception:console_screenshot(m, "failed-encrypt.ppm")raiseself.assertIn("crypt", m.execute("lsblk -snlo TYPE $(findmnt -no SOURCE /)"))self.addPackageSet("clevis")self.enableRepo()self.login_and_go("/storage")# While we are here, let's quickly test that Cockpit warns# about renaming the volume group that contains the root# filesystem.b.go("#/vg/root")b.click(self.card_desc_action("LVM2 volume group", "Name"))self.dialog_wait_open()b.wait_in_text("#dialog", "This volume group contains the root filesystem.")b.wait_visible("#dialog button.apply.pf-m-danger")self.dialog_cancel()self.dialog_wait_close()b.go("#/")# Add a clevis key and then reboot.## We also remove the original passphrase in order to be sure# that it was in fact clevis that has unlocked the rootfs, and# not the magic provided by "modify_rootfs"self.click_card_row("Storage", location="root")panel = "#encryption-keys "b.wait_in_text(panel + "tr:nth-child(1)", "Passphrase")with b.wait_timeout(360):b.click(self.card("Encryption") + " [aria-label='Add']")self.dialog_wait_open()self.dialog_wait_apply_enabled()self.dialog_set_val("type", "tang")self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_set_val("passphrase", "einszweidrei")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash with the Tang server")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()b.wait_in_text("#dialog", "Add Network Bound Disk Encryption")b.wait_in_text("#dialog", "The clevis-dracut package must be installed")b.wait_in_text("#dialog", "The initrd must be regenerated")self.dialog_apply()self.dialog_wait_close()b.click(panel + "tr:nth-child(1) button[aria-label='Remove']")b.click("#force-remove-passphrase")b.click("button:contains('Remove')")b.wait_in_text(panel + "tr:nth-child(1)", "Keyserver")# Tell the initrd to configure our special inter-machine# network that has the "tang" machine.routes = json.loads(m.execute("ip --json route show 10.111.112.0/20"))iface = routes[0]["dev"]m.execute(f"grubby --update-kernel=ALL --args='ip=10.111.112.1::10.111.112.1:255.255.255.0::{iface}:off'")try:self.reboot(timeout_sec=300)except Exception:console_screenshot(m, "failed-reboot.ppm")raisem.start_cockpit()b.relogin()b.enter_page("/storage")self.assertIn("crypt", m.execute("lsblk -snlo TYPE $(findmnt -no SOURCE /)"))if __name__ == '__main__':testlib.test_main()