#!/usr/bin/python # -*- coding: utf-8 -*- # Authors: # Thomas Woerner # # Based on ipa-client-install code # # Copyright (C) 2017 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 . ANSIBLE_METADATA = { 'metadata_version': '1.0', 'supported_by': 'community', 'status': ['preview'], } DOCUMENTATION = ''' --- module: sssd_conf short description: Configure sssd description: Configure sssd options: servers: description: The FQDN of the IPA servers to connect to. required: false domain: description: The primary DNS domain of an existing IPA deployment. required: false realm: description: The Kerberos realm of an existing IPA deployment. required: true hostname: description: The hostname of the machine to join (FQDN). required: true services: description: The services that should be enabled in the ssd configuration. required: true krb5_offline_passwords: description: Whether user passwords are stored when the server is offline. required: false on_master: description: Whether the configuration is done on the maseter or not. required: false primary: description: Whether to use fixed server as primary IPA server. required: false preserve_sssd: description: Preserve old SSSD configuration if possible. required: false permit: description: Disable access rules by default, permit all access. required: false dns_updates: description: Configures the machine to attempt dns updates when the ip address changes. required: false all_ip_addresses: description: All routable IP addresses configured on any interface will be added to DNS. required: false author: - Thomas Woerner ''' EXAMPLES = ''' - name: Configure SSSD sssd: servers: ["server1.example.com","server2.example.com"] domain: example.com realm: EXAMPLE.COM hostname: client1.example.com services: ["ssh", "sudo"] cache_credentials: yes krb5_offline_passwords: yes ''' RETURN = ''' ''' import os import SSSDConfig from ansible.module_utils.basic import AnsibleModule from ipalib.install import sysrestore from ipaplatform.paths import paths from ipapython.ipautil import file_exists from ipaclient.install.client import get_server_connection_interface, \ configure_nsswitch_database def sssd_enable_service(module, sssdconfig, service): try: sssdconfig.new_service(service) except SSSDConfig.ServiceAlreadyExists: pass except SSSDConfig.ServiceNotRecognizedError: module.fail_json( msg="Unable to activate the %s service in SSSD config." % service) sssdconfig.activate_service(service) def main(): module = AnsibleModule( argument_spec = dict( servers=dict(required=True, type='list'), domain=dict(required=True), realm=dict(required=True), hostname=dict(required=True), services=dict(required=True, type='list'), krb5_offline_passwords=dict(required=False, type='bool'), on_master=dict(required=False, type='bool'), primary=dict(required=False, type='bool'), preserve_sssd=dict(required=False, type='bool'), permit=dict(required=False, type='bool'), dns_updates=dict(required=False, type='bool'), all_ip_addresses=dict(required=False, type='bool'), ), # required_one_of = ( [ '', '' ] ), supports_check_mode = True, ) module._ansible_debug = True cli_servers = module.params.get('servers') cli_domain = module.params.get('domain') cli_realm = module.params.get('realm') client_hostname = module.params.get('hostname') services = module.params.get('services') krb5_offline_passwords = module.params.get('krb5_offline_passwords') on_master = module.params.get('on_master') primary = module.params.get('primary') preserve_sssd = module.params.get('preserve_sssd') permit = module.params.get('permit') dns_updates = module.params.get('dns_updates') all_ip_addresses = module.params.get('all_ip_addresses') fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) client_domain = client_hostname[client_hostname.find(".")+1:] try: sssdconfig = SSSDConfig.SSSDConfig() sssdconfig.import_config() except Exception as e: if os.path.exists(paths.SSSD_CONF) and preserve_sssd: # SSSD config is in place but we are unable to read it # In addition, we are instructed to preserve it # This all means we can't use it and have to bail out module.fail_json( msg="SSSD config exists but cannot be parsed: %s" % str(e)) # SSSD configuration does not exist or we are not asked to preserve it, # create new one # We do make new SSSDConfig instance because IPAChangeConf-derived # classes have no means to reset their state and ParseError exception # could come due to parsing error from older version which cannot be # upgraded anymore, leaving sssdconfig instance practically unusable # Note that we already backed up sssd.conf before going into this # routine if isinstance(e, IOError): pass else: # It was not IOError so it must have been parsing error module.fail_json(msg="Unable to parse existing SSSD config.") module.log("New SSSD config will be created") sssdconfig = SSSDConfig.SSSDConfig() sssdconfig.new_config() try: domain = sssdconfig.new_domain(cli_domain) except SSSDConfig.DomainAlreadyExistsError: module.log("Domain %s is already configured in existing SSSD " "config, creating a new one." % cli_domain) sssdconfig = SSSDConfig.SSSDConfig() sssdconfig.new_config() domain = sssdconfig.new_domain(cli_domain) if on_master: sssd_enable_service(module, sssdconfig, 'ifp') if (("ssh" in services and file_exists(paths.SSH_CONFIG)) or ("sshd" in services and file_exists(paths.SSHD_CONFIG))): sssd_enable_service(module, sssdconfig, 'ssh') if "sudo" in services: sssd_enable_service(module, sssdconfig, 'sudo') configure_nsswitch_database(fstore, 'sudoers', ['sss'], default_value=['files']) domain.add_provider('ipa', 'id') # add discovery domain if client domain different from server domain # do not set this config in server mode (#3947) if not on_master and cli_domain != client_domain: domain.set_option('dns_discovery_domain', cli_domain) if not on_master: if primary: domain.set_option('ipa_server', ', '.join(cli_servers)) else: domain.set_option('ipa_server', '_srv_, %s' % ', '.join(cli_servers)) else: domain.set_option('ipa_server_mode', 'True') # the master should only use itself for Kerberos domain.set_option('ipa_server', cli_servers[0]) # increase memcache timeout to 10 minutes when in server mode try: nss_service = sssdconfig.get_service('nss') except SSSDConfig.NoServiceError: nss_service = sssdconfig.new_service('nss') nss_service.set_option('memcache_timeout', 600) sssdconfig.save_service(nss_service) domain.set_option('ipa_domain', cli_domain) domain.set_option('ipa_hostname', client_hostname) if cli_domain.lower() != cli_realm.lower(): domain.set_option('krb5_realm', cli_realm) # Might need this if /bin/hostname doesn't return a FQDN # domain.set_option('ipa_hostname', 'client.example.com') domain.add_provider('ipa', 'auth') domain.add_provider('ipa', 'chpass') if not permit: domain.add_provider('ipa', 'access') else: domain.add_provider('permit', 'access') domain.set_option('cache_credentials', True) # SSSD will need TLS for checking if ipaMigrationEnabled attribute is set # Note that SSSD will force StartTLS because the channel is later used for # authentication as well if password migration is enabled. Thus set # the option unconditionally. domain.set_option('ldap_tls_cacert', paths.IPA_CA_CRT) if dns_updates: domain.set_option('dyndns_update', True) if all_ip_addresses: domain.set_option('dyndns_iface', '*') else: iface = get_server_connection_interface(cli_servers[0]) domain.set_option('dyndns_iface', iface) if krb5_offline_passwords: domain.set_option('krb5_store_password_if_offline', True) domain.set_active(True) sssdconfig.save_domain(domain) sssdconfig.write(paths.SSSD_CONF) module.exit_json(changed=True) if __name__ == '__main__': main()