archinstall/archinstall/lib/disk/encryption_menu.py

289 lines
8.4 KiB
Python

from pathlib import Path
from typing import Dict, Optional, Any, TYPE_CHECKING, List
from . import LvmConfiguration, LvmVolume
from ..disk import (
DeviceModification,
DiskLayoutConfiguration,
PartitionModification,
DiskEncryption,
EncryptionType
)
from ..menu import (
Selector,
AbstractSubMenu,
MenuSelectionType,
TableMenu
)
from ..interactions.utils import get_password
from ..menu import Menu
from ..general import secret
from .fido import Fido2Device, Fido2
from ..output import FormattedOutput
if TYPE_CHECKING:
_: Any
class DiskEncryptionMenu(AbstractSubMenu):
def __init__(
self,
disk_config: DiskLayoutConfiguration,
data_store: Dict[str, Any],
preset: Optional[DiskEncryption] = None
):
if preset:
self._preset = preset
else:
self._preset = DiskEncryption()
self._disk_config = disk_config
super().__init__(data_store=data_store)
def setup_selection_menu_options(self):
self._menu_options['encryption_type'] = \
Selector(
_('Encryption type'),
func=lambda preset: select_encryption_type(self._disk_config, preset),
display_func=lambda x: EncryptionType.type_to_text(x) if x else None,
default=self._preset.encryption_type,
enabled=True,
)
self._menu_options['encryption_password'] = \
Selector(
_('Encryption password'),
lambda x: select_encrypted_password(),
dependencies=[self._check_dep_enc_type],
display_func=lambda x: secret(x) if x else '',
default=self._preset.encryption_password,
enabled=True
)
self._menu_options['partitions'] = \
Selector(
_('Partitions'),
func=lambda preset: select_partitions_to_encrypt(self._disk_config.device_modifications, preset),
display_func=lambda x: f'{len(x)} {_("Partitions")}' if x else None,
dependencies=[self._check_dep_partitions],
default=self._preset.partitions,
preview_func=self._prev_partitions,
enabled=True
)
self._menu_options['lvm_vols'] = \
Selector(
_('LVM volumes'),
func=lambda preset: self._select_lvm_vols(preset),
display_func=lambda x: f'{len(x)} {_("LVM volumes")}' if x else None,
dependencies=[self._check_dep_lvm_vols],
default=self._preset.lvm_volumes,
preview_func=self._prev_lvm_vols,
enabled=True
)
self._menu_options['HSM'] = \
Selector(
description=_('Use HSM to unlock encrypted drive'),
func=lambda preset: select_hsm(preset),
display_func=lambda x: self._display_hsm(x),
preview_func=self._prev_hsm,
dependencies=[self._check_dep_enc_type],
default=self._preset.hsm_device,
enabled=True
)
def _select_lvm_vols(self, preset: List[LvmVolume]) -> List[LvmVolume]:
if self._disk_config.lvm_config:
return select_lvm_vols_to_encrypt(self._disk_config.lvm_config, preset=preset)
return []
def _check_dep_enc_type(self) -> bool:
enc_type: Optional[EncryptionType] = self._menu_options['encryption_type'].current_selection
if enc_type and enc_type != EncryptionType.NoEncryption:
return True
return False
def _check_dep_partitions(self) -> bool:
enc_type: Optional[EncryptionType] = self._menu_options['encryption_type'].current_selection
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
return True
return False
def _check_dep_lvm_vols(self) -> bool:
enc_type: Optional[EncryptionType] = self._menu_options['encryption_type'].current_selection
if enc_type and enc_type == EncryptionType.LuksOnLvm:
return True
return False
def run(self, allow_reset: bool = True) -> Optional[DiskEncryption]:
super().run(allow_reset=allow_reset)
enc_type = self._data_store.get('encryption_type', None)
enc_password = self._data_store.get('encryption_password', None)
enc_partitions = self._data_store.get('partitions', None)
enc_lvm_vols = self._data_store.get('lvm_vols', None)
if enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks] and enc_partitions:
enc_lvm_vols = []
if enc_type == EncryptionType.LuksOnLvm:
enc_partitions = []
if enc_type != EncryptionType.NoEncryption and enc_password and (enc_partitions or enc_lvm_vols):
return DiskEncryption(
encryption_password=enc_password,
encryption_type=enc_type,
partitions=enc_partitions,
lvm_volumes=enc_lvm_vols,
hsm_device=self._data_store.get('HSM', None)
)
return None
def _display_hsm(self, device: Optional[Fido2Device]) -> Optional[str]:
if device:
return device.manufacturer
return None
def _prev_partitions(self) -> Optional[str]:
partitions: Optional[List[PartitionModification]] = self._menu_options['partitions'].current_selection
if partitions:
output = str(_('Partitions to be encrypted')) + '\n'
output += FormattedOutput.as_table(partitions)
return output.rstrip()
return None
def _prev_lvm_vols(self) -> Optional[str]:
volumes: Optional[List[PartitionModification]] = self._menu_options['lvm_vols'].current_selection
if volumes:
output = str(_('LVM volumes to be encrypted')) + '\n'
output += FormattedOutput.as_table(volumes)
return output.rstrip()
return None
def _prev_hsm(self) -> Optional[str]:
try:
Fido2.get_fido2_devices()
except ValueError:
return str(_('Unable to determine fido2 devices. Is libfido2 installed?'))
fido_device: Optional[Fido2Device] = self._menu_options['HSM'].current_selection
if fido_device:
output = '{}: {}'.format(str(_('Path')), fido_device.path)
output += '{}: {}'.format(str(_('Manufacturer')), fido_device.manufacturer)
output += '{}: {}'.format(str(_('Product')), fido_device.product)
return output
return None
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> Optional[EncryptionType]:
title = str(_('Select disk encryption option'))
if disk_config.lvm_config:
options = [
EncryptionType.type_to_text(EncryptionType.LvmOnLuks),
EncryptionType.type_to_text(EncryptionType.LuksOnLvm)
]
else:
options = [EncryptionType.type_to_text(EncryptionType.Luks)]
preset_value = EncryptionType.type_to_text(preset)
choice = Menu(title, options, preset_values=preset_value).run()
match choice.type_:
case MenuSelectionType.Reset: return None
case MenuSelectionType.Skip: return preset
case MenuSelectionType.Selection: return EncryptionType.text_to_type(choice.value) # type: ignore
def select_encrypted_password() -> Optional[str]:
if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))):
return passwd
return None
def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]:
title = _('Select a FIDO2 device to use for HSM')
try:
fido_devices = Fido2.get_fido2_devices()
except ValueError:
return None
if fido_devices:
choice = TableMenu(title, data=fido_devices).run()
match choice.type_:
case MenuSelectionType.Reset:
return None
case MenuSelectionType.Skip:
return preset
case MenuSelectionType.Selection:
return choice.value # type: ignore
return None
def select_partitions_to_encrypt(
modification: List[DeviceModification],
preset: List[PartitionModification]
) -> List[PartitionModification]:
partitions: List[PartitionModification] = []
# do not allow encrypting the boot partition
for mod in modification:
partitions += list(filter(lambda x: x.mountpoint != Path('/boot'), mod.partitions))
# do not allow encrypting existing partitions that are not marked as wipe
avail_partitions = list(filter(lambda x: not x.exists(), partitions))
if avail_partitions:
title = str(_('Select which partitions to encrypt'))
partition_table = FormattedOutput.as_table(avail_partitions)
choice = TableMenu(
title,
table_data=(avail_partitions, partition_table),
preset=preset,
multi=True
).run()
match choice.type_:
case MenuSelectionType.Reset:
return []
case MenuSelectionType.Skip:
return preset
case MenuSelectionType.Selection:
return choice.multi_value
return []
def select_lvm_vols_to_encrypt(
lvm_config: LvmConfiguration,
preset: List[LvmVolume]
) -> List[LvmVolume]:
volumes: List[LvmVolume] = lvm_config.get_all_volumes()
if volumes:
title = str(_('Select which LVM volumes to encrypt'))
partition_table = FormattedOutput.as_table(volumes)
choice = TableMenu(
title,
table_data=(volumes, partition_table),
preset=preset,
multi=True
).run()
match choice.type_:
case MenuSelectionType.Reset:
return []
case MenuSelectionType.Skip:
return preset
case MenuSelectionType.Selection:
return choice.multi_value
return []