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