#!/usr/bin/python # -*- coding: utf-8 -*- # Authors: # Thomas Woerner # # Based on ipa-replica-install code # # Copyright (C) 2018 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 . from __future__ import print_function ANSIBLE_METADATA = { 'metadata_version': '1.0', 'supported_by': 'community', 'status': ['preview'], } DOCUMENTATION = ''' --- module: ipareplica_prepare short description: Prepare ipa replica installation description: Prepare ipa replica installation: Create IPA configuration file, run install checks again and also update the host name and the hosts file if needed. The tests and also the results from ipareplica_test are needed. ptions: dm_password: description: Directory Manager password required: yes password: description: Admin user kerberos password required: yes ip_addresses: description: List of Master Server IP Addresses required: no domain: description: Primary DNS domain of the IPA deployment required: yes realm: description: Kerberos realm name of the IPA deployment required: yes hostname: description: Fully qualified name of this host required: yes ca_cert_files: description: List of iles containing CA certificates for the service certificate files required: yes no_host_dns: description: Do not use DNS for hostname lookup during installation required: yes setup_adtrust: description: required: yes setup_kra: description: required: yes setup_dns: description: required: yes external_ca: description: required: yes external_cert_files: description: required: yes subject_base: description: required: yes ca_subject: description: required: yes reverse_zones: description: required: yes no_reverse: description: required: yes auto_reverse: description: required: yes forwarders: description: required: yes no_forwarders: description: required: yes auto_forwarders: description: required: yes forward_policy: description: required: yes enable_compat: description: required: yes netbios_name: description: required: yes rid_base: description: required: yes secondary_rid_base: description: required: yes setup_ca: description: required: yes _hostname_overridden: description: required: yes author: - Thomas Woerner ''' EXAMPLES = ''' ''' RETURN = ''' ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.ansible_ipa_replica import * def main(): ansible_module = AnsibleModule( argument_spec = dict( ### basic ### dm_password=dict(required=False, no_log=True), password=dict(required=False, no_log=True), ip_addresses=dict(required=False, type='list', default=[]), domain=dict(required=False), realm=dict(required=False), hostname=dict(required=False), principal=dict(required=True), ca_cert_files=dict(required=False, type='list', default=[]), no_host_dns=dict(required=False, type='bool', default=False), ### server ### setup_adtrust=dict(required=False, type='bool'), setup_ca=dict(required=False, type='bool'), setup_kra=dict(required=False, type='bool'), setup_dns=dict(required=False, type='bool'), ### ssl certificate ### dirsrv_cert_files=dict(required=False, type='list', default=[]), dirsrv_pin=dict(required=False), http_cert_files=dict(required=False, type='list', default=[]), http_pin=dict(required=False), pkinit_cert_files=dict(required=False, type='list', default=[]), pkinit_pin=dict(required=False), ### client ### keytab=dict(required=False), mkhomedir=dict(required=False, type='bool'), force_join=dict(required=False, type='bool'), no_ntp=dict(required=False, type='bool'), ssh_trust_dns=dict(required=False, type='bool'), no_ssh=dict(required=False, type='bool'), no_sshd=dict(required=False, type='bool'), no_dns_sshfp=dict(required=False, type='bool'), ### certificate system ### #subject_base=dict(required=False), no_dnssec_validation=dict(required=False, type='bool'), ### dns ### ### ad trust ### ### additional ### server=dict(required=True), skip_conncheck=dict(required=False, type='bool'), ), supports_check_mode = True, ) ansible_module._ansible_debug = True ansible_log = AnsibleModuleLog(ansible_module) # get parameters # options.dm_password = ansible_module.params.get('dm_password') options.password = options.dm_password options.admin_password = ansible_module.params.get('password') options.ip_addresses = ansible_module_get_parsed_ip_addresses( ansible_module) options.domain_name = ansible_module.params.get('domain') options.realm_name = ansible_module.params.get('realm') options.host_name = ansible_module.params.get('hostname') options.principal = ansible_module.params.get('principal') options.ca_cert_files = ansible_module.params.get('ca_cert_files') options.no_host_dns = ansible_module.params.get('no_host_dns') ### server ### options.setup_adtrust = ansible_module.params.get('setup_adtrust') options.setup_ca = ansible_module.params.get('setup_ca') options.setup_kra = ansible_module.params.get('setup_kra') options.setup_dns = ansible_module.params.get('setup_dns') ### ssl certificate ### options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files') options.http_cert_files = ansible_module.params.get('http_cert_files') options.pkinit_cert_files = ansible_module.params.get('pkinit_cert_files') ### client ### options.keytab = ansible_module.params.get('keytab') options.mkhomedir = ansible_module.params.get('mkhomedir') options.force_join = ansible_module.params.get('force_join') options.no_ntp = ansible_module.params.get('no_ntp') options.ssh_trust_dns = ansible_module.params.get('ssh_trust_dns') options.no_ssh = ansible_module.params.get('no_ssh') options.no_sshd = ansible_module.params.get('no_sshd') options.no_dns_sshfp = ansible_module.params.get('no_dns_sshfp') ### certificate system ### options.external_ca = ansible_module.params.get('external_ca') options.external_cert_files = ansible_module.params.get( 'external_cert_files') #options.subject_base = ansible_module.params.get('subject_base') #options.ca_subject = ansible_module.params.get('ca_subject') options.no_dnssec_validation = ansible_module.params.get('no_dnssec_validation') ### dns ### options.reverse_zones = ansible_module.params.get('reverse_zones') options.no_reverse = ansible_module.params.get('no_reverse') options.auto_reverse = ansible_module.params.get('auto_reverse') options.forwarders = ansible_module.params.get('forwarders') options.no_forwarders = ansible_module.params.get('no_forwarders') options.auto_forwarders = ansible_module.params.get('auto_forwarders') options.forward_policy = ansible_module.params.get('forward_policy') ### additional ### #options._host_name_overridden = ansible_module.params.get( # '_hostname_overridden') options.server = ansible_module.params.get('server') options.skip_conncheck = ansible_module.params.get('skip_conncheck') # init # fstore = sysrestore.FileStore(paths.SYSRESTORE) sstore = sysrestore.StateFile(paths.SYSRESTORE) # prepare (install prepare, install checks) # ########################################################################## # replica promote_check ################################################## ########################################################################## ansible_log.debug("== PROMOTE CHECK ==") #ansible_log.debug("-- NO_NTP --") # already done in test ## check selinux status, http and DS ports, NTP conflicting services #common_check(options.no_ntp) sstore = sysrestore.StateFile(paths.SYSRESTORE) fstore = sysrestore.FileStore(paths.SYSRESTORE) installer._enrollment_performed = False installer._top_dir = tempfile.mkdtemp("ipa") #with ipautil.private_ccache(): dir_path = tempfile.mkdtemp(prefix='krbcc') os.environ['KRB5CCNAME'] = os.path.join(dir_path, 'ccache') ansible_log.debug("-- API --") env = Env() env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) env._finalize_core(**dict(constants.DEFAULT_CONFIG)) # pylint: disable=no-member xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=installutils.realm_to_ldapi_uri(env.realm), xmlrpc_uri=xmlrpc_uri) # pylint: enable=no-member api.finalize() ansible_log.debug("-- REPLICA_CONFIG --") config = ReplicaConfig() config.realm_name = api.env.realm config.host_name = api.env.host config.domain_name = api.env.domain config.master_host_name = api.env.server config.ca_host_name = api.env.ca_host config.kra_host_name = config.ca_host_name config.ca_ds_port = 389 config.setup_ca = options.setup_ca config.setup_kra = options.setup_kra config.dir = installer._top_dir config.basedn = api.env.basedn # load and check certificates # ansible_log.debug("-- CERT_FILES --") http_pkcs12_file = None http_pkcs12_info = None http_ca_cert = None dirsrv_pkcs12_file = None dirsrv_pkcs12_info = None dirsrv_ca_cert = None pkinit_pkcs12_file = None pkinit_pkcs12_info = None pkinit_ca_cert = None if options.http_cert_files: ansible_log.debug("-- HTTP_CERT_FILES --") if options.http_pin is None: ansible_module.fail_json(msg= "Apache Server private key unlock password required") http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12( cert_files=options.http_cert_files, key_password=options.http_pin, key_nickname=options.http_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) http_pkcs12_info = (http_pkcs12_file.name, http_pin) if options.dirsrv_cert_files: ansible_log.debug("-- DIRSRV_CERT_FILES --") if options.dirsrv_pin is None: ansible_module.fail_json(msg= "Directory Server private key unlock password required") dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12( cert_files=options.dirsrv_cert_files, key_password=options.dirsrv_pin, key_nickname=options.dirsrv_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) if options.pkinit_cert_files: ansible_log.debug("-- PKINIT_CERT_FILES --") if options.pkinit_pin is None: ansible_module.fail_json(msg= "Kerberos KDC private key unlock password required") pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, realm_name=config.realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and http_ca_cert != dirsrv_ca_cert): ansible_module.fail_json( msg="Apache Server SSL certificate and Directory " "Server SSL certificate are not signed by the same" " CA certificate") if (options.http_cert_files and options.pkinit_cert_files and http_ca_cert != pkinit_ca_cert): ansible_module.fail_json( msg="Apache Server SSL certificate and PKINIT KDC " "certificate are not signed by the same CA " "certificate") ansible_log.debug("-- FQDN --") installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns) ansible_log.debug("-- KINIT_KEYTAB --") ccache = os.environ['KRB5CCNAME'] kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env), paths.KRB5_KEYTAB, ccache) ansible_log.debug("-- CA_CRT --") cafile = paths.IPA_CA_CRT if not os.path.isfile(cafile): ansible_module.fail_json( msg="CA cert file is not available! Please reinstall" "the client and try again.") ansible_log.debug("-- REMOTE_API --") ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name) xmlrpc_uri = 'https://{}/ipa/xml'.format( ipautil.format_netloc(config.master_host_name)) remote_api = create_api(mode=None) remote_api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=ldapuri, xmlrpc_uri=xmlrpc_uri) remote_api.finalize() installer._remote_api = remote_api ansible_log.debug("-- RPC_CLIENT --") with rpc_client(remote_api) as client: check_remote_version(client, parse_version(api.env.version)) check_remote_fips_mode(client, api.env.fips_mode) conn = remote_api.Backend.ldap2 replman = None try: ansible_log.debug("-- CONNECT --") # Try out authentication conn.connect(ccache=ccache) replman = ReplicationManager(config.realm_name, config.master_host_name, None) ansible_log.debug("-- CHECK IPA_DOMAIN --") promotion_check_ipa_domain(conn, remote_api.env.basedn) ansible_log.debug("-- CHECK DOMAIN_LEVEL --") # Make sure that domain fulfills minimal domain level # requirement domain_level = current_domain_level(remote_api) check_domain_level_is_supported(domain_level) if domain_level < constants.MIN_DOMAIN_LEVEL: ansible_module.fail_json( msg= "Cannot promote this client to a replica. The domain level " "must be raised to {mindomainlevel} before the replica can be " "installed".format( mindomainlevel=constants.MIN_DOMAIN_LEVEL)) ansible_log.debug("-- CHECK AUTHORIZATION --") # Check authorization result = remote_api.Command['hostgroup_find']( cn=u'ipaservers', host=[unicode(api.env.host)] )['result'] add_to_ipaservers = not result ansible_log.debug("-- ADD_TO_IPASERVERS --") if add_to_ipaservers: if options.password and not options.admin_password: raise errors.ACIError(info="Not authorized") if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: installutils.check_creds(options, config.realm_name) installer._ccache = os.environ.get('KRB5CCNAME') finally: os.environ['KRB5CCNAME'] = ccache conn.disconnect() conn.connect(ccache=installer._ccache) try: result = remote_api.Command['hostgroup_show']( u'ipaservers', all=True, rights=True )['result'] if 'w' not in result['attributelevelrights']['member']: raise errors.ACIError(info="Not authorized") finally: ansible_log.debug("-- RECONNECT --") conn.disconnect() conn.connect(ccache=ccache) ansible_log.debug("-- CHECK FOR REPLICATION AGREEMENT --") # Check that we don't already have a replication agreement if replman.get_replication_agreement(config.host_name): msg = ("A replication agreement for this host already exists. " "It needs to be removed.\n" "Run this command:\n" " %% ipa-replica-manage del {host} --force" .format(host=config.host_name)) raise ScriptError(msg, rval=3) ansible_log.debug("-- DETECT REPLICATION MANAGER GROUP --") # Detect if the other master can handle replication managers # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'), ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name)) try: conn.get_entry(dn) except errors.NotFound: msg = ("The Replication Managers group is not available in " "the domain. Replica promotion requires the use of " "Replication Managers to be able to replicate data. " "Upgrade the peer master or use the ipa-replica-prepare " "command on the master and use a prep file to install " "this replica.") logger.error("%s", msg) raise ScriptError(rval=3) ansible_log.debug("-- CHECK DNS_MASTERS --") dns_masters = remote_api.Object['dnsrecord'].get_dns_masters() if dns_masters: if not options.no_host_dns: logger.debug('Check forward/reverse DNS resolution') resolution_ok = ( check_dns_resolution(config.master_host_name, dns_masters) and check_dns_resolution(config.host_name, dns_masters)) if not resolution_ok and installer.interactive: if not ipautil.user_input("Continue?", False): raise ScriptError(rval=0) else: logger.debug('No IPA DNS servers, ' 'skipping forward/reverse resolution check') ansible_log.debug("-- GET_IPA_CONFIG --") entry_attrs = conn.get_ipa_config() subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0] if subject_base is not None: config.subject_base = DN(subject_base) ansible_log.debug("-- SEARCH FOR CA --") # Find if any server has a CA ca_host = service.find_providing_server( 'CA', conn, config.ca_host_name) if ca_host is not None: config.ca_host_name = ca_host ca_enabled = True if options.dirsrv_cert_files: logger.error("Certificates could not be provided when " "CA is present on some master.") raise ScriptError(rval=3) else: if options.setup_ca: logger.error("The remote master does not have a CA " "installed, can't set up CA") raise ScriptError(rval=3) ca_enabled = False if not options.dirsrv_cert_files: logger.error("Cannot issue certificates: a CA is not " "installed. Use the --http-cert-file, " "--dirsrv-cert-file options to provide " "custom certificates.") raise ScriptError(rval=3) ansible_log.debug("-- SEARCH FOR KRA --") kra_host = service.find_providing_server( 'KRA', conn, config.kra_host_name) if kra_host is not None: config.kra_host_name = kra_host kra_enabled = True else: if options.setup_kra: logger.error("There is no KRA server in the domain, " "can't setup a KRA clone") raise ScriptError(rval=3) kra_enabled = False ansible_log.debug("-- CHECK CA --") if ca_enabled: options.realm_name = config.realm_name options.host_name = config.host_name ca.install_check(False, config, options) ansible_log.debug(" ca.external_cert_file=%s" % repr(ca.external_cert_file)) ansible_log.debug(" ca.external_ca_file=%s" % repr(ca.external_ca_file)) # TODO # TODO # Save global vars external_cert_file, external_ca_file for # later use # TODO # TODO ansible_log.debug("-- CHECK KRA --") if kra_enabled: try: kra.install_check(remote_api, config, options) except RuntimeError as e: raise ScriptError(e) ansible_log.debug("-- CHECK DNS --") if options.setup_dns: dns.install_check(False, remote_api, True, options, config.host_name) config.ips = dns.ip_addresses else: config.ips = installutils.get_server_ip_address( config.host_name, not installer.interactive, False, options.ip_addresses) # check addresses here, dns module is doing own check no_matching_interface_for_ip_address_warning(config.ips) ansible_log.debug("-- CHECK ADTRUST --") if options.setup_adtrust: adtrust.install_check(False, options, remote_api) except errors.ACIError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nInsufficient privileges to promote the server." "\nPossible issues:" "\n- A user has insufficient privileges" "\n- This client has insufficient privileges " "to become an IPA replica") except errors.LDAPError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nUnable to connect to LDAP server %s" % config.master_host_name) finally: if replman and replman.conn: ansible_log.debug("-- UNBIND REPLMAN--") replman.conn.unbind() if conn.isconnected(): ansible_log.debug("-- DISCONNECT --") conn.disconnect() ansible_log.debug("-- CHECK CONNECTION --") # check connection if not options.skip_conncheck: if add_to_ipaservers: # use user's credentials when the server host is not ipaservers if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: with redirect_stdout(ansible_log): replica_conn_check( config.master_host_name, config.host_name, config.realm_name, options.setup_ca, 389, options.admin_password, principal=options.principal, ca_cert_file=cafile) finally: if add_to_ipaservers: os.environ['KRB5CCNAME'] = ccache installer._ca_enabled = ca_enabled installer._kra_enabled = kra_enabled installer._ca_file = cafile installer._fstore = fstore installer._sstore = sstore installer._config = config installer._add_to_ipaservers = add_to_ipaservers # done # ansible_module.exit_json(changed=True, ccache=ccache, installer_ccache=installer._ccache, subject_base=str(config.subject_base), _ca_enabled=ca_enabled, _ca_subject=str(options._ca_subject), _subject_base=str(options._subject_base) if options._subject_base is not None else None, _kra_enabled=kra_enabled, _ca_file=cafile, _top_dir=installer._top_dir, _add_to_ipaservers=add_to_ipaservers, _dirsrv_pkcs12_file=dirsrv_pkcs12_file, _dirsrv_pkcs12_info=dirsrv_pkcs12_info, _dirsrv_ca_cert=dirsrv_ca_cert, _http_pkcs12_file=http_pkcs12_file, _http_pkcs12_info=http_pkcs12_info, _http_ca_cert=http_ca_cert, _pkinit_pkcs12_file=pkinit_pkcs12_file, _pkinit_pkcs12_info=pkinit_pkcs12_info, _pkinit_ca_cert=pkinit_ca_cert, no_dnssec_validation=options.no_dnssec_validation, config_setup_ca=config.setup_ca, config_master_host_name=config.master_host_name, config_ca_host_name=config.ca_host_name, config_ips=[ str(ip) for ip in config.ips ]) if __name__ == '__main__': main()