727 lines
31 KiB
Python
Executable File
727 lines
31 KiB
Python
Executable File
#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)
|
|
|
|
# This file is part of Cockpit.
|
|
#
|
|
# Copyright (C) 2015 Red Hat, Inc.
|
|
#
|
|
# Cockpit is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation; either version 2.1 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Cockpit 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
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import subprocess
|
|
|
|
from packagelib import PackageCase
|
|
from storagelib import StorageCase
|
|
from testlib import attach, skipImage, test_main, timeout, wait
|
|
|
|
|
|
def console_screenshot(machine, name):
|
|
subprocess.run("virsh -c qemu:///session screenshot %s '%s'" % (str(machine._domain.ID()), name),
|
|
shell=True)
|
|
attach(name, move=True)
|
|
print("Wrote screenshot to " + name)
|
|
|
|
|
|
class TestStorageLuks(StorageCase):
|
|
|
|
# LUKS uses memory hard PBKDF, 1 GiB is not enough; see https://bugzilla.redhat.com/show_bug.cgi?id=1881829
|
|
provision = {
|
|
"0": {"memory_mb": 1536}
|
|
}
|
|
|
|
def testLuks(self):
|
|
self.allow_journal_messages("Device is not initialized.*", ".*could not be opened.")
|
|
m = self.machine
|
|
b = self.browser
|
|
|
|
mount_point_secret = "/run/secret"
|
|
|
|
self.login_and_go("/storage")
|
|
|
|
# Add a disk and partition it
|
|
m.add_disk("100M", serial="MYDISK")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
b.click('button:contains(Create partition table)')
|
|
self.dialog({"type": "gpt"})
|
|
self.content_row_wait_in_col(1, 0, "Free space")
|
|
|
|
self.assertEqual(m.execute("grep -v ^# /etc/crypttab || true").strip(), "")
|
|
|
|
# Format it with luks
|
|
self.content_row_action(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", 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()
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
self.content_tab_wait_in_info(1, 2, "Mount point", mount_point_secret)
|
|
|
|
dev = "/dev/sda1"
|
|
|
|
if self.default_crypto_type == "luks1":
|
|
self.content_tab_wait_in_info(1, 3, "Encryption type", "LUKS1")
|
|
elif self.default_crypto_type == "luks2":
|
|
self.content_tab_wait_in_info(1, 3, "Encryption type", "LUKS2")
|
|
|
|
uuid = m.execute(f"cryptsetup luksUUID {dev}").strip()
|
|
cleartext_dev = "/dev/mapper/luks-" + uuid
|
|
passphrase_path = "/etc/luks-keys/luks-" + uuid
|
|
|
|
self.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 test
|
|
date_no_mins = b.eval_js("Intl.DateTimeFormat('en', { dateStyle: 'medium', timeStyle: 'short' }).format()").split(':')[0]
|
|
self.content_tab_wait_in_info(1, 3, "Stored passphrase", f"Last modified: {date_no_mins}:")
|
|
|
|
tab = self.content_tab_expand(1, 3)
|
|
b.assert_pixels(tab, "tab", ignore=["dt:contains(Stored passphrase) + dd",
|
|
"dt:contains(Cleartext device) + dd"])
|
|
|
|
# Unmount. This locks it
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
self.assert_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")
|
|
self.content_tab_wait_in_info(1, 2, "Mount point", "The filesystem is not mounted")
|
|
|
|
# It should be listed on the Overview. Click it to come back
|
|
# here.
|
|
b.go("#/")
|
|
b.click(f"#mounts tr:contains({mount_point_secret})")
|
|
b.wait_visible("#storage-detail")
|
|
|
|
# Mount, this uses the stored passphrase for unlocking
|
|
self.content_row_action(1, "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.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
self.content_tab_info_action(1, 3, "Options")
|
|
self.dialog({"options": "weird,options"})
|
|
self.assert_in_configuration(dev, "crypttab", "options", "weird,options")
|
|
|
|
# Change stored passphrase
|
|
b.click(self.content_tab_info_label(1, 3, "Stored passphrase") + " + dd button")
|
|
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 it
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
self.assert_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")
|
|
b.wait_not_in_text("#detail-content", "ext4 filesystem")
|
|
|
|
# Mount, this tries the wrong passphrase but eventually prompts.
|
|
self.content_row_action(1, "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.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
|
|
self.wait_mounted(1, 2)
|
|
|
|
# Remove passphrase
|
|
b.click(self.content_tab_info_label(1, 3, "Stored passphrase") + " + dd button")
|
|
self.dialog({"passphrase": ""})
|
|
self.assert_in_configuration(dev, "crypttab", "passphrase-path", "")
|
|
|
|
# Unmount it
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
b.wait_not_in_text("#detail-content", "ext4 filesystem")
|
|
self.assert_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")
|
|
|
|
# Mount it readonly. This asks for a passphrase.
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog({"mount_options.ro": True,
|
|
"passphrase": "vainu-reku-toma-rolle-kaja"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
self.wait_mounted(1, 2)
|
|
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")
|
|
|
|
# Delete the partition.
|
|
self.content_dropdown_action(1, "Delete")
|
|
self.confirm()
|
|
self.content_row_wait_in_col(1, 0, "Free space")
|
|
b.wait_not_in_text("#detail-content", "ext4 filesystem")
|
|
# luksmeta-monitor-hack.py races with the partition deletion
|
|
self.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()
|
|
wait(lambda: m.execute("(loginctl list-users | grep admin) || true") == "")
|
|
|
|
def testLuks1Slots(self):
|
|
self.allow_journal_messages("Device is not initialized.*", ".*could not be opened.")
|
|
m = self.machine
|
|
b = 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")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
# create volume and passphrase
|
|
self.content_row_action(1, "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.content_row_wait_in_col(1, 2, "Filesystem (encrypted)")
|
|
self.content_tab_wait_in_info(1, 1, "Mount point", "The filesystem is not mounted")
|
|
self.content_tab_wait_in_info(1, 2, "Encryption type", "LUKS1")
|
|
self.content_tab_wait_in_info(1, 2, "Options", "nofail")
|
|
|
|
dev = "/dev/sda"
|
|
uuid = m.execute(f"cryptsetup luksUUID {dev}").strip()
|
|
cleartext_dev = "/dev/mapper/luks-" + uuid
|
|
|
|
# add one more passphrase
|
|
tab = self.content_tab_expand(1, 2)
|
|
panel = tab + " .pf-v5-c-card:contains(Keys) "
|
|
b.wait_visible(panel)
|
|
b.click(panel + "[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 passphrase
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")
|
|
# unlock with second passphrase
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
self.assert_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")
|
|
b.wait_not_in_text("#detail-content", "ext4 filesystem")
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja-1"})
|
|
self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
# delete second key slot
|
|
b.click(panel + "li:nth-child(2) button[aria-label=Remove]")
|
|
# do not accept the same passphrase
|
|
b.set_input_text("#remove-passphrase", "vainu-reku-toma-rolle-kaja-1")
|
|
b.click("button:contains('Remove')")
|
|
b.wait_in_text(".pf-v5-c-alert__title", "No key available with this passphrase.")
|
|
# delete with passphrase from slot 0
|
|
b.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 passphrase
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
self.assert_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_in_child_configuration(dev, "fstab", "opts", "noauto")
|
|
b.wait_not_in_text("#detail-content", "ext4 filesystem")
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog_wait_open()
|
|
self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja-1")
|
|
self.dialog_apply()
|
|
b.wait_visible(".pf-v5-c-alert")
|
|
self.assertIn(b.text("h4.pf-v5-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 LUKSv1
|
|
for i in range(1, 8):
|
|
b.click(panel + "button[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 inactive
|
|
b.wait_visible(panel + ".pf-v5-c-card__header button:disabled")
|
|
# check if edit button is inactive
|
|
slots_row = tab + " .pf-v5-c-card ul li:first-child"
|
|
b.wait_visible(slots_row + " button:disabled")
|
|
|
|
# remove one slot
|
|
slots_list = tab + " .pf-v5-c-card ul "
|
|
b.wait_visible(".pf-v5-c-data-list__cell:contains('Slot 7')")
|
|
b.click(slots_list + "li: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 slot
|
|
b.wait_not_present(slots_list + ":disabled")
|
|
b.wait_not_present(panel + ":disabled")
|
|
# remove slot 0, with the original passphrase
|
|
b.click(slots_list + "li: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 passphrase
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog_wait_open()
|
|
self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja")
|
|
self.dialog_apply()
|
|
b.wait_visible(".pf-v5-c-alert")
|
|
self.assertIn(b.text("h4.pf-v5-c-alert__title:not(span)").split("Danger alert:", 1).pop(), error_messages)
|
|
self.dialog_cancel()
|
|
# change one of the passphrases
|
|
b.wait_visible(slots_list + "li:last-child [aria-label=Edit]")
|
|
b.click(slots_list + "li: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 passphrase
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja-8"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
self.wait_mounted(1, 1)
|
|
self.assert_not_in_configuration(dev, "crypttab", "options", "noauto")
|
|
self.assert_not_in_configuration(cleartext_dev, "fstab", "opts", "noauto")
|
|
|
|
def testNoFsys(self):
|
|
m = self.machine
|
|
b = self.browser
|
|
|
|
self.login_and_go("/storage")
|
|
|
|
# Add a disk and format it with luks, but without filesystem
|
|
m.add_disk("50M", serial="MYDISK")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.content_row_action(1, "Format")
|
|
self.dialog({"type": "empty",
|
|
"crypto": self.default_crypto_type,
|
|
"passphrase": "vainu-reku-toma-rolle-kaja",
|
|
"passphrase2": "vainu-reku-toma-rolle-kaja"})
|
|
self.content_row_wait_in_col(1, 2, "Unrecognized data (encrypted)")
|
|
|
|
# Lock it
|
|
self.content_dropdown_action(1, "Lock")
|
|
self.content_row_wait_in_col(1, 2, "Locked encrypted data")
|
|
|
|
# Make it readonly
|
|
self.content_tab_info_action(1, 1, "Options")
|
|
self.dialog({"options": "readonly"})
|
|
self.assertNotEqual(m.execute("grep readonly /etc/crypttab"), "")
|
|
|
|
# Unlock it
|
|
self.content_row_action(1, "Unlock")
|
|
self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja"})
|
|
|
|
# Try to format it, just for kicks
|
|
self.content_row_action(1, "Format")
|
|
self.dialog({"type": "ext4",
|
|
"mount_point": "/run/foo"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
def testKeepKeys(self):
|
|
m = self.machine
|
|
b = self.browser
|
|
|
|
self.login_and_go("/storage")
|
|
|
|
# Add a disk and format it with luks and a filesystem
|
|
|
|
m.add_disk("50M", serial="MYDISK")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.content_row_action(1, "Format")
|
|
self.dialog({"type": "ext4",
|
|
"crypto": self.default_crypto_type,
|
|
"passphrase": "vainu-reku-toma-rolle-kaja",
|
|
"passphrase2": "vainu-reku-toma-rolle-kaja",
|
|
"mount_point": "/run/foo"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
# Format it again but keep the keys
|
|
|
|
self.content_dropdown_action(1, "Format")
|
|
self.dialog({"type": "ext4",
|
|
"crypto": " keep",
|
|
"mount_point": "/run/foo"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
# Unmount (and lock) it
|
|
|
|
self.content_dropdown_action(1, "Unmount")
|
|
self.confirm()
|
|
self.content_tab_wait_in_info(1, 1, "Mount point", "The filesystem is not mounted")
|
|
|
|
# Format it again and keep the keys. Because it's locked, we
|
|
# need the old passphrase.
|
|
|
|
self.content_dropdown_action(1, "Format")
|
|
self.dialog({"type": "ext4",
|
|
"crypto": " keep",
|
|
"old_passphrase": "vainu-reku-toma-rolle-kaja",
|
|
"mount_point": "/run/foo"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
def testReboot(self):
|
|
m = self.machine
|
|
b = self.browser
|
|
|
|
self.login_and_go("/storage")
|
|
|
|
# Add a disk and format it with luks and a filesystem, then
|
|
# reboot and check that the filesystem is mounted.
|
|
|
|
m.add_disk("50M", serial="MYDISK")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.content_row_action(1, "Format")
|
|
self.dialog({"type": "ext4",
|
|
"crypto": self.default_crypto_type,
|
|
"passphrase": "vainu-reku-toma-rolle-kaja",
|
|
"passphrase2": "vainu-reku-toma-rolle-kaja",
|
|
"mount_point": "/run/foo"})
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem (encrypted)")
|
|
|
|
self.setup_systemd_password_agent("vainu-reku-toma-rolle-kaja")
|
|
m.reboot()
|
|
m.start_cockpit()
|
|
b.relogin()
|
|
b.enter_page("/storage")
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.wait_mounted(1, 1)
|
|
|
|
|
|
class TestStorageNBDE(StorageCase, PackageCase):
|
|
provision = {
|
|
"0": {"address": "10.111.112.1/20", "memory_mb": 2048},
|
|
"tang": {"address": "10.111.112.5/20"}
|
|
}
|
|
|
|
def testBasic(self):
|
|
m = self.machine
|
|
b = 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 luks
|
|
m.add_disk("50M", serial="MYDISK")
|
|
b.wait_in_text("#drives", "MYDISK")
|
|
b.click('.sidepanel-row:contains("MYDISK")')
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.content_row_action(1, "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.content_row_wait_in_col(1, 2, "Filesystem (encrypted)")
|
|
self.content_tab_wait_in_info(1, 1, "Mount point", "The filesystem is not mounted")
|
|
|
|
self.content_tab_wait_in_info(1, 2, "Options", "nofail")
|
|
tab = self.content_tab_expand(1, 2)
|
|
panel = tab + " .pf-v5-c-card:contains(Keys) "
|
|
b.wait_visible(panel)
|
|
b.wait_in_text(panel + "ul li:nth-child(1)", "Passphrase")
|
|
|
|
# Add a key
|
|
#
|
|
b.click(panel + "[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", "Make sure the key hash from the Tang server matches")
|
|
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_visible(panel + "ul li:nth-child(2)")
|
|
b.wait_in_text(panel + "ul li:nth-child(2)", "10.111.112.5")
|
|
|
|
# Adding the key should add "_netdev" options
|
|
#
|
|
self.content_tab_wait_in_info(1, 1, "Mount point", "after network")
|
|
self.content_tab_wait_in_info(1, 2, "Options", "_netdev")
|
|
|
|
# Mount it. This should succeed without passphrase.
|
|
#
|
|
self.content_row_action(1, "Mount")
|
|
self.dialog_wait_open()
|
|
self.dialog_wait_val("mount_point", mount_point_secret)
|
|
self.dialog_apply()
|
|
self.dialog_wait_close()
|
|
self.content_row_wait_in_col(1, 2, "ext4 filesystem")
|
|
|
|
# Edit the key, without providing an existing passphrase
|
|
#
|
|
b.click(panel + "ul li: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", "Make sure the key hash from the Tang server matches")
|
|
b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())
|
|
self.dialog_apply()
|
|
self.dialog_wait_close()
|
|
b.wait_in_text(panel + "ul li: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.
|
|
#
|
|
self.content_tab_info_action(1, 1, "Mount point")
|
|
self.dialog({"at_boot": "nofail"})
|
|
self.content_tab_wait_in_info(1, 2, "Options", "nofail")
|
|
|
|
if need_fixing:
|
|
# Break things again, including PackageKit
|
|
m.execute("if type dnf; then dnf remove -y clevis-systemd; else apt-get purge -y clevis-systemd; fi")
|
|
m.execute("systemctl mask --now packagekit")
|
|
|
|
# Add a second key, this should try to fix things again, but
|
|
# we have to help with the package install
|
|
#
|
|
b.click(panel + "[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", "Make sure the key hash from the Tang server matches")
|
|
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 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(panel + "[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", "Make sure the key hash from the Tang server matches")
|
|
b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())
|
|
self.dialog_apply()
|
|
|
|
self.dialog_wait_close()
|
|
|
|
b.wait_visible(panel + "ul li:nth-child(3)")
|
|
b.wait_in_text(panel + "ul li:nth-child(3)", "http://10.111.112.5")
|
|
|
|
# This should bring the options back.
|
|
#
|
|
self.content_tab_wait_in_info(1, 1, "Mount point", "after network")
|
|
self.content_tab_wait_in_info(1, 2, "Options", "_netdev")
|
|
|
|
# Reboot
|
|
#
|
|
m.reboot()
|
|
m.start_cockpit()
|
|
b.relogin()
|
|
b.enter_page("/storage")
|
|
b.wait_visible("#storage-detail")
|
|
|
|
self.wait_mounted(1, 1)
|
|
|
|
# Remove one key on client
|
|
#
|
|
tab = self.content_tab_expand(1, 2)
|
|
panel = tab + " .pf-v5-c-card:contains(Keys) "
|
|
b.click(panel + 'ul li:contains("Slot 1") button[aria-label=Remove]')
|
|
self.confirm()
|
|
b.wait_not_present(panel + 'ul li:contains("Slot 1")')
|
|
|
|
@skipImage("TODO: don't know how to encrypt the rootfs", "debian-*", "ubuntu-*", "arch")
|
|
@timeout(1200)
|
|
def testRootReboot(self):
|
|
m = self.machine
|
|
b = self.browser
|
|
|
|
tang_m = self.machines["tang"]
|
|
tang_m.execute("systemctl start tangd.socket")
|
|
tang_m.execute("firewall-cmd --add-port 80/tcp")
|
|
|
|
try:
|
|
self.encrypt_root("einszweidrei")
|
|
except Exception:
|
|
console_screenshot(m, "failed-encrypt.ppm")
|
|
raise
|
|
|
|
self.assertIn("crypt", m.execute("lsblk -snlo TYPE $(findmnt -no SOURCE /)"))
|
|
|
|
self.addPackageSet("clevis")
|
|
self.enableRepo()
|
|
|
|
self.login_and_go("/storage")
|
|
|
|
# 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 "encrypt_root"
|
|
|
|
b.click('#devices .sidepanel-row:contains("/dev/root/")')
|
|
b.wait_visible("#storage-detail")
|
|
b.click('.sidepanel-row:contains("Encrypted partition")')
|
|
|
|
tab = self.content_tab_expand(2, 2)
|
|
panel = tab + " .pf-v5-c-card:contains(Keys) "
|
|
b.wait_visible(panel)
|
|
b.wait_in_text(panel + "ul li:nth-child(1)", "Passphrase")
|
|
|
|
with b.wait_timeout(360):
|
|
b.click(panel + "[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", "Make sure the key hash from the Tang server matches")
|
|
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 + "ul li:nth-child(1) button[aria-label=Remove]")
|
|
b.click("#force-remove-passphrase")
|
|
b.click("button:contains('Remove')")
|
|
b.wait_in_text(panel + "ul li:nth-child(1)", "Keyserver")
|
|
|
|
# Tell the initrd to configure our special inter-machine
|
|
# network that has the "tang" machine.
|
|
#
|
|
m.execute("grubby --update-kernel=ALL --args='ip=10.111.112.1::10.111.112.1:255.255.255.0::eth1:off'")
|
|
|
|
try:
|
|
m.reboot(timeout_sec=300)
|
|
except Exception:
|
|
console_screenshot(m, "failed-reboot.ppm")
|
|
raise
|
|
|
|
m.start_cockpit()
|
|
b.relogin()
|
|
b.enter_page("/storage")
|
|
|
|
self.assertIn("crypt", m.execute("lsblk -snlo TYPE $(findmnt -no SOURCE /)"))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_main()
|