Save encryption configuration (#1672)

* Save encryption configuration

* Fix deserialization problem

* Added .part_uuid to MapperDev

---------

Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
Co-authored-by: Anton Hvornum <anton@hvornum.se>
Co-authored-by: Anton Hvornum <anton.feeds+github@gmail.com>
This commit is contained in:
Daniel Girtler 2023-03-29 21:48:11 +11:00 committed by GitHub
parent b2fc71c9e5
commit 83f4b4178f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 22 deletions

View File

@ -240,6 +240,14 @@ def load_config():
superusers = arguments.get('!superusers', None) superusers = arguments.get('!superusers', None)
arguments['!users'] = User.parse_arguments(users, superusers) arguments['!users'] = User.parse_arguments(users, superusers)
if arguments.get('disk_encryption', None) is not None and arguments.get('disk_layouts', None) is not None:
password = arguments.get('encryption_password', '')
arguments['disk_encryption'] = DiskEncryption.parse_arg(
arguments['disk_layouts'],
arguments['disk_encryption'],
password
)
def post_process_arguments(arguments): def post_process_arguments(arguments):
storage['arguments'] = arguments storage['arguments'] = arguments

View File

@ -44,7 +44,7 @@ class ConfigurationOutput:
self._disk_layout_file = "user_disk_layout.json" self._disk_layout_file = "user_disk_layout.json"
self._sensitive = ['!users'] self._sensitive = ['!users']
self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run', 'disk_encryption'] self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run']
self._process_config() self._process_config()
@ -71,6 +71,9 @@ class ConfigurationOutput:
else: else:
self._user_config[key] = self._config[key] self._user_config[key] = self._config[key]
if key == 'disk_encryption': # special handling for encryption password
self._user_credentials['encryption_password'] = self._config[key].encryption_password
def user_config_to_json(self) -> str: def user_config_to_json(self) -> str:
return json.dumps({ return json.dumps({
'config_version': storage['__version__'], # Tells us what version was used to generate the config 'config_version': storage['__version__'], # Tells us what version was used to generate the config

View File

@ -46,7 +46,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
Selector( Selector(
_('Partitions'), _('Partitions'),
func=lambda preset: select_partitions_to_encrypt(self._disk_layouts, preset), func=lambda preset: select_partitions_to_encrypt(self._disk_layouts, preset),
display_func=lambda x: f'{len(x)} {_("Partitions")}' if x else None, display_func=lambda x: f'{sum([len(y) for y in x.values()])} {_("Partitions")}' if x else None,
dependencies=['encryption_password'], dependencies=['encryption_password'],
default=self._preset.partitions, default=self._preset.partitions,
preview_func=self._prev_disk_layouts, preview_func=self._prev_disk_layouts,
@ -86,9 +86,14 @@ class DiskEncryptionMenu(AbstractSubMenu):
def _prev_disk_layouts(self) -> Optional[str]: def _prev_disk_layouts(self) -> Optional[str]:
selector = self._menu_options['partitions'] selector = self._menu_options['partitions']
if selector.has_selection(): if selector.has_selection():
partitions: List[Any] = selector.current_selection partitions: Dict[str, Any] = selector.current_selection
all_partitions = []
for parts in partitions.values():
all_partitions += parts
output = str(_('Partitions to be encrypted')) + '\n' output = str(_('Partitions to be encrypted')) + '\n'
output += current_partition_layout(partitions, with_title=False) output += current_partition_layout(all_partitions, with_title=False)
return output.rstrip() return output.rstrip()
return None return None
@ -132,7 +137,7 @@ def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]:
return None return None
def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: List[Any]) -> List[Any]: def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: Dict[str, Any]) -> Dict[str, Any]:
# If no partitions was marked as encrypted, but a password was supplied and we have some disks to format.. # If no partitions was marked as encrypted, but a password was supplied and we have some disks to format..
# Then we need to identify which partitions to encrypt. This will default to / (root). # Then we need to identify which partitions to encrypt. This will default to / (root).
all_partitions = [] all_partitions = []
@ -153,10 +158,17 @@ def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: List[Any]
match choice.type_: match choice.type_:
case MenuSelectionType.Reset: case MenuSelectionType.Reset:
return [] return {}
case MenuSelectionType.Skip: case MenuSelectionType.Skip:
return preset return preset
case MenuSelectionType.Selection: case MenuSelectionType.Selection:
return choice.value # type: ignore selections: List[Any] = choice.value # type: ignore
partitions = {}
return [] for path, device in disk_layouts.items():
for part in selections:
if part in device.get('partitions', []):
partitions.setdefault(path, []).append(part)
return partitions
return {}

View File

@ -113,7 +113,7 @@ class Filesystem:
format_options = partition.get('options',[]) + partition.get('filesystem',{}).get('format_options',[]) format_options = partition.get('options',[]) + partition.get('filesystem',{}).get('format_options',[])
disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption')
if disk_encryption and partition in disk_encryption.partitions: if disk_encryption and partition in disk_encryption.all_partitions:
if not partition['device_instance']: if not partition['device_instance']:
raise DiskError(f"Internal error caused us to loose the partition. Please report this issue upstream!") raise DiskError(f"Internal error caused us to loose the partition. Please report this issue upstream!")

View File

@ -1,9 +1,11 @@
from __future__ import annotations
import getpass import getpass
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import List from typing import List, Dict
from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes
from ..disk.partition import Partition from ..disk.partition import Partition
@ -16,6 +18,21 @@ class Fido2Device:
manufacturer: str manufacturer: str
product: str product: str
def json(self) -> Dict[str, str]:
return {
'path': str(self.path),
'manufacturer': self.manufacturer,
'product': self.product
}
@classmethod
def parse_arg(cls, arg: Dict[str, str]) -> 'Fido2Device':
return Fido2Device(
Path(arg['path']),
arg['manufacturer'],
arg['product']
)
class Fido2: class Fido2:
_loaded: bool = False _loaded: bool = False

View File

@ -248,7 +248,7 @@ class Installer:
# we manage the encrypted partititons # we manage the encrypted partititons
if self._disk_encryption: if self._disk_encryption:
for partition in self._disk_encryption.partitions: for partition in self._disk_encryption.all_partitions:
# open the luks device and all associate stuff # open the luks device and all associate stuff
loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}" loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}"

View File

@ -279,8 +279,8 @@ class GlobalMenu(AbstractMenu):
output = str(_('Encryption type')) + f': {enc_type}\n' output = str(_('Encryption type')) + f': {enc_type}\n'
output += str(_('Password')) + f': {secret(encryption.encryption_password)}\n' output += str(_('Password')) + f': {secret(encryption.encryption_password)}\n'
if encryption.partitions: if encryption.all_partitions:
output += 'Partitions: {} selected'.format(len(encryption.partitions)) + '\n' output += 'Partitions: {} selected'.format(len(encryption.all_partitions)) + '\n'
if encryption.hsm_device: if encryption.hsm_device:
output += f'HSM: {encryption.hsm_device.manufacturer}' output += f'HSM: {encryption.hsm_device.manufacturer}'

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, auto from enum import Enum
from typing import Optional, List, Dict, TYPE_CHECKING, Any from typing import Optional, List, Dict, TYPE_CHECKING, Any
from ..hsm.fido import Fido2Device from ..hsm.fido import Fido2Device
@ -9,8 +11,7 @@ if TYPE_CHECKING:
class EncryptionType(Enum): class EncryptionType(Enum):
Partition = auto() Partition = 'partition'
# FullDiskEncryption = auto()
@classmethod @classmethod
def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']: def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']:
@ -35,9 +36,55 @@ class EncryptionType(Enum):
class DiskEncryption: class DiskEncryption:
encryption_type: EncryptionType = EncryptionType.Partition encryption_type: EncryptionType = EncryptionType.Partition
encryption_password: str = '' encryption_password: str = ''
partitions: List[str] = field(default_factory=list) partitions: Dict[str, List[Dict[str, Any]]] = field(default_factory=dict)
hsm_device: Optional[Fido2Device] = None hsm_device: Optional[Fido2Device] = None
def generate_encryption_file(self, partition) -> bool: @property
return partition in self.partitions and partition['mountpoint'] != '/' def all_partitions(self) -> List[Dict[str, Any]]:
_all: List[Dict[str, Any]] = []
for parts in self.partitions.values():
_all += parts
return _all
def generate_encryption_file(self, partition) -> bool:
return partition in self.all_partitions and partition['mountpoint'] != '/'
def json(self) -> Dict[str, Any]:
obj = {
'encryption_type': self.encryption_type.value,
'partitions': self.partitions
}
if self.hsm_device:
obj['hsm_device'] = self.hsm_device.json()
return obj
@classmethod
def parse_arg(
cls,
disk_layout: Dict[str, Any],
arg: Dict[str, Any],
password: str = ''
) -> 'DiskEncryption':
# we have to map the enc partition config to the disk layout objects
# they both need to point to the same object as it will get modified
# during the installation process
enc_partitions: Dict[str, List[Dict[str, Any]]] = {}
for path, partitions in disk_layout.items():
conf_partitions = arg['partitions'].get(path, [])
for part in partitions['partitions']:
if part in conf_partitions:
enc_partitions.setdefault(path, []).append(part)
enc = DiskEncryption(
EncryptionType(arg['encryption_type']),
password,
enc_partitions
)
if hsm := arg.get('hsm_device', None):
enc.hsm_device = Fido2Device.parse_arg(hsm)
return enc

View File

@ -60,7 +60,6 @@ def select_harddrives(preset: List[str] = []) -> List[str]:
selected_harddrive = Menu( selected_harddrive = Menu(
title, title,
list(options.keys()), list(options.keys()),
preset_values=preset,
multi=True, multi=True,
allow_reset=True, allow_reset=True,
allow_reset_warning_msg=warning allow_reset_warning_msg=warning