#!/usr/bin/python2 -E # # Authors: # Rob Crittenden # # Copyright (C) 2012 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty 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, see . # # Configure the automount client for ldap. from __future__ import print_function import sys import os import time import tempfile import gssapi try: from xml.etree import cElementTree as etree except ImportError: from xml.etree import ElementTree as etree import SSSDConfig # pylint: disable=import-error from six.moves.urllib.parse import urlsplit # pylint: enable=import-error from optparse import OptionParser # pylint: disable=deprecated-module from ipaclient.install import ipachangeconf, ipadiscovery from ipalib import api, errors from ipalib.install import sysrestore from ipalib.install.kinit import kinit_keytab from ipapython import ipautil from ipapython.ipa_log_manager import root_logger, standard_logging_setup from ipapython.dn import DN from ipaplatform.constants import constants from ipaplatform.tasks import tasks from ipaplatform import services from ipaplatform.paths import paths def parse_options(): usage = "%prog [options]\n" parser = OptionParser(usage=usage) parser.add_option("--server", dest="server", help="FQDN of IPA server") parser.add_option("--location", dest="location", help="Automount location", default="default") parser.add_option("-S", "--no-sssd", dest="sssd", action="store_false", default=True, help="Do not configure the client to use SSSD for automount") parser.add_option("--debug", dest="debug", action="store_true", default=False, help="enable debugging") parser.add_option("-U", "--unattended", dest="unattended", action="store_true", default=False, help="unattended installation never prompts the user") parser.add_option("--uninstall", dest="uninstall", action="store_true", default=False, help="Unconfigure automount") options, args = parser.parse_args() return options, args def wait_for_sssd(): """ It takes a bit for sssd to get going, lets loop until it is serving data. This function returns nothing. """ n = 0 found = False time.sleep(1) while n < 10 and not found: try: ipautil.run(["getent", "passwd", "admin@%s" % api.env.realm]) found = True except Exception: time.sleep(1) n = n + 1 # This should never happen but if it does, may as well warn the user if not found: err_msg = ("Unable to find 'admin' user with " "'getent passwd admin@%s'!" % api.env.realm) root_logger.debug(err_msg) print(err_msg) print("This may mean that sssd didn't re-start properly after the configuration changes.") def configure_xml(fstore): authconf = paths.AUTOFS_LDAP_AUTH_CONF fstore.backup_file(authconf) try: tree = etree.parse(authconf) except IOError as e: root_logger.debug('Unable to open file %s' % e) root_logger.debug('Creating new from template') tree = etree.ElementTree( element=etree.Element('autofs_ldap_sasl_conf') ) element = tree.getroot() if element.tag != 'autofs_ldap_sasl_conf': raise RuntimeError('Invalid XML root in file %s' % authconf) element.set('usetls', 'no') element.set('tlsrequired', 'no') element.set('authrequired', 'yes') element.set('authtype', 'GSSAPI') element.set('clientprinc', 'host/%s@%s' % (api.env.host, api.env.realm)) try: tree.write(authconf, xml_declaration=True, encoding='UTF-8') except IOError as e: print("Unable to write %s: %s" % (authconf, e)) else: print("Configured %s" % authconf) def configure_nsswitch(fstore, options): """ Point automount to ldap in nsswitch.conf. This function is for non-SSSD setups only """ fstore.backup_file(paths.NSSWITCH_CONF) conf = ipachangeconf.IPAChangeConf("IPA Installer") conf.setOptionAssignment(':') nss_value = ' files ldap' opts = [{'name':'automount', 'type':'option', 'action':'set', 'value':nss_value}, {'name':'empty', 'type':'empty'}] conf.changeConf(paths.NSSWITCH_CONF, opts) print("Configured %s" % paths.NSSWITCH_CONF) def configure_autofs_sssd(fstore, statestore, autodiscover, options): try: sssdconfig = SSSDConfig.SSSDConfig() sssdconfig.import_config() domains = sssdconfig.list_active_domains() except Exception as e: sys.exit(e) try: sssdconfig.new_service('autofs') except SSSDConfig.ServiceAlreadyExists: pass except SSSDConfig.ServiceNotRecognizedError: root_logger.error("Unable to activate the Autofs service in SSSD config.") root_logger.info( "Please make sure you have SSSD built with autofs support installed.") root_logger.info( "Configure autofs support manually in /etc/sssd/sssd.conf.") sys.exit("Cannot create the autofs service in sssd.conf") sssdconfig.activate_service('autofs') domain = None for name in domains: domain = sssdconfig.get_domain(name) try: provider = domain.get_option('id_provider') except SSSDConfig.NoOptionError: continue if provider == "ipa": domain.add_provider('ipa', 'autofs') try: domain.get_option('ipa_automount_location') sys.exit('An automount location is already configured') except SSSDConfig.NoOptionError: domain.set_option('ipa_automount_location', options.location) break if domain is None: sys.exit('SSSD is not configured.') sssdconfig.save_domain(domain) sssdconfig.write(paths.SSSD_CONF) statestore.backup_state('autofs', 'sssd', True) sssd = services.service('sssd') sssd.restart() print("Restarting sssd, waiting for it to become available.") wait_for_sssd() def configure_autofs(fstore, statestore, autodiscover, server, options): """ fstore: the FileStore to back up files in options.server: the IPA server to use options.location: the Automount location to use """ if not autodiscover: ldap_uri = "ldap://%s" % server else: ldap_uri = "ldap:///%s" % api.env.basedn search_base = str(DN(('cn', options.location), api.env.container_automount, api.env.basedn)) replacevars = { 'MAP_OBJECT_CLASS': 'automountMap', 'ENTRY_OBJECT_CLASS': 'automount', 'MAP_ATTRIBUTE': 'automountMapName', 'ENTRY_ATTRIBUTE': 'automountKey', 'VALUE_ATTRIBUTE': 'automountInformation', 'SEARCH_BASE': search_base, 'LDAP_URI': ldap_uri, } ipautil.backup_config_and_replace_variables(fstore, paths.SYSCONFIG_AUTOFS, replacevars=replacevars) tasks.restore_context(paths.SYSCONFIG_AUTOFS) statestore.backup_state('autofs', 'sssd', False) print("Configured %s" % paths.SYSCONFIG_AUTOFS) def configure_autofs_common(fstore, statestore, options): autofs = services.knownservices.autofs statestore.backup_state('autofs', 'enabled', autofs.is_enabled()) statestore.backup_state('autofs', 'running', autofs.is_running()) try: autofs.restart() print("Started %s" % autofs.service_name) except Exception as e: root_logger.error("%s failed to restart: %s", autofs.service_name, e) try: autofs.enable() except Exception as e: print("Failed to configure automatic startup of the %s daemon" % (autofs.service_name)) root_logger.error("Failed to enable automatic startup of the %s daemon: %s" % (autofs.service_name, str(e))) def uninstall(fstore, statestore): print("Restoring configuration") if fstore.has_file(paths.SYSCONFIG_AUTOFS): fstore.restore_file(paths.SYSCONFIG_AUTOFS) if fstore.has_file(paths.NSSWITCH_CONF): fstore.restore_file(paths.NSSWITCH_CONF) if fstore.has_file(paths.AUTOFS_LDAP_AUTH_CONF): fstore.restore_file(paths.AUTOFS_LDAP_AUTH_CONF) if fstore.has_file(paths.SYSCONFIG_NFS): fstore.restore_file(paths.SYSCONFIG_NFS) if fstore.has_file(paths.IDMAPD_CONF): fstore.restore_file(paths.IDMAPD_CONF) if statestore.has_state('autofs'): enabled = statestore.restore_state('autofs', 'enabled') running = statestore.restore_state('autofs', 'running') sssd = statestore.restore_state('autofs', 'sssd') autofs = services.knownservices.autofs if not enabled: autofs.disable() if not running: autofs.stop() if sssd: try: sssdconfig = SSSDConfig.SSSDConfig() sssdconfig.import_config() sssdconfig.deactivate_service('autofs') domains = sssdconfig.list_active_domains() for name in domains: domain = sssdconfig.get_domain(name) try: provider = domain.get_option('id_provider') except SSSDConfig.NoOptionError: continue if provider == "ipa": domain.remove_option('ipa_automount_location') domain.remove_provider('autofs') break sssdconfig.save_domain(domain) sssdconfig.write(paths.SSSD_CONF) sssd = services.service('sssd') sssd.restart() wait_for_sssd() except Exception as e: print('Unable to restore SSSD configuration: %s' % str(e)) root_logger.debug('Unable to restore SSSD configuration: %s' % str(e)) if statestore.has_state('rpcidmapd'): enabled = statestore.restore_state('rpcidmapd', 'enabled') running = statestore.restore_state('rpcidmapd', 'running') rpcidmapd = services.knownservices.rpcidmapd if not enabled: rpcidmapd.disable() if not running: rpcidmapd.stop() if statestore.has_state('rpcgssd'): enabled = statestore.restore_state('rpcgssd', 'enabled') running = statestore.restore_state('rpcgssd', 'running') rpcgssd = services.knownservices.rpcgssd if not enabled: rpcgssd.disable() if not running: rpcgssd.stop() return 0 def configure_nfs(fstore, statestore): """ Configure secure NFS """ replacevars = { constants.SECURE_NFS_VAR: 'yes', } ipautil.backup_config_and_replace_variables(fstore, paths.SYSCONFIG_NFS, replacevars=replacevars) tasks.restore_context(paths.SYSCONFIG_NFS) print("Configured %s" % paths.SYSCONFIG_NFS) # Prepare the changes # We need to use IPAChangeConf as simple regexp substitution # does not cut it here conf = ipachangeconf.IPAChangeConf("IPA automount installer") conf.case_insensitive_sections = False conf.setOptionAssignment(" = ") conf.setSectionNameDelimiters(("[", "]")) changes = [conf.setOption('Domain', api.env.domain)] section_with_changes = [conf.setSection('General', changes)] # Backup the file and apply the changes fstore.backup_file(paths.IDMAPD_CONF) conf.changeConf(paths.IDMAPD_CONF, section_with_changes) tasks.restore_context(paths.IDMAPD_CONF) print("Configured %s" % paths.IDMAPD_CONF) rpcidmapd = services.knownservices.rpcidmapd statestore.backup_state('rpcidmapd', 'enabled', rpcidmapd.is_enabled()) statestore.backup_state('rpcidmapd', 'running', rpcidmapd.is_running()) try: rpcidmapd.restart() print("Started %s" % rpcidmapd.service_name) except Exception as e: root_logger.error("%s failed to restart: %s", rpcidmapd.service_name, e) try: rpcidmapd.enable() except Exception as e: print("Failed to configure automatic startup of the %s daemon" % (rpcidmapd.service_name)) root_logger.error("Failed to enable automatic startup of the %s daemon: %s" % (rpcidmapd.service_name, str(e))) rpcgssd = services.knownservices.rpcgssd statestore.backup_state('rpcgssd', 'enabled', rpcgssd.is_enabled()) statestore.backup_state('rpcgssd', 'running', rpcgssd.is_running()) try: rpcgssd.restart() print("Started %s" % rpcgssd.service_name) except Exception as e: root_logger.error("%s failed to restart: %s", rpcgssd.service_name, e) try: rpcgssd.enable() except Exception as e: print("Failed to configure automatic startup of the %s daemon" % (rpcgssd.service_name)) root_logger.error("Failed to enable automatic startup of the %s daemon: %s" % (rpcgssd.service_name, str(e))) def main(): fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE) if not fstore.has_files() and not os.path.exists(paths.IPA_DEFAULT_CONF): sys.exit('IPA client is not configured on this system.\n') options, _args = parse_options() standard_logging_setup( paths.IPACLIENT_INSTALL_LOG, verbose=False, debug=options.debug, filemode='a', console_format='%(message)s') if options.uninstall: return uninstall(fstore, statestore) cfg = dict( context='cli_installer', confdir=paths.ETC_IPA, in_server=False, debug=options.debug, verbose=0, ) api.bootstrap(**cfg) api.finalize() ca_cert_path = None if os.path.exists(paths.IPA_CA_CRT): ca_cert_path = paths.IPA_CA_CRT if statestore.has_state('autofs'): sys.exit('automount is already configured on this system.\n') autodiscover = False ds = ipadiscovery.IPADiscovery() if not options.server: print("Searching for IPA server...") ret = ds.search(ca_cert_path=ca_cert_path) root_logger.debug('Executing DNS discovery') if ret == ipadiscovery.NO_LDAP_SERVER: root_logger.debug('Autodiscovery did not find LDAP server') s = urlsplit(api.env.xmlrpc_uri) server = [s.netloc] root_logger.debug('Setting server to %s' % s.netloc) else: autodiscover = True if not ds.servers: sys.exit('Autodiscovery was successful but didn\'t return a server') root_logger.debug('Autodiscovery success, possible servers %s' % ','.join(ds.servers)) server = ds.servers[0] else: server = options.server root_logger.debug("Verifying that %s is an IPA server" % server) ldapret = ds.ipacheckldap(server, api.env.realm, ca_cert_path) if ldapret[0] == ipadiscovery.NO_ACCESS_TO_LDAP: print("Anonymous access to the LDAP server is disabled.") print("Proceeding without strict verification.") print("Note: This is not an error if anonymous access has been explicitly restricted.") elif ldapret[0] == ipadiscovery.NO_TLS_LDAP: root_logger.warning("Unencrypted access to LDAP is not supported.") elif ldapret[0] != 0: sys.exit('Unable to confirm that %s is an IPA server' % server) if not autodiscover: print("IPA server: %s" % server) root_logger.debug('Using fixed server %s' % server) else: print("IPA server: DNS discovery") root_logger.debug('Configuring to use DNS discovery') print("Location: %s" % options.location) root_logger.debug('Using automount location %s' % options.location) ccache_dir = tempfile.mkdtemp() ccache_name = os.path.join(ccache_dir, 'ccache') try: try: host_princ = str('host/%s@%s' % (api.env.host, api.env.realm)) kinit_keytab(host_princ, paths.KRB5_KEYTAB, ccache_name) os.environ['KRB5CCNAME'] = ccache_name except gssapi.exceptions.GSSError as e: sys.exit("Failed to obtain host TGT: %s" % e) # Now we have a TGT, connect to IPA try: api.Backend.rpcclient.connect() except errors.KerberosError as e: sys.exit('Cannot connect to the server due to ' + str(e)) try: # Use the RPC directly so older servers are supported api.Backend.rpcclient.forward( 'automountlocation_show', ipautil.fsdecode(options.location), version=u'2.0', ) except errors.VersionError as e: sys.exit('This client is incompatible: ' + str(e)) except errors.NotFound: sys.exit("Automount location '%s' does not exist" % options.location) except errors.PublicError as e: sys.exit("Cannot connect to the server due to generic error: %s" % str(e)) finally: os.remove(ccache_name) os.rmdir(ccache_dir) if not options.unattended and not ipautil.user_input("Continue to configure the system with these values?", False): sys.exit("Installation aborted") try: if not options.sssd: configure_nsswitch(fstore, options) configure_nfs(fstore, statestore) if options.sssd: configure_autofs_sssd(fstore, statestore, autodiscover, options) else: configure_xml(fstore) configure_autofs(fstore, statestore, autodiscover, server, options) configure_autofs_common(fstore, statestore, options) except Exception as e: root_logger.debug('Raised exception %s' % e) print("Installation failed. Rolling back changes.") uninstall(fstore, statestore) return 1 return 0 try: if not os.geteuid()==0: sys.exit("\nMust be run as root\n") sys.exit(main()) except SystemExit as e: sys.exit(e) except RuntimeError as e: sys.exit(e) except (KeyboardInterrupt, EOFError): sys.exit(1)