131 lines
4.0 KiB
Python
131 lines
4.0 KiB
Python
import getpass
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from archinstall.lib.general import SysCommandWorker
|
|
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
|
|
from archinstall.lib.models.users import User
|
|
from archinstall.lib.output import debug
|
|
from archinstall.lib.translationhandler import tr
|
|
from archinstall.tui.curses_menu import Tui
|
|
|
|
if TYPE_CHECKING:
|
|
from archinstall.lib.installer import Installer
|
|
|
|
|
|
class AuthenticationHandler:
|
|
def setup_auth(
|
|
self,
|
|
install_session: 'Installer',
|
|
auth_config: AuthenticationConfiguration,
|
|
hostname: str,
|
|
) -> None:
|
|
if auth_config.u2f_config and auth_config.users is not None:
|
|
self._setup_u2f_login(install_session, auth_config.u2f_config, auth_config.users, hostname)
|
|
|
|
def _setup_u2f_login(self, install_session: 'Installer', u2f_config: U2FLoginConfiguration, users: list[User], hostname: str) -> None:
|
|
self._configure_u2f_mapping(install_session, u2f_config, users, hostname)
|
|
self._update_pam_config(install_session, u2f_config)
|
|
|
|
def _update_pam_config(
|
|
self,
|
|
install_session: 'Installer',
|
|
u2f_config: U2FLoginConfiguration,
|
|
) -> None:
|
|
match u2f_config.u2f_login_method:
|
|
case U2FLoginMethod.Passwordless:
|
|
config_entry = 'auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue'
|
|
case U2FLoginMethod.SecondFactor:
|
|
config_entry = 'auth required pam_u2f.so authfile=/etc/u2f_mappings cue'
|
|
case _:
|
|
raise ValueError(f'Unknown U2F login method: {u2f_config.u2f_login_method}')
|
|
|
|
debug(f'U2F PAM configuration: {config_entry}')
|
|
debug(f'Passwordless sudo enabled: {u2f_config.passwordless_sudo}')
|
|
|
|
sudo_config = install_session.target / 'etc/pam.d/sudo'
|
|
sys_login = install_session.target / 'etc/pam.d/system-login'
|
|
|
|
if u2f_config.passwordless_sudo:
|
|
self._add_u2f_entry(sudo_config, config_entry)
|
|
|
|
self._add_u2f_entry(sys_login, config_entry)
|
|
|
|
def _add_u2f_entry(self, file: Path, entry: str) -> None:
|
|
if not file.exists():
|
|
debug(f'File does not exist: {file}')
|
|
return None
|
|
|
|
content = file.read_text().splitlines()
|
|
|
|
# remove any existing u2f auth entry
|
|
content = [line for line in content if 'pam_u2f.so' not in line]
|
|
|
|
# add the u2f auth entry as the first one after comments
|
|
for i, line in enumerate(content):
|
|
if not line.startswith('#'):
|
|
content.insert(i, entry)
|
|
break
|
|
else:
|
|
content.append(entry)
|
|
|
|
file.write_text('\n'.join(content) + '\n')
|
|
|
|
def _configure_u2f_mapping(
|
|
self,
|
|
install_session: 'Installer',
|
|
u2f_config: U2FLoginConfiguration,
|
|
users: list[User],
|
|
hostname: str,
|
|
) -> None:
|
|
debug(f'Setting up U2F login: {u2f_config.u2f_login_method.value}')
|
|
|
|
install_session.pacman.strap('pam-u2f')
|
|
|
|
Tui.print(tr(f'Setting up U2F login: {u2f_config.u2f_login_method.value}'))
|
|
|
|
# https://developers.yubico.com/pam-u2f/
|
|
u2f_auth_file = install_session.target / 'etc/u2f_mappings'
|
|
u2f_auth_file.touch()
|
|
existing_keys = u2f_auth_file.read_text()
|
|
|
|
registered_keys: list[str] = []
|
|
|
|
for user in users:
|
|
Tui.print('')
|
|
Tui.print(tr('Setting up U2F device for user: {}').format(user.username))
|
|
Tui.print(tr('You may need to enter the PIN and then touch your U2F device to register it'))
|
|
|
|
cmd = ' '.join(
|
|
['arch-chroot', '-S', str(install_session.target), 'pamu2fcfg', '-u', user.username, '-o', f'pam://{hostname}', '-i', f'pam://{hostname}']
|
|
)
|
|
|
|
debug(f'Enrolling U2F device: {cmd}')
|
|
|
|
worker = SysCommandWorker(cmd, peek_output=True)
|
|
pin_inputted = False
|
|
|
|
while worker.is_alive():
|
|
if pin_inputted is False:
|
|
if bytes('enter pin for', 'UTF-8') in worker._trace_log.lower():
|
|
worker.write(bytes(getpass.getpass(''), 'UTF-8'))
|
|
pin_inputted = True
|
|
|
|
output = worker.decode().strip().splitlines()
|
|
debug(f'Output from pamu2fcfg: {output}')
|
|
|
|
key = output[-1].strip()
|
|
registered_keys.append(key)
|
|
|
|
all_keys = '\n'.join(registered_keys)
|
|
|
|
if existing_keys:
|
|
existing_keys += f'\n{all_keys}'
|
|
else:
|
|
existing_keys = all_keys
|
|
|
|
u2f_auth_file.write_text(existing_keys)
|
|
|
|
|
|
auth_handler = AuthenticationHandler()
|