Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions archinstall/lib/authentication/authentication_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def _define_menu_options(self) -> list[MenuItem]:
preview_action=self._prev_u2f_login,
key='u2f_config',
),
MenuItem(
text=tr('Lock root account'),
action=select_lock_root_account,
value=self._auth_config.lock_root_account,
preview_action=self._prev_lock_root,
dependencies=['root_enc_password', self._check_dep_sudo_users],
key='lock_root_account',
),
]

def _create_user_account(self, preset: list[User] | None = None) -> list[User]:
Expand All @@ -82,6 +90,13 @@ def _depends_on_u2f(self) -> bool:
return False
return True

def _check_dep_sudo_users(self) -> bool:
"""Check if at least one sudo user exists"""
users: list[User] | None = self._item_group.find_by_key('users').value
if users:
return any(user.sudo for user in users)
return False

def _prev_u2f_login(self, item: MenuItem) -> str | None:
if item.value is not None:
u2f_config: U2FLoginConfiguration = item.value
Expand All @@ -100,6 +115,11 @@ def _prev_u2f_login(self, item: MenuItem) -> str | None:

return None

def _prev_lock_root(self, item: MenuItem) -> str | None:
if item.value is True:
return tr('Root account will be locked after installation')
return None


def select_root_password(preset: str | None = None) -> Password | None:
password = get_password(text=tr('Root password'), allow_skip=True)
Expand Down Expand Up @@ -157,3 +177,27 @@ def select_u2f_login(preset: U2FLoginConfiguration) -> U2FLoginConfiguration | N
return None
case _:
raise ValueError('Unhandled result type')


def select_lock_root_account(preset: bool) -> bool:
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.yes() if preset else MenuItem.no()

header = tr('Lock root account? Can be undone using passwd -u root later.\n') + tr('Sudo users can still edit /etc/shadow or use sudo directly.\n')

result = SelectMenu[bool](
group,
header=header,
alignment=Alignment.CENTER,
columns=2,
orientation=Orientation.HORIZONTAL,
allow_skip=True,
).run()

match result.type_:
case ResultType.Selection:
return result.item() == MenuItem.yes()
case ResultType.Skip:
return preset
case _:
return False
10 changes: 10 additions & 0 deletions archinstall/lib/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,16 @@ def set_user_password(self, user: User) -> bool:
debug(f'Error setting user password: {err}')
return False

def lock_root_account(self) -> bool:
info('Locking root account')

try:
self.arch_chroot('passwd -l root')
return True
except SysCallError as err:
error(f'Failed to lock root account: {err}')
return False

def user_set_shell(self, user: str, shell: str) -> bool:
info(f'Setting shell for {user} to {shell}')

Expand Down
8 changes: 8 additions & 0 deletions archinstall/lib/models/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class U2FLoginConfigSerialization(TypedDict):

class AuthenticationSerialization(TypedDict):
u2f_config: NotRequired[U2FLoginConfigSerialization]
lock_root_account: NotRequired[bool]


class U2FLoginMethod(Enum):
Expand Down Expand Up @@ -62,6 +63,7 @@ class AuthenticationConfiguration:
root_enc_password: Password | None = None
users: list[User] = field(default_factory=list)
u2f_config: U2FLoginConfiguration | None = None
lock_root_account: bool = False

@staticmethod
def parse_arg(args: dict[str, Any]) -> 'AuthenticationConfiguration':
Expand All @@ -73,6 +75,9 @@ def parse_arg(args: dict[str, Any]) -> 'AuthenticationConfiguration':
if enc_password := args.get('root_enc_password'):
auth_config.root_enc_password = Password(enc_password=enc_password)

if lock_root := args.get('lock_root_account'):
auth_config.lock_root_account = lock_root

return auth_config

def json(self) -> AuthenticationSerialization:
Expand All @@ -81,4 +86,7 @@ def json(self) -> AuthenticationSerialization:
if self.u2f_config:
config['u2f_config'] = self.u2f_config.json()

if self.lock_root_account:
config['lock_root_account'] = self.lock_root_account

return config
3 changes: 3 additions & 0 deletions archinstall/scripts/guided.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ def perform_installation(mountpoint: Path) -> None:
root_user = User('root', config.auth_config.root_enc_password, False)
installation.set_user_password(root_user)

if config.auth_config.lock_root_account:
installation.lock_root_account()

if (profile_config := config.profile_config) and profile_config.profile:
profile_config.profile.post_install(installation)

Expand Down
3 changes: 3 additions & 0 deletions examples/config-sample.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"archinstall-language": "English",
"auth_config": {
"lock_root_account": false
},
"audio_config": {
"audio": "pipewire"
},
Expand Down