filesystem: add GUI support for recovery-key
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
d8ebc56b69
commit
2d8a2b669e
|
@ -14,6 +14,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import pathlib
|
||||
from typing import Optional
|
||||
|
||||
import attr
|
||||
from urwid import Text, connect_signal
|
||||
|
@ -26,6 +28,7 @@ from subiquity.common.types import (
|
|||
GuidedStorageTargetManual,
|
||||
GuidedStorageTargetReformat,
|
||||
Partition,
|
||||
RecoveryKey,
|
||||
)
|
||||
from subiquity.models.filesystem import humanize_size
|
||||
from subiquitycore.ui.buttons import other_btn
|
||||
|
@ -53,6 +56,15 @@ subtitle = _("Configure a guided storage layout, or create a custom one:")
|
|||
class LUKSOptionsForm(SubForm):
|
||||
passphrase = PasswordField(_("Passphrase:"))
|
||||
confirm_passphrase = PasswordField(_("Confirm passphrase:"))
|
||||
recovery_key = BooleanField(
|
||||
("Also create a recovery key"),
|
||||
help=_(
|
||||
"The key will be stored as"
|
||||
" ~/recovery-key.txt in the live system and will"
|
||||
" be copied to /var/log/installer/ in the target"
|
||||
" system."
|
||||
),
|
||||
)
|
||||
|
||||
def validate_passphrase(self):
|
||||
if len(self.passphrase.value) < 1:
|
||||
|
@ -368,6 +380,7 @@ class GuidedDiskSelectionView(BaseView):
|
|||
target = guided_choice["disk"]
|
||||
tpm_choice = self.form.guided_choice.widget.form.tpm_choice
|
||||
password = None
|
||||
recovery_key: Optional[RecoveryKey] = None
|
||||
if tpm_choice is not None:
|
||||
if guided_choice.get("use_tpm", tpm_choice.default):
|
||||
capability = GuidedCapability.CORE_BOOT_ENCRYPTED
|
||||
|
@ -378,6 +391,16 @@ class GuidedDiskSelectionView(BaseView):
|
|||
if opts.get("encrypt", False):
|
||||
capability = GuidedCapability.LVM_LUKS
|
||||
password = opts["luks_options"]["passphrase"]
|
||||
if opts["luks_options"]["recovery_key"]:
|
||||
# There is only one encrypted LUKS (at max) in guided
|
||||
# so no need to prefix the locations with the name of
|
||||
# the VG.
|
||||
recovery_key = RecoveryKey(
|
||||
live_location=str(
|
||||
pathlib.Path("~/recovery-key.txt").expanduser()
|
||||
),
|
||||
backup_location="var/log/installer/recovery-key.txt",
|
||||
)
|
||||
else:
|
||||
capability = GuidedCapability.LVM
|
||||
else:
|
||||
|
@ -389,6 +412,7 @@ class GuidedDiskSelectionView(BaseView):
|
|||
target=target,
|
||||
capability=capability,
|
||||
password=password,
|
||||
recovery_key=recovery_key,
|
||||
)
|
||||
else:
|
||||
choice = GuidedChoiceV2(
|
||||
|
|
|
@ -15,11 +15,12 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
from urwid import Text, connect_signal
|
||||
|
||||
from subiquity.models.filesystem import get_lvm_size, humanize_size
|
||||
from subiquity.models.filesystem import RecoveryKeyHandler, get_lvm_size, humanize_size
|
||||
from subiquity.ui.views.filesystem.compound import (
|
||||
CompoundDiskForm,
|
||||
MultiDeviceField,
|
||||
|
@ -78,10 +79,22 @@ class VolGroupForm(CompoundDiskForm):
|
|||
encrypt = BooleanField(_("Create encrypted volume"))
|
||||
passphrase = PasswordField(_("Passphrase:"))
|
||||
confirm_passphrase = PasswordField(_("Confirm passphrase:"))
|
||||
# TODO replace the placeholders in the help - also potentially replacing
|
||||
# "~" with the actual home directory.
|
||||
create_recovery_key = BooleanField(
|
||||
_("Also create a recovery key:"),
|
||||
help=_(
|
||||
"The key will be stored as"
|
||||
" ~/recovery-key-{name}.txt in the live system and will"
|
||||
" be copied to /var/log/installer/ in the target"
|
||||
" system."
|
||||
),
|
||||
)
|
||||
|
||||
def _change_encrypt(self, sender, new_value):
|
||||
self.passphrase.enabled = new_value
|
||||
self.confirm_passphrase.enabled = new_value
|
||||
self.create_recovery_key.enabled = new_value
|
||||
if not new_value:
|
||||
self.passphrase.validate()
|
||||
self.confirm_passphrase.validate()
|
||||
|
@ -147,6 +160,7 @@ class VolGroupStretchy(Stretchy):
|
|||
devices = {}
|
||||
key = ""
|
||||
encrypt = False
|
||||
create_recovery_key = False
|
||||
for d in existing.devices:
|
||||
if d.type == "dm_crypt":
|
||||
encrypt = True
|
||||
|
@ -161,6 +175,7 @@ class VolGroupStretchy(Stretchy):
|
|||
# TODO make this more user friendly.
|
||||
if d.key is not None:
|
||||
key = d.key
|
||||
create_recovery_key = d.recovery_key is not None
|
||||
d = d.volume
|
||||
devices[d] = "active"
|
||||
initial = {
|
||||
|
@ -169,6 +184,7 @@ class VolGroupStretchy(Stretchy):
|
|||
"encrypt": encrypt,
|
||||
"passphrase": key,
|
||||
"confirm_passphrase": key,
|
||||
"create_recovery_key": create_recovery_key,
|
||||
}
|
||||
|
||||
possible_components = get_possible_components(
|
||||
|
@ -205,6 +221,15 @@ class VolGroupStretchy(Stretchy):
|
|||
del result["size"]
|
||||
mdc = self.form.devices.widget
|
||||
result["devices"] = mdc.active_devices
|
||||
if "create_recovery_key" in result:
|
||||
if result["create_recovery_key"]:
|
||||
backup_prefix = pathlib.Path("/var/log/installer")
|
||||
filename = pathlib.Path(f"recovery-key-{result['name']}.txt")
|
||||
result["recovery-key"] = RecoveryKeyHandler(
|
||||
live_location=pathlib.Path("~").expanduser() / filename,
|
||||
backup_location=backup_prefix / filename,
|
||||
)
|
||||
del result["create_recovery_key"]
|
||||
if "confirm_passphrase" in result:
|
||||
del result["confirm_passphrase"]
|
||||
safe_result = result.copy()
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import urwid
|
||||
|
||||
from subiquity.client.controllers.filesystem import FilesystemController
|
||||
from subiquity.models.filesystem import RecoveryKeyHandler
|
||||
from subiquity.ui.views.filesystem.lvm import VolGroupStretchy
|
||||
from subiquity.ui.views.filesystem.tests.test_partition import make_model_and_disk
|
||||
from subiquitycore.testing import view_helpers
|
||||
|
@ -66,6 +69,7 @@ class LVMViewTests(unittest.TestCase):
|
|||
"encrypt": True,
|
||||
"passphrase": "passw0rd",
|
||||
"confirm_passphrase": "passw0rd",
|
||||
"create_recovery_key": False,
|
||||
}
|
||||
expected_data = {
|
||||
"name": "vg1",
|
||||
|
@ -76,3 +80,31 @@ class LVMViewTests(unittest.TestCase):
|
|||
view_helpers.enter_data(stretchy.form, form_data)
|
||||
view_helpers.click(stretchy.form.done_btn.base_widget)
|
||||
view.controller.volgroup_handler.assert_called_once_with(None, expected_data)
|
||||
|
||||
def test_create_vg_encrypted_with_recovery(self):
|
||||
model, disk = make_model_and_disk()
|
||||
part1 = model.add_partition(disk, size=10 * (2**30), offset=0)
|
||||
part2 = model.add_partition(disk, size=10 * (2**30), offset=10 * (2**30))
|
||||
view, stretchy = make_view(model)
|
||||
form_data = {
|
||||
"name": "vg1",
|
||||
"devices": {part1: "active", part2: "active"},
|
||||
"encrypt": True,
|
||||
"passphrase": "passw0rd",
|
||||
"confirm_passphrase": "passw0rd",
|
||||
"create_recovery_key": True,
|
||||
}
|
||||
expected_data = {
|
||||
"name": "vg1",
|
||||
"devices": {part1, part2},
|
||||
"encrypt": True,
|
||||
"passphrase": "passw0rd",
|
||||
"recovery-key": RecoveryKeyHandler(
|
||||
live_location=pathlib.Path("/home/ubuntu/recovery-key-vg1.txt"),
|
||||
backup_location=pathlib.Path("/var/log/installer/recovery-key-vg1.txt"),
|
||||
),
|
||||
}
|
||||
view_helpers.enter_data(stretchy.form, form_data)
|
||||
with mock.patch.dict(os.environ, {"HOME": "/home/ubuntu"}):
|
||||
view_helpers.click(stretchy.form.done_btn.base_widget)
|
||||
view.controller.volgroup_handler.assert_called_once_with(None, expected_data)
|
||||
|
|
Loading…
Reference in New Issue