Store password as hash instead of plaintext (#3276)
* Rework user password to be hash * Update * Update * Update * Update * Update * Update * Update * Update * Generate yescrypt hash * Update * Update * Update * Update
This commit is contained in:
parent
bae0e29e18
commit
4f1d1b4739
2
PKGBUILD
2
PKGBUILD
|
|
@ -19,6 +19,8 @@ depends=(
|
|||
'e2fsprogs'
|
||||
'glibc'
|
||||
'kbd'
|
||||
'libcrypt.so'
|
||||
'libxcrypt'
|
||||
'pciutils'
|
||||
'procps-ng'
|
||||
'python'
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from archinstall.lib.models.mirrors import MirrorConfiguration
|
|||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.output import error, warn
|
||||
from archinstall.lib.plugins import load_plugin
|
||||
from archinstall.lib.storage import storage
|
||||
|
|
@ -71,16 +71,16 @@ class ArchConfig:
|
|||
# Special fields that should be handle with care due to security implications
|
||||
users: list[User] = field(default_factory=list)
|
||||
disk_encryption: DiskEncryption | None = None
|
||||
root_password: str | None = None
|
||||
root_enc_password: Password | None = None
|
||||
|
||||
def unsafe_json(self) -> dict[str, Any]:
|
||||
config = {
|
||||
'!users': [user.json() for user in self.users],
|
||||
'!root-password': self.root_password,
|
||||
'users': [user.json() for user in self.users],
|
||||
'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else None,
|
||||
}
|
||||
|
||||
if self.disk_encryption:
|
||||
config['encryption_password'] = self.disk_encryption.encryption_password
|
||||
if self.disk_encryption and self.disk_encryption.encryption_password:
|
||||
config['encryption_password'] = self.disk_encryption.encryption_password.plaintext
|
||||
|
||||
return config
|
||||
|
||||
|
|
@ -149,10 +149,12 @@ class ArchConfig:
|
|||
if net_config := args_config.get('network_config', None):
|
||||
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
||||
|
||||
users = args_config.get('!users', None)
|
||||
superusers = args_config.get('!superusers', None)
|
||||
if users is not None or superusers is not None:
|
||||
arch_config.users = User.parse_arguments(users, superusers)
|
||||
# DEPRECATED: backwards copatibility
|
||||
if users := args_config.get('!users', None):
|
||||
arch_config.users = User.parse_arguments(users)
|
||||
|
||||
if users := args_config.get('users', None):
|
||||
arch_config.users = User.parse_arguments(users)
|
||||
|
||||
if bootloader_config := args_config.get('bootloader', None):
|
||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
||||
|
|
@ -167,7 +169,7 @@ class ArchConfig:
|
|||
arch_config.disk_encryption = DiskEncryption.parse_arg(
|
||||
arch_config.disk_config,
|
||||
args_config['disk_encryption'],
|
||||
args_config.get('encryption_password', '')
|
||||
Password(plaintext=args_config.get('encryption_password', ''))
|
||||
)
|
||||
|
||||
if hostname := args_config.get('hostname', ''):
|
||||
|
|
@ -192,8 +194,12 @@ class ArchConfig:
|
|||
if services := args_config.get('services', []):
|
||||
arch_config.services = services
|
||||
|
||||
# DEPRECATED: backwards compatibility
|
||||
if root_password := args_config.get('!root-password', None):
|
||||
arch_config.root_password = root_password
|
||||
arch_config.root_enc_password = Password(plaintext=root_password)
|
||||
|
||||
if enc_password := args_config.get('root_enc_password', None):
|
||||
arch_config.root_enc_password = Password(enc_password=enc_password)
|
||||
|
||||
if custom_commands := args_config.get('custom_commands', []):
|
||||
arch_config.custom_commands = custom_commands
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import ctypes
|
||||
import ctypes.util
|
||||
from pathlib import Path
|
||||
|
||||
from .output import debug
|
||||
|
||||
libcrypt = ctypes.CDLL("libcrypt.so")
|
||||
|
||||
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||
libcrypt.crypt.restype = ctypes.c_char_p
|
||||
|
||||
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
|
||||
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
|
||||
|
||||
LOGIN_DEFS = Path('/etc/login.defs')
|
||||
|
||||
|
||||
def _search_login_defs(key: str) -> str | None:
|
||||
defs = LOGIN_DEFS.read_text()
|
||||
for line in defs.split('\n'):
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
if line.startswith(key):
|
||||
value = line.split(' ')[1]
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
|
||||
if isinstance(prefix, str):
|
||||
prefix = prefix.encode('utf-8')
|
||||
|
||||
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
|
||||
|
||||
if setting is None:
|
||||
raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
|
||||
|
||||
return setting
|
||||
|
||||
|
||||
def crypt_yescrypt(plaintext: str) -> str:
|
||||
"""
|
||||
By default chpasswd in Arch uses PAM to to hash the password with crypt_yescrypt
|
||||
the PAM code https://github.com/linux-pam/linux-pam/blob/master/modules/pam_unix/support.c
|
||||
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
|
||||
If no value was specified (or commented out) a default of 5 is choosen
|
||||
"""
|
||||
value = _search_login_defs('YESCRYPT_COST_FACTOR')
|
||||
if value is not None:
|
||||
rounds = int(value)
|
||||
if rounds < 3:
|
||||
rounds = 3
|
||||
elif rounds > 11:
|
||||
rounds = 11
|
||||
else:
|
||||
rounds = 5
|
||||
|
||||
debug(f'Creating yescrypt hash with rounds {rounds}')
|
||||
|
||||
enc_plaintext = plaintext.encode('utf-8')
|
||||
salt = crypt_gen_salt('$y$', rounds)
|
||||
|
||||
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
|
||||
|
||||
if crypt_hash is None:
|
||||
raise ValueError('crypt() returned NULL')
|
||||
|
||||
return crypt_hash.decode('utf-8')
|
||||
|
|
@ -38,6 +38,7 @@ from ..models.device_model import (
|
|||
_DeviceInfo,
|
||||
_PartitionInfo,
|
||||
)
|
||||
from ..models.users import Password
|
||||
from ..output import debug, error, info, log
|
||||
from ..utils.util import is_subpath
|
||||
from .utils import (
|
||||
|
|
@ -307,7 +308,7 @@ class DeviceHandler:
|
|||
self,
|
||||
dev_path: Path,
|
||||
mapper_name: str | None,
|
||||
enc_password: str,
|
||||
enc_password: Password | None,
|
||||
lock_after_create: bool = True
|
||||
) -> Luks2:
|
||||
luks_handler = Luks2(
|
||||
|
|
@ -338,6 +339,9 @@ class DeviceHandler:
|
|||
fs_type: FilesystemType,
|
||||
enc_conf: DiskEncryption
|
||||
) -> None:
|
||||
if not enc_conf.encryption_password:
|
||||
raise ValueError('No encryption password provided')
|
||||
|
||||
luks_handler = Luks2(
|
||||
dev_path,
|
||||
mapper_name=mapper_name,
|
||||
|
|
@ -678,7 +682,12 @@ class DeviceHandler:
|
|||
if luks_handler is not None and luks_handler.mapper_dev is not None:
|
||||
luks_handler.lock()
|
||||
|
||||
def unlock_luks2_dev(self, dev_path: Path, mapper_name: str, enc_password: str) -> Luks2:
|
||||
def unlock_luks2_dev(
|
||||
self,
|
||||
dev_path: Path,
|
||||
mapper_name: str,
|
||||
enc_password: Password | None
|
||||
) -> Luks2:
|
||||
luks_handler = Luks2(dev_path, mapper_name=mapper_name, password=enc_password)
|
||||
|
||||
if not luks_handler.is_unlocked():
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from archinstall.tui.types import Alignment, FrameProperties, ResultType
|
|||
|
||||
from ..menu.abstract_menu import AbstractSubMenu
|
||||
from ..models.device_model import Fido2Device
|
||||
from ..models.users import Password
|
||||
from ..output import FormattedOutput
|
||||
from ..utils.util import get_password
|
||||
from .fido import Fido2
|
||||
|
|
@ -122,7 +123,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
super().run()
|
||||
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_password: str | None = self._item_group.find_by_key('encryption_password').value
|
||||
enc_password: Password | None = self._item_group.find_by_key('encryption_password').value
|
||||
enc_partitions = self._item_group.find_by_key('partitions').value
|
||||
enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
|
||||
|
||||
|
|
@ -183,8 +184,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
enc_pwd = self._item_group.find_by_key('encryption_password').value
|
||||
|
||||
if enc_pwd:
|
||||
pwd_text = '*' * len(enc_pwd)
|
||||
return f'{_("Encryption password")}: {pwd_text}'
|
||||
return f'{_("Encryption password")}: {enc_pwd.hidden()}'
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
|
|||
return result.get_value()
|
||||
|
||||
|
||||
def select_encrypted_password() -> str | None:
|
||||
def select_encrypted_password() -> Password | None:
|
||||
header = str(_('Enter disk encryption password (leave blank for no encryption)')) + '\n'
|
||||
password = get_password(
|
||||
text=str(_('Disk encryption password')),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from archinstall.lib.models.device_model import Fido2Device
|
|||
|
||||
from ..exceptions import SysCallError
|
||||
from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes_from_str
|
||||
from ..models.users import Password
|
||||
from ..output import error, info
|
||||
|
||||
|
||||
|
|
@ -74,7 +75,7 @@ class Fido2:
|
|||
cls,
|
||||
hsm_device: Fido2Device,
|
||||
dev_path: Path,
|
||||
password: str
|
||||
password: Password
|
||||
) -> None:
|
||||
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}", peek_output=True)
|
||||
pw_inputted = False
|
||||
|
|
@ -83,7 +84,7 @@ class Fido2:
|
|||
while worker.is_alive():
|
||||
if pw_inputted is False:
|
||||
if bytes(f"please enter current passphrase for disk {dev_path}", 'UTF-8') in worker._trace_log.lower():
|
||||
worker.write(bytes(password, 'UTF-8'))
|
||||
worker.write(bytes(password.plaintext, 'UTF-8'))
|
||||
pw_inputted = True
|
||||
elif pin_inputted is False:
|
||||
if bytes("please enter security token pin", 'UTF-8') in worker._trace_log.lower():
|
||||
|
|
|
|||
|
|
@ -474,8 +474,3 @@ def _pid_exists(pid: int) -> bool:
|
|||
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def secret(x: str) -> str:
|
||||
""" return * with len equal to to the input string """
|
||||
return '*' * len(x)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
|||
|
||||
from .args import ArchConfig
|
||||
from .configuration import save_config
|
||||
from .general import secret
|
||||
from .hardware import SysInfo
|
||||
from .interactions.general_conf import (
|
||||
add_number_of_parallel_downloads,
|
||||
|
|
@ -31,7 +30,7 @@ from .models.locale import LocaleConfiguration
|
|||
from .models.mirrors import MirrorConfiguration
|
||||
from .models.network_configuration import NetworkConfiguration, NicType
|
||||
from .models.profile_model import ProfileConfiguration
|
||||
from .models.users import User
|
||||
from .models.users import Password, User
|
||||
from .output import FormattedOutput
|
||||
from .translationhandler import Language, translation_handler
|
||||
from .utils.util import get_password
|
||||
|
|
@ -125,7 +124,7 @@ class GlobalMenu(AbstractMenu):
|
|||
text=str(_('Root password')),
|
||||
action=self._set_root_password,
|
||||
preview_action=self._prev_root_pwd,
|
||||
key='root_password',
|
||||
key='root_enc_password',
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('User account')),
|
||||
|
|
@ -234,8 +233,8 @@ class GlobalMenu(AbstractMenu):
|
|||
missing = set()
|
||||
|
||||
for item in self._item_group.items:
|
||||
if item.key in ['root_password', 'users']:
|
||||
if not check('root_password') and not has_superuser():
|
||||
if item.key in ['root_enc_password', 'users']:
|
||||
if not check('root_enc_password') and not has_superuser():
|
||||
missing.add(
|
||||
str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
|
||||
)
|
||||
|
|
@ -364,7 +363,8 @@ class GlobalMenu(AbstractMenu):
|
|||
|
||||
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{_("Root password")}: {secret(item.value)}'
|
||||
password: Password = item.value
|
||||
return f'{_("Root password")}: {password.hidden()}'
|
||||
return None
|
||||
|
||||
def _prev_audio(self, item: MenuItem) -> str | None:
|
||||
|
|
@ -399,7 +399,9 @@ class GlobalMenu(AbstractMenu):
|
|||
if enc_config:
|
||||
enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
|
||||
output = str(_('Encryption type')) + f': {enc_type}\n'
|
||||
output += str(_('Password')) + f': {secret(enc_config.encryption_password)}\n'
|
||||
|
||||
if enc_config.encryption_password:
|
||||
output += str(_('Password')) + f': {enc_config.encryption_password.hidden()}\n'
|
||||
|
||||
if enc_config.partitions:
|
||||
output += f'Partitions: {len(enc_config.partitions)} selected\n'
|
||||
|
|
@ -501,7 +503,7 @@ class GlobalMenu(AbstractMenu):
|
|||
|
||||
return None
|
||||
|
||||
def _set_root_password(self, preset: str | None = None) -> str | None:
|
||||
def _set_root_password(self, preset: str | None = None) -> Password | None:
|
||||
password = get_password(text=str(_('Root password')), allow_skip=True)
|
||||
return password
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,10 @@ class Installer:
|
|||
|
||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||
self.helper_flags: dict[str, str | bool | None] = {'base': False, 'bootloader': None}
|
||||
self._helper_flags: dict[str, str | bool | None] = {
|
||||
'base': False,
|
||||
'bootloader': None
|
||||
}
|
||||
|
||||
for kernel in self.kernels:
|
||||
self._base_packages.append(kernel)
|
||||
|
|
@ -415,11 +418,12 @@ class Installer:
|
|||
|
||||
if part_mod.is_root() and not gen_enc_file:
|
||||
if self._disk_encryption.hsm_device:
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
part_mod.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
)
|
||||
if self._disk_encryption.encryption_password:
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
part_mod.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
)
|
||||
|
||||
def _generate_key_file_lvm_volumes(self) -> None:
|
||||
for vol in self._disk_encryption.lvm_volumes:
|
||||
|
|
@ -437,16 +441,17 @@ class Installer:
|
|||
|
||||
if vol.is_root() and not gen_enc_file:
|
||||
if self._disk_encryption.hsm_device:
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
vol.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
)
|
||||
if self._disk_encryption.encryption_password:
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
vol.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
)
|
||||
|
||||
def sync_log_to_install_medium(self) -> bool:
|
||||
# Copy over the install log (if there is one) to the install medium if
|
||||
# at least the base has been strapped in, otherwise we won't have a filesystem/structure to copy to.
|
||||
if self.helper_flags.get('base-strapped', False) is True:
|
||||
if self._helper_flags.get('base-strapped', False) is True:
|
||||
if filename := storage.get('LOG_FILE', None):
|
||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||
|
||||
|
|
@ -480,7 +485,7 @@ class Installer:
|
|||
self._kernel_params.append(f'resume_offset={resume_offset}')
|
||||
|
||||
def post_install_check(self, *args: str, **kwargs: str) -> list[str]:
|
||||
return [step for step, flag in self.helper_flags.items() if flag is False]
|
||||
return [step for step, flag in self._helper_flags.items() if flag is False]
|
||||
|
||||
def set_mirrors(
|
||||
self,
|
||||
|
|
@ -690,7 +695,7 @@ class Installer:
|
|||
|
||||
if enable_services:
|
||||
# If we haven't installed the base yet (function called pre-maturely)
|
||||
if self.helper_flags.get('base', False) is False:
|
||||
if self._helper_flags.get('base', False) is False:
|
||||
self._base_packages.append('iwd')
|
||||
|
||||
# This function will be called after minimal_installation()
|
||||
|
|
@ -719,7 +724,7 @@ class Installer:
|
|||
|
||||
if enable_services:
|
||||
# If we haven't installed the base yet (function called pre-maturely)
|
||||
if self.helper_flags.get('base', False) is False:
|
||||
if self._helper_flags.get('base', False) is False:
|
||||
|
||||
def post_install_enable_networkd_resolved(*args: str, **kwargs: str) -> None:
|
||||
self.enable_service(['systemd-networkd', 'systemd-resolved'])
|
||||
|
|
@ -847,7 +852,7 @@ class Installer:
|
|||
pacman_conf.apply()
|
||||
|
||||
self.pacman.strap(self._base_packages)
|
||||
self.helper_flags['base-strapped'] = True
|
||||
self._helper_flags['base-strapped'] = True
|
||||
|
||||
pacman_conf.persist()
|
||||
|
||||
|
|
@ -878,7 +883,7 @@ class Installer:
|
|||
if mkinitcpio and not self.mkinitcpio(['-P']):
|
||||
error('Error generating initramfs (continuing anyway)')
|
||||
|
||||
self.helper_flags['base'] = True
|
||||
self._helper_flags['base'] = True
|
||||
|
||||
# Run registered post-install hooks
|
||||
for function in self.post_base_install:
|
||||
|
|
@ -1144,7 +1149,7 @@ class Installer:
|
|||
|
||||
loader_conf.write_text('\n'.join(loader_data) + '\n')
|
||||
|
||||
self.helper_flags['bootloader'] = 'systemd'
|
||||
self._helper_flags['bootloader'] = 'systemd'
|
||||
|
||||
def _add_grub_bootloader(
|
||||
self,
|
||||
|
|
@ -1229,7 +1234,7 @@ class Installer:
|
|||
except SysCallError as err:
|
||||
raise DiskError(f"Could not configure GRUB: {err}")
|
||||
|
||||
self.helper_flags['bootloader'] = "grub"
|
||||
self._helper_flags['bootloader'] = "grub"
|
||||
|
||||
def _add_limine_bootloader(
|
||||
self,
|
||||
|
|
@ -1389,7 +1394,7 @@ class Installer:
|
|||
|
||||
config_path.write_text(config_contents)
|
||||
|
||||
self.helper_flags['bootloader'] = "limine"
|
||||
self._helper_flags['bootloader'] = "limine"
|
||||
|
||||
def _add_efistub_bootloader(
|
||||
self,
|
||||
|
|
@ -1439,7 +1444,7 @@ class Installer:
|
|||
cmd = [arg.format(kernel=kernel) for arg in cmd_template]
|
||||
SysCommand(cmd)
|
||||
|
||||
self.helper_flags['bootloader'] = "efistub"
|
||||
self._helper_flags['bootloader'] = "efistub"
|
||||
|
||||
def _config_uki(
|
||||
self,
|
||||
|
|
@ -1573,12 +1578,9 @@ class Installer:
|
|||
users = [users]
|
||||
|
||||
for user in users:
|
||||
self.user_create(user.username, user.password, user.groups, user.sudo)
|
||||
|
||||
def user_create(self, user: str, password: str | None = None, groups: list[str] | None = None, sudo: bool = False) -> None:
|
||||
if groups is None:
|
||||
groups = []
|
||||
self._create_user(user)
|
||||
|
||||
def _create_user(self, user: User) -> None:
|
||||
# This plugin hook allows for the plugin to handle the creation of the user.
|
||||
# Password and Group management is still handled by user_create()
|
||||
handled_by_plugin = False
|
||||
|
|
@ -1588,14 +1590,14 @@ class Installer:
|
|||
handled_by_plugin = result
|
||||
|
||||
if not handled_by_plugin:
|
||||
info(f'Creating user {user}')
|
||||
info(f'Creating user {user.username}')
|
||||
|
||||
cmd = f'arch-chroot {self.target} useradd -m'
|
||||
|
||||
if sudo:
|
||||
if user.sudo:
|
||||
cmd += ' -G wheel'
|
||||
|
||||
cmd += f' {user}'
|
||||
cmd += f' {user.username}'
|
||||
|
||||
try:
|
||||
SysCommand(cmd)
|
||||
|
|
@ -1607,29 +1609,29 @@ class Installer:
|
|||
if result := plugin.on_user_created(self, user):
|
||||
handled_by_plugin = result
|
||||
|
||||
if password:
|
||||
self.user_set_pw(user, password)
|
||||
if user.password:
|
||||
self.set_user_password(user)
|
||||
|
||||
if groups:
|
||||
for group in groups:
|
||||
SysCommand(f'arch-chroot {self.target} gpasswd -a {user} {group}')
|
||||
for group in user.groups:
|
||||
SysCommand(f'arch-chroot {self.target} gpasswd -a {user.username} {group}')
|
||||
|
||||
if sudo and self.enable_sudo(user):
|
||||
self.helper_flags['user'] = True
|
||||
def set_user_password(self, user: User) -> bool:
|
||||
info(f'Setting password for {user.username}')
|
||||
|
||||
def user_set_pw(self, user: str, password: str) -> bool:
|
||||
info(f'Setting password for {user}')
|
||||
enc_password = user.password.enc_password if user.password else None
|
||||
|
||||
if user == 'root':
|
||||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||
self.helper_flags['user'] = True
|
||||
if not enc_password:
|
||||
debug('User password is empty')
|
||||
return False
|
||||
|
||||
cmd = ['arch-chroot', str(self.target), 'chpasswd']
|
||||
input_data = f'{user.username}:{enc_password}'.encode()
|
||||
cmd = ['arch-chroot', str(self.target), 'chpasswd', '--encrypted']
|
||||
|
||||
try:
|
||||
run(cmd, input_data=f'{user}:{password}'.encode())
|
||||
run(cmd, input_data=input_data)
|
||||
return True
|
||||
except CalledProcessError:
|
||||
except CalledProcessError as err:
|
||||
debug(f'Error setting user password: {err}')
|
||||
return False
|
||||
|
||||
def user_set_shell(self, user: str, shell: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from archinstall.tui.curses_menu import EditMenu, SelectMenu
|
|||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.types import Alignment, Orientation, ResultType
|
||||
|
||||
from ..general import secret
|
||||
from ..menu.list_manager import ListManager
|
||||
from ..models.users import User
|
||||
from ..utils.util import get_password
|
||||
|
|
@ -91,7 +90,7 @@ class UserList(ListManager):
|
|||
if not password:
|
||||
return None
|
||||
|
||||
header += f'{_("Password")}: {secret(password)}\n\n'
|
||||
header += f'{_("Password")}: {password.hidden()}\n\n'
|
||||
header += str(_('Should "{}" be a superuser (sudo)?\n')).format(username)
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from archinstall.lib.disk.utils import get_lsblk_info
|
|||
|
||||
from .exceptions import DiskError, SysCallError
|
||||
from .general import SysCommand, SysCommandWorker, generate_password, run
|
||||
from .models.users import Password
|
||||
from .output import debug, info
|
||||
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ from .output import debug, info
|
|||
class Luks2:
|
||||
luks_dev_path: Path
|
||||
mapper_name: str | None = None
|
||||
password: str | None = None
|
||||
password: Password | None = None
|
||||
key_file: Path | None = None
|
||||
auto_unmount: bool = False
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ class Luks2:
|
|||
if isinstance(self.password, bytes):
|
||||
return self.password
|
||||
else:
|
||||
return bytes(self.password, 'UTF-8')
|
||||
return bytes(self.password.plaintext, 'UTF-8')
|
||||
|
||||
def _get_passphrase_args(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from parted import Disk, Geometry, Partition
|
|||
from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator
|
||||
|
||||
from ..hardware import SysInfo
|
||||
from ..models.users import Password
|
||||
from ..output import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -1422,7 +1423,7 @@ class _DiskEncryptionSerialization(TypedDict):
|
|||
@dataclass
|
||||
class DiskEncryption:
|
||||
encryption_type: EncryptionType = EncryptionType.NoEncryption
|
||||
encryption_password: str = ''
|
||||
encryption_password: Password | None = None
|
||||
partitions: list[PartitionModification] = field(default_factory=list)
|
||||
lvm_volumes: list[LvmVolume] = field(default_factory=list)
|
||||
hsm_device: Fido2Device | None = None
|
||||
|
|
@ -1472,12 +1473,12 @@ class DiskEncryption:
|
|||
cls,
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
disk_encryption: _DiskEncryptionSerialization,
|
||||
password: str = ''
|
||||
password: Password | None = None
|
||||
) -> 'DiskEncryption | None':
|
||||
if not cls.validate_enc(disk_config):
|
||||
return None
|
||||
|
||||
if len(password) < 1:
|
||||
if not password:
|
||||
return None
|
||||
|
||||
enc_partitions = []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, TypedDict, override
|
||||
from typing import TYPE_CHECKING, NotRequired, TypedDict, override
|
||||
|
||||
from ..crypt import crypt_yescrypt
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -103,72 +105,117 @@ class PasswordStrength(Enum):
|
|||
return PasswordStrength.VERY_WEAK
|
||||
|
||||
|
||||
_UserSerialization = TypedDict('_UserSerialization', {'username': str, '!password': str, 'sudo': bool})
|
||||
_UserSerialization = TypedDict(
|
||||
'_UserSerialization',
|
||||
{
|
||||
'username': str,
|
||||
'!password': NotRequired[str],
|
||||
'sudo': bool,
|
||||
'groups': list[str],
|
||||
'enc_password': str | None
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class Password:
|
||||
def __init__(
|
||||
self,
|
||||
plaintext: str = '',
|
||||
enc_password: str | None = None
|
||||
):
|
||||
if plaintext:
|
||||
enc_password = crypt_yescrypt(plaintext)
|
||||
|
||||
if not plaintext and not enc_password:
|
||||
raise ValueError('Either plaintext or enc_password must be provided')
|
||||
|
||||
self._plaintext = plaintext
|
||||
self.enc_password = enc_password
|
||||
|
||||
@property
|
||||
def plaintext(self) -> str:
|
||||
return self._plaintext
|
||||
|
||||
@plaintext.setter
|
||||
def plaintext(self, value: str):
|
||||
self._plaintext = value
|
||||
self.enc_password = crypt_yescrypt(value)
|
||||
|
||||
@override
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Password):
|
||||
return NotImplemented
|
||||
|
||||
if self._plaintext and other._plaintext:
|
||||
return self._plaintext == other._plaintext
|
||||
|
||||
return self.enc_password == other.enc_password
|
||||
|
||||
def hidden(self) -> str:
|
||||
if self._plaintext:
|
||||
return '*' * len(self._plaintext)
|
||||
else:
|
||||
return '*' * 8
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
username: str
|
||||
password: str
|
||||
password: Password
|
||||
sudo: bool
|
||||
groups: list[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def groups(self) -> list[str]:
|
||||
# this property should be transferred into a class attr instead
|
||||
# if it's every going to be used
|
||||
return []
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
# safety overwrite to make sure password is not leaked
|
||||
return f'User({self.username=}, {self.sudo=}, {self.groups=})'
|
||||
|
||||
def table_data(self) -> dict[str, str | bool | list[str]]:
|
||||
return {
|
||||
'username': self.username,
|
||||
'password': self.password.hidden(),
|
||||
'sudo': self.sudo,
|
||||
'groups': self.groups
|
||||
}
|
||||
|
||||
def json(self) -> _UserSerialization:
|
||||
return {
|
||||
'username': self.username,
|
||||
'!password': self.password,
|
||||
'sudo': self.sudo
|
||||
'enc_password': self.password.enc_password,
|
||||
'sudo': self.sudo,
|
||||
'groups': self.groups
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _parse(cls, config_users: list[_UserSerialization]) -> list['User']:
|
||||
users = []
|
||||
|
||||
for entry in config_users:
|
||||
username = entry.get('username', None)
|
||||
password = entry.get('!password', '')
|
||||
sudo = entry.get('sudo', False)
|
||||
|
||||
if username is None:
|
||||
continue
|
||||
|
||||
user = User(username, password, sudo)
|
||||
users.append(user)
|
||||
|
||||
return users
|
||||
|
||||
@classmethod
|
||||
def _parse_backwards_compatible(cls, config_users: dict[str, dict[str, str]], sudo: bool) -> list['User']:
|
||||
if len(config_users.keys()) > 0:
|
||||
username = list(config_users.keys())[0]
|
||||
password = config_users[username]['!password']
|
||||
|
||||
if password:
|
||||
return [User(username, password, sudo)]
|
||||
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def parse_arguments(
|
||||
cls,
|
||||
config_users: list[_UserSerialization] | dict[str, dict[str, str]],
|
||||
config_superusers: dict[str, dict[str, str]] | None
|
||||
args: list[_UserSerialization]
|
||||
) -> list['User']:
|
||||
users = []
|
||||
users: list[User] = []
|
||||
|
||||
# backwards compatibility
|
||||
if isinstance(config_users, dict):
|
||||
users += cls._parse_backwards_compatible(config_users, False)
|
||||
else:
|
||||
users += cls._parse(config_users)
|
||||
for entry in args:
|
||||
username = entry.get('username')
|
||||
password: Password | None = None
|
||||
groups = entry.get('groups', [])
|
||||
plaintext = entry.get('!password')
|
||||
enc_password = entry.get('enc_password')
|
||||
|
||||
# backwards compatibility
|
||||
if isinstance(config_superusers, dict):
|
||||
users += cls._parse_backwards_compatible(config_superusers, True)
|
||||
# DEPRECATED: backwards compatibility
|
||||
if plaintext:
|
||||
password = Password(plaintext=plaintext)
|
||||
elif enc_password:
|
||||
password = Password(enc_password=enc_password)
|
||||
|
||||
if username is None or password is None:
|
||||
continue
|
||||
|
||||
user = User(
|
||||
username=username,
|
||||
password=password,
|
||||
sudo=entry.get('sudo', False) is True,
|
||||
groups=groups
|
||||
)
|
||||
|
||||
users.append(user)
|
||||
|
||||
return users
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
from .profile_menu import ProfileMenu, select_greeter, select_profile
|
||||
from .profiles_handler import profile_handler
|
||||
|
||||
__all__ = [
|
||||
'ProfileMenu',
|
||||
'profile_handler',
|
||||
'select_greeter',
|
||||
'select_profile',
|
||||
]
|
||||
|
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
|||
from archinstall.tui.curses_menu import EditMenu
|
||||
from archinstall.tui.types import Alignment
|
||||
|
||||
from ..general import secret
|
||||
from ..models.users import Password
|
||||
from ..output import FormattedOutput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -20,7 +20,7 @@ def get_password(
|
|||
header: str | None = None,
|
||||
allow_skip: bool = False,
|
||||
preset: str | None = None
|
||||
) -> str | None:
|
||||
) -> Password | None:
|
||||
failure: str | None = None
|
||||
|
||||
while True:
|
||||
|
|
@ -42,13 +42,12 @@ def get_password(
|
|||
if allow_skip and not result.has_item():
|
||||
return None
|
||||
|
||||
password = result.text()
|
||||
hidden = secret(password)
|
||||
password = Password(plaintext=result.text())
|
||||
|
||||
if header is not None:
|
||||
confirmation_header = f'{header}{_("Password")}: {hidden}\n'
|
||||
confirmation_header = f'{header}{_("Password")}: {password.hidden()}\n'
|
||||
else:
|
||||
confirmation_header = f'{_("Password")}: {hidden}\n'
|
||||
confirmation_header = f'{_("Password")}: {password.hidden()}\n'
|
||||
|
||||
result = EditMenu(
|
||||
str(_('Confirm password')),
|
||||
|
|
@ -58,7 +57,7 @@ def get_password(
|
|||
hide_input=True
|
||||
).input()
|
||||
|
||||
if password == result.text():
|
||||
if password._plaintext == result.text():
|
||||
return password
|
||||
|
||||
failure = str(_('The confirmation password did not match, please try again'))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from archinstall.lib.models.device_model import (
|
|||
EncryptionType,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
|
@ -128,8 +129,9 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
if accessibility_tools_in_use():
|
||||
installation.enable_espeakup()
|
||||
|
||||
if (root_pw := config.root_password) and len(root_pw):
|
||||
installation.user_set_pw('root', root_pw)
|
||||
if root_pw := config.root_enc_password:
|
||||
root_user = User('root', root_pw, False)
|
||||
installation.set_user_password(root_user)
|
||||
|
||||
if (profile_config := config.profile_config) and profile_config.profile:
|
||||
profile_config.profile.post_install(installation)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,15 @@ from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
|||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models import Bootloader, User
|
||||
from archinstall.lib.models import Bootloader
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskLayoutConfiguration,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
profile_config = ProfileConfiguration(MinimalProfile())
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
user = User('devel', 'devel', False)
|
||||
user = User('devel', Password(plaintext='devel'), False)
|
||||
installation.create_users(user)
|
||||
|
||||
# Once this is done, we output some useful information to the user
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ Below is an example of how to set the root password and below that are descripti
|
|||
.. code-block:: json
|
||||
|
||||
{
|
||||
"!root-password" : "SecretSanta2022"
|
||||
"root_enc_password" : "SecretSanta2022"
|
||||
}
|
||||
|
||||
.. list-table:: ``--creds`` options
|
||||
|
|
@ -262,16 +262,16 @@ Below is an example of how to set the root password and below that are descripti
|
|||
- ``str``
|
||||
- Password to encrypt disk, not encrypted if password not provided
|
||||
- No
|
||||
* - ``!root-password``
|
||||
* - ``root_enc_password``
|
||||
- ``str``
|
||||
- The root account password
|
||||
- No
|
||||
* - ``!users``
|
||||
* - ``users``
|
||||
- .. code-block:: json
|
||||
|
||||
{
|
||||
"username": "<USERNAME>",
|
||||
"!password": "<PASSWORD>",
|
||||
"enc_password": "<PASSWORD_HASH>",
|
||||
"sudo": false
|
||||
}
|
||||
- List of regular user credentials, see configuration for reference
|
||||
|
|
@ -280,11 +280,9 @@ Below is an example of how to set the root password and below that are descripti
|
|||
|
||||
.. note::
|
||||
|
||||
``!users`` is optional only if ``!root-password`` was set. ``!users`` will be enforced otherwise and the minimum amount of users with sudo privileges required will be set to 1.
|
||||
``users`` is optional only if ``root_enc_password`` was set. ``users`` will be enforced otherwise and the minimum amount of users with sudo privileges required will be set to 1.
|
||||
|
||||
.. note::
|
||||
|
||||
The keys start with ``!`` because internal log functions will mask any keys starting with exclamation marks from logs and unrestricted configurations.
|
||||
|
||||
.. _scripts: https://github.com/archlinux/archinstall/tree/master/archinstall/scripts
|
||||
.. _Guided Installer: https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"!users": [
|
||||
"users": [
|
||||
{
|
||||
"sudo": true,
|
||||
"username": "archinstall"
|
||||
"username": "archinstall",
|
||||
"enc_password": "password_hash"
|
||||
|
||||
}
|
||||
],
|
||||
"encryption_password": "..."
|
||||
"root_enc_password": "password_hash"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from archinstall.lib.models.device_model import (
|
|||
Unit,
|
||||
)
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
|
||||
# we're creating a new ext4 filesystem installation
|
||||
fs_type = FilesystemType('ext4')
|
||||
|
|
@ -81,7 +81,7 @@ disk_config = DiskLayoutConfiguration(
|
|||
|
||||
# disk encryption configuration (Optional)
|
||||
disk_encryption = DiskEncryption(
|
||||
encryption_password="enc_password",
|
||||
encryption_password=Password(plaintext="enc_password"),
|
||||
encryption_type=EncryptionType.Luks,
|
||||
partitions=[home_partition],
|
||||
hsm_device=None
|
||||
|
|
@ -111,5 +111,5 @@ with Installer(
|
|||
profile_config = ProfileConfiguration(MinimalProfile())
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
user = User('archinstall', 'password', True)
|
||||
user = User('archinstall', Password(plaintext='password'), True)
|
||||
installation.create_users(user)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from archinstall.lib.models.device_model import (
|
|||
EncryptionType,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
|
@ -127,8 +128,9 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
if accessibility_tools_in_use():
|
||||
installation.enable_espeakup()
|
||||
|
||||
if (root_pw := config.root_password) and len(root_pw):
|
||||
installation.user_set_pw('root', root_pw)
|
||||
if root_pw := config.root_enc_password:
|
||||
root_user = User('root', root_pw, False)
|
||||
installation.set_user_password(root_user)
|
||||
|
||||
if (profile_config := config.profile_config) and profile_config.profile:
|
||||
profile_config.profile.post_install(installation)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import time
|
||||
|
||||
from archinstall.lib.output import info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.storage import storage
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
|||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models import Bootloader, User
|
||||
from archinstall.lib.models import Bootloader
|
||||
from archinstall.lib.models.device_model import DiskLayoutConfiguration
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
profile_config = ProfileConfiguration(MinimalProfile())
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
user = User('devel', 'devel', False)
|
||||
user = User('devel', Password(plaintext='devel'), False)
|
||||
installation.create_users(user)
|
||||
|
||||
# Once this is done, we output some useful information to the user
|
||||
|
|
|
|||
|
|
@ -14,8 +14,13 @@ def creds_fixture() -> Path:
|
|||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def mirror_backwards_config() -> Path:
|
||||
return Path(__file__).parent / 'data' / 'test_config_mirror_backwards.json'
|
||||
def deprecated_creds_config() -> Path:
|
||||
return Path(__file__).parent / 'data' / 'test_deprecated_creds_config.json'
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def deprecated_mirror_config() -> Path:
|
||||
return Path(__file__).parent / 'data' / 'test_deprecated_mirror_config.json'
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"!root-password": "super_pwd",
|
||||
"!users": [
|
||||
"root_enc_password": "password_hash",
|
||||
"users": [
|
||||
{
|
||||
"!password": "user_pwd",
|
||||
"enc_password": "password_hash",
|
||||
"sudo": true,
|
||||
"username": "user_name"
|
||||
"username": "user_name",
|
||||
"groups": ["wheel"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"!root-password": "rootPwd",
|
||||
"!users": [
|
||||
{
|
||||
"!password": "userPwd",
|
||||
"sudo": true,
|
||||
"username": "user_name",
|
||||
"groups": ["wheel"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ from archinstall.lib.models.mirrors import CustomRepository, CustomServer, Mirro
|
|||
from archinstall.lib.models.network_configuration import NetworkConfiguration, Nic, NicType
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import translation_handler
|
||||
|
||||
|
|
@ -189,22 +189,29 @@ def test_config_file_parsing(
|
|||
parallel_downloads=66,
|
||||
swap=False,
|
||||
timezone='UTC',
|
||||
users=[User(username='user_name', password='user_pwd', sudo=True)],
|
||||
users=[
|
||||
User(
|
||||
username='user_name',
|
||||
password=Password(enc_password='password_hash'),
|
||||
sudo=True,
|
||||
groups=['wheel']
|
||||
)
|
||||
],
|
||||
disk_encryption=None,
|
||||
services=['service_1', 'service_2'],
|
||||
root_password='super_pwd',
|
||||
root_enc_password=Password(enc_password='password_hash'),
|
||||
custom_commands=["echo 'Hello, World!'"]
|
||||
)
|
||||
|
||||
|
||||
def test_mirror_backwards_config_file_parsing(
|
||||
def test_deprecated_mirror_config_parsing(
|
||||
monkeypatch: MonkeyPatch,
|
||||
mirror_backwards_config: Path,
|
||||
deprecated_mirror_config: Path,
|
||||
) -> None:
|
||||
monkeypatch.setattr('sys.argv', [
|
||||
'archinstall',
|
||||
'--config',
|
||||
str(mirror_backwards_config),
|
||||
str(deprecated_mirror_config),
|
||||
])
|
||||
|
||||
handler = ArchConfigHandler()
|
||||
|
|
@ -228,3 +235,28 @@ def test_mirror_backwards_config_file_parsing(
|
|||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_deprecated_creds_config_parsing(
|
||||
monkeypatch: MonkeyPatch,
|
||||
deprecated_creds_config: Path,
|
||||
) -> None:
|
||||
monkeypatch.setattr('sys.argv', [
|
||||
'archinstall',
|
||||
'--creds',
|
||||
str(deprecated_creds_config),
|
||||
])
|
||||
|
||||
handler = ArchConfigHandler()
|
||||
arch_config = handler.config
|
||||
|
||||
assert arch_config.root_enc_password == Password(plaintext='rootPwd')
|
||||
|
||||
assert arch_config.users == [
|
||||
User(
|
||||
username='user_name',
|
||||
password=Password(plaintext='userPwd'),
|
||||
sudo=True,
|
||||
groups=['wheel']
|
||||
)
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue