# # Kickstart module for the users module. # # Copyright (C) 2018 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # the GNU General Public License v.2, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY expressed or implied, including the implied warranties of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. You should have received a copy of the # GNU General Public License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the # source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc. # from pyanaconda.core.configuration.anaconda import conf from pyanaconda.core.dbus import DBus from pyanaconda.core.signal import Signal from pyanaconda.modules.common.base import KickstartService from pyanaconda.modules.common.constants.services import USERS from pyanaconda.modules.common.containers import TaskContainer from pyanaconda.modules.common.structures.user import UserData from pyanaconda.modules.common.structures.group import GroupData from pyanaconda.modules.common.structures.sshkey import SshKeyData from pyanaconda.modules.users.kickstart import UsersKickstartSpecification from pyanaconda.modules.users.users_interface import UsersInterface from pyanaconda.modules.users.installation import SetRootPasswordTask, CreateUsersTask, \ CreateGroupsTask, SetSshKeysTask, ConfigureRootPasswordSSHLoginTask from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) class UsersService(KickstartService): """The Users service.""" def __init__(self): super().__init__() self.can_change_root_password_changed = Signal() self._can_change_root_password = True self.root_password_is_set_changed = Signal() self._root_password = "" self._root_password_is_crypted = False self.root_account_locked_changed = Signal() self._root_account_locked = True self._root_password_ssh_login_allowed = False self.root_password_ssh_login_allowed_changed = Signal() self.users_changed = Signal() self._users = [] self.groups_changed = Signal() self._groups = [] self.ssh_keys_changed = Signal() self._ssh_keys = [] self._rootpw_seen = False def publish(self): """Publish the module.""" TaskContainer.set_namespace(USERS.namespace) DBus.publish_object(USERS.object_path, UsersInterface(self)) DBus.register_service(USERS.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return UsersKickstartSpecification def process_kickstart(self, data): """Process the kickstart data.""" self.set_root_password(data.rootpw.password, crypted=data.rootpw.isCrypted) self.set_root_account_locked(data.rootpw.lock) self.set_root_password_ssh_login_allowed(data.rootpw.allow_ssh) # make sure the root account is locked unless a password is set in kickstart if not data.rootpw.password: log.debug("root specified in kickstart without password, locking account") self.set_root_account_locked(True) # if password was set in kickstart it can't be changed by default if data.rootpw.seen: self.set_can_change_root_password(False) self._rootpw_seen = True user_data_list = [] for user_ksdata in data.user.userList: user_data_list.append(self._ksdata_to_user_data(user_ksdata)) self.set_users(user_data_list) group_data_list = [] for group_ksdata in data.group.groupList: group_data = GroupData() group_data.name = group_ksdata.name group_data.set_gid(group_ksdata.gid) group_data_list.append(group_data) self.set_groups(group_data_list) ssh_key_data_list = [] for ssh_key_ksdata in data.sshkey.sshUserList: ssh_key_data = SshKeyData() ssh_key_data.key = ssh_key_ksdata.key ssh_key_data.username = ssh_key_ksdata.username ssh_key_data_list.append(ssh_key_data) self.set_ssh_keys(ssh_key_data_list) # pylint: disable=arguments-differ def setup_kickstart(self, data): """Set up the kickstart data.""" data.rootpw.password = self._root_password data.rootpw.isCrypted = self._root_password_is_crypted data.rootpw.lock = self.root_account_locked data.rootpw.allow_ssh = self.root_password_ssh_login_allowed for user_data in self.users: data.user.userList.append(self._user_data_to_ksdata(data.UserData(), user_data)) for group_data in self.groups: group_ksdata = data.GroupData() group_ksdata.name = group_data.name group_ksdata.gid = group_data.get_gid() data.group.groupList.append(group_ksdata) for ssh_key_data in self.ssh_keys: ssh_key_ksdata = data.SshKeyData() ssh_key_ksdata.key = ssh_key_data.key ssh_key_ksdata.username = ssh_key_data.username data.sshkey.sshUserList.append(ssh_key_ksdata) def configure_groups_with_task(self): """Return the user group configuration task. :returns: a user group configuration task """ return CreateGroupsTask( sysroot=conf.target.system_root, group_data_list=self.groups ) def configure_users_with_task(self): """Return the user configuration task. :returns: a user configuration task """ return CreateUsersTask( sysroot=conf.target.system_root, user_data_list=self.users ) def set_root_password_with_task(self): """Return the root password configuration task. :returns: a root password configuration task """ return SetRootPasswordTask( sysroot=conf.target.system_root, password=self.root_password, crypted=self.root_password_is_crypted, locked=self.root_account_locked ) def set_ssh_keys_with_task(self): """Return the SSH key configuration task. :returns: o SSH key configuration task """ return SetSshKeysTask( sysroot=conf.target.system_root, ssh_key_data_list=self.ssh_keys ) def configure_root_password_ssh_login_with_task(self): """Return the root password SSH login configuration task. :returns: a root password SSH login configuration task """ return ConfigureRootPasswordSSHLoginTask( sysroot=conf.target.system_root, password_allowed=self.root_password_ssh_login_allowed ) def install_with_tasks(self): """Return the installation tasks of this module. :returns: list of tasks """ return [ self.configure_groups_with_task(), self.configure_users_with_task(), self.set_root_password_with_task(), self.set_ssh_keys_with_task(), self.configure_root_password_ssh_login_with_task() ] def _ksdata_to_user_data(self, user_ksdata): """Apply kickstart user command data to UserData instance. :param user_ksdata: data for the kickstart user command :return: UserData instance with kickstart data applied """ user_data = UserData() user_data.name = user_ksdata.name user_data.groups = user_ksdata.groups user_data.set_uid(user_ksdata.uid) user_data.set_gid(user_ksdata.gid) user_data.homedir = user_ksdata.homedir user_data.password = user_ksdata.password user_data.is_crypted = user_ksdata.isCrypted user_data.lock = user_ksdata.lock # make sure the user account is locked by default unless a password # is set in kickstart if not user_ksdata.password: log.debug("user (%s) specified in kickstart without password, locking account", user_ksdata.name) user_data.lock = True user_data.shell = user_ksdata.shell user_data.gecos = user_ksdata.gecos return user_data def _user_data_to_ksdata(self, user_ksdata, user_data): """Convert UserData instance to kickstart user command data. :param user_ksdata: UserData instance from Kickstart :param user_data: our UserData instance :return: kickstart user command data for a single user """ user_ksdata.name = user_data.name user_ksdata.groups = user_data.groups user_ksdata.uid = user_data.get_uid() user_ksdata.gid = user_data.get_gid() user_ksdata.homedir = user_data.homedir user_ksdata.password = user_data.password user_ksdata.isCrypted = user_data.is_crypted user_ksdata.lock = user_data.lock user_ksdata.shell = user_data.shell user_ksdata.gecos = user_data.gecos return user_ksdata @property def users(self): """List of UserData instances, one per user.""" return self._users def set_users(self, users): """Set the list of UserData instances, one per user.""" self._users = users self.users_changed.emit() log.debug("A new user list has been set: %s", self._users) @property def groups(self): """List of GroupData instances, one per group.""" return self._groups def set_groups(self, groups): """Set the list of GroupData instances, one per group.""" self._groups = groups self.groups_changed.emit() log.debug("A new group list has been set: %s", self._groups) @property def ssh_keys(self): """List of SshKeyData instances, one per ssh key.""" return self._ssh_keys def set_ssh_keys(self, ssh_keys): """Set the list of SshKeyData instances, one per ssh keys.""" self._ssh_keys = ssh_keys self.ssh_keys_changed.emit() log.debug("A new ssh key list has been set: %s", self._ssh_keys) @property def can_change_root_password(self): return self._can_change_root_password def set_can_change_root_password(self, can_change_root_password): self._can_change_root_password = can_change_root_password self.can_change_root_password_changed.emit() log.debug("Can change root password state changed: %s.", can_change_root_password) @property def root_password(self): """The root password. :returns: root password (might be crypted) :rtype: str """ return self._root_password @property def root_password_is_crypted(self): """Is the root password crypted ? :returns: if root password is crypted :rtype: bool """ return self._root_password_is_crypted def set_root_password(self, root_password, crypted): """Set the crypted root password. NOTE: Setting password == "" is equivalent to calling clear_root_password(). :param str root_password: root password :param bool crypted: if the root password is crypted """ if root_password == "": self._root_password = "" self._root_password_is_crypted = False self.set_root_account_locked(True) self.root_password_is_set_changed.emit() log.debug("Root password cleared.") else: self._root_password = root_password self._root_password_is_crypted = crypted self.root_password_is_set_changed.emit() log.debug("Root password set.") def clear_root_password(self): """Clear any set root password.""" self.set_root_password("", False) @property def root_password_is_set(self): """Is the root password set ?""" return bool(self._root_password) def set_root_account_locked(self, locked): """Lock or unlock the root account. :param bool locked: True id the account should be locked, False otherwise. """ self._root_account_locked = locked self.root_account_locked_changed.emit() if locked: log.debug("Root account has been locked.") else: log.debug("Root account has been unlocked.") @property def root_account_locked(self): """Is the root account locked ?""" return self._root_account_locked def set_root_password_ssh_login_allowed(self, root_password_ssh_login_allowed): """Allow/disable root login via SSH with password. (Login as root with key is always allowed) param bool root_password_ssh_login_allowed: True to allow, False to disallow """ self._root_password_ssh_login_allowed = root_password_ssh_login_allowed self.root_password_ssh_login_allowed_changed.emit() if root_password_ssh_login_allowed: log.debug("SSH login as root with password will be allowed.") else: log.debug("SSH login as root with password will not be allowed.") @property def root_password_ssh_login_allowed(self): """Is logging in as root via SSH with password allowed ?""" return self._root_password_ssh_login_allowed @property def check_admin_user_exists(self): """Reports if at least one admin user exists. - an unlocked root account is considered to be an admin user - an unlocked user account that is member of the group "wheel" is considered to be an admin user :return: if at least one admin user exists """ # any root set from kickstart is fine if self._rootpw_seen: return True # if not set by kickstart root must not be # locked to be cosnidered admin elif self.root_password and not self.root_account_locked: return True # let's check all users for user in self.users: if not user.lock: if "wheel" in user.groups: return True # no admin user found return False