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)
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):
storage['arguments'] = arguments

View File

@ -44,7 +44,7 @@ class ConfigurationOutput:
self._disk_layout_file = "user_disk_layout.json"
self._sensitive = ['!users']
self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run', 'disk_encryption']
self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run']
self._process_config()
@ -71,6 +71,9 @@ class ConfigurationOutput:
else:
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:
return json.dumps({
'config_version': storage['__version__'], # Tells us what version was used to generate the config

View File

@ -46,7 +46,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
Selector(
_('Partitions'),
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'],
default=self._preset.partitions,
preview_func=self._prev_disk_layouts,
@ -86,9 +86,14 @@ class DiskEncryptionMenu(AbstractSubMenu):
def _prev_disk_layouts(self) -> Optional[str]:
selector = self._menu_options['partitions']
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 += current_partition_layout(partitions, with_title=False)
output += current_partition_layout(all_partitions, with_title=False)
return output.rstrip()
return None
@ -132,7 +137,7 @@ def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]:
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..
# Then we need to identify which partitions to encrypt. This will default to / (root).
all_partitions = []
@ -153,10 +158,17 @@ def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: List[Any]
match choice.type_:
case MenuSelectionType.Reset:
return []
return {}
case MenuSelectionType.Skip:
return preset
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',[])
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']:
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 logging
from dataclasses import dataclass
from pathlib import Path
from typing import List
from typing import List, Dict
from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes
from ..disk.partition import Partition
@ -16,6 +18,21 @@ class Fido2Device:
manufacturer: 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:
_loaded: bool = False

View File

@ -248,7 +248,7 @@ class Installer:
# we manage the encrypted partititons
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
loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}"
@ -324,7 +324,7 @@ class Installer:
file = f"/{file}"
if len(file.strip()) <= 0 or file == '/':
raise ValueError(f"The filename for the swap file has to be a valid path, not: {self.target}{file}")
SysCommand(f'dd if=/dev/zero of={self.target}{file} bs={size} count=1')
SysCommand(f'chmod 0600 {self.target}{file}')
SysCommand(f'mkswap {self.target}{file}')
@ -452,7 +452,7 @@ class Installer:
if hasattr(plugin, 'on_genfstab'):
if plugin.on_genfstab(self) is True:
break
with open(f"{self.target}/etc/fstab", 'a') as fstab_fh:
for entry in self.FSTAB_ENTRIES:
fstab_fh.write(f'{entry}\n')

View File

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

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum, auto
from enum import Enum
from typing import Optional, List, Dict, TYPE_CHECKING, Any
from ..hsm.fido import Fido2Device
@ -9,8 +11,7 @@ if TYPE_CHECKING:
class EncryptionType(Enum):
Partition = auto()
# FullDiskEncryption = auto()
Partition = 'partition'
@classmethod
def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']:
@ -35,9 +36,55 @@ class EncryptionType(Enum):
class DiskEncryption:
encryption_type: EncryptionType = EncryptionType.Partition
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
@property
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.partitions and partition['mountpoint'] != '/'
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(
title,
list(options.keys()),
preset_values=preset,
multi=True,
allow_reset=True,
allow_reset_warning_msg=warning