#!/usr/bin/python # -*- coding: utf-8 -*- # Authors: # Thomas Woerner # # Copyright (C) 2019 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 . import os import sys import shutil import tempfile import argparse import traceback import subprocess def parse_options(): usage = "Usage: anisble-ipa-server-install [options] " parser = argparse.ArgumentParser(usage=usage) parser.add_argument("--version", dest="version", action="store_true", help="show program's version number and exit") parser.add_argument("-U", "--unattended", dest="unattended", action="store_true", help="unattended (un)installation never prompts the " "user") parser.add_argument("--uninstall", dest="uninstall", action="store_true", help="uninstall an existing installation. The " "uninstall can be run with --unattended option") # basic parser.add_argument("-p", "--ds-password", dest="dm_password", default=None, help="Directory Manager password") parser.add_argument("-a", "--admin-password", dest="admin_password", default=None, help="admin user kerberos password") parser.add_argument("--ip-address", dest="ip_addresses", metavar="IP_ADDRESS", action='append', default=None, help="Master Server IP Address. This option can be " "used multiple times") parser.add_argument("-n", "--domain", dest="domain", metavar="DOMAIN_NAME", default=None, help="primary DNS domain of the IPA deployment (not " "necessarily related to the current hostname)") parser.add_argument("-r", "--realm", dest="realm", metavar="REALM_NAME", default=None, help="Kerberos realm name of the IPA deployment " "(typically un upper-cased name of the primary DNS " "domain)") parser.add_argument("--hostname", dest="hostname", metavar="HOST_NAME", default=None, help="fully qualified name of this host") parser.add_argument("--ca-cert-file", dest="ca_cert_file", metavar="FILE", default=None, help="File containing CA certificates for the " "service certificate files") parser.add_argument("--pki-config-override", dest="pki_config_override", default=None, help="Path to ini file with config overrides.") parser.add_argument("--no-host-dns", dest="no_host_dns", action="store_true", help="Do not use DNS for hostname lookup during " "installation") # server parser.add_argument("--setup-adtrust", dest="setup_adtrust", action="store_true", help="configure AD trust capability") parser.add_argument("--setup-kra", dest="setup_kra", action="store_true", help="configure a dogtag KRA") parser.add_argument("--setup-dns", dest="setup_dns", action="store_true", help="configure bind with our zone") parser.add_argument("--idstart", dest="idstart", type=int, default=None, help="The starting value for the IDs range (default " "random)") parser.add_argument("--idmax", dest="idmax", default=None, type=int, help="The max value for the IDs range (default: " "idstart+199999)") parser.add_argument("--no-hbac-allow", dest="no_hbac_allow", action="store_true", help="Don't install allow_all HBAC rule") parser.add_argument("--no-pkinit", dest="no_pkinit", action="store_true", help="disables pkinit setup steps") parser.add_argument("--no-ui-redirect", dest="no_ui_redirect", action="store_true", help="Do not automatically redirect to the Web UI") parser.add_argument("--dirsrv-config-file", dest="dirsrv_config_file", metavar="FILE", default=None, help="The path to LDIF file that will be used to " "modify configuration of dse.ldif during " "installation of the directory server instance") # ssl certificate parser.add_argument("--dirsrv-cert-file", dest="dirsrv_cert_files", metavar="FILE", default=None, action="append", help="File containing the Directory Server SSL " "certificate and private key") parser.add_argument("--http-cert-file", dest="http_cert_files", metavar="FILE", default=None, action="append", help="File containing the Apache Server SSL " "certificate and private key") parser.add_argument("--pkinit-cert-file", dest="pkinit_cert_files", metavar="FILE", default=None, action="append", help="File containing the Kerberos KDC SSL " "certificate and Private key") parser.add_argument("--dirsrv-pin", dest="dirsrv_pin", metavar="PIN", default=None, help="The password to unlock the Directory Server " "private key") parser.add_argument("--http-pin", dest="http_pin", metavar="PIN", default=None, help="The password to unlock the Apache Server " "private key") parser.add_argument("--pkinit-pin", dest="pkinit_pin", metavar="PIN", default=None, help="The password to unlock the Kerberos KDC " "private key") parser.add_argument("--dirsrv-cert-name", dest="dirsrv_cert_name", metavar="NAME", default=None, help="Name of the Directory Server SSL certificate " "to install") parser.add_argument("--http-cert-name", dest="http_cert_name", metavar="NAME", default=None, help="Name of the Apache Server SSL certificate to " "install") parser.add_argument("--pkinit-cert-name", dest="pkinit_cert_name", metavar="NAME", default=None, help="Name of the Kerberos KDC SSL certificate to " "install") # client parser.add_argument("--mkhomedir", dest="mkhomedir", action="store_true", help="create home directories for users on their " "first login") parser.add_argument("--ntp-server", dest="ntp_servers", metavar="NTP_SERVER", action='append', default=None, help="ntp server to use. This option can be used " "multiple times") parser.add_argument("--ntp-pool", dest="ntp_pool", default=None, help="ntp server pool to use") parser.add_argument("-N", "--no-ntp", dest="no_ntp", action="store_true", help="do not configure ntp") parser.add_argument("--ssh-trust-dns", dest="ssh_trust_dns", action="store_true", help="configure OpenSSH client to trust DNS SSHFP " "records") parser.add_argument("--no-ssh", dest="no_ssh", action="store_true", help="do not configure OpenSSH client") parser.add_argument("--no-sshd", dest="no_sshd", action="store_true", help="do not configure OpenSSH server") parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp", action="store_true", help="do not automatically create DNS SSHFP records") # certificate system parser.add_argument("--external-ca", dest="external_ca", action="store_true", help="Generate a CSR for the IPA CA certificate to " "be signed by an external CA") parser.add_argument("--external-ca-type", dest="external_ca_type", choices=("generic", "ms-cs"), default=None, help="Type of the external CA") parser.add_argument("--external-ca-profile", dest="external_ca_profile", default=None, help="Specify the certificate profile/template to " "use at the external CA") parser.add_argument("--external-cert-file", dest="external_cert_files", metavar="FILE", default=None, action="append", help="File containing the IPA CA certificate and the " "external CA certificate chain") parser.add_argument("--subject-base", dest="subject_base", default=None, help="The certificate subject base (default " "O=). RDNs are in LDAP order (most " "specific RDN first).") parser.add_argument("--ca-subject", dest="ca_subject", default=None, help="The CA certificate subject DN (default " "CN=Certificate Authority,O=). RDNs are " "in LDAP order (most specific RDN first).") parser.add_argument("--ca-signing-algorithm", dest="ca_signing_algorithm", choices=("SHA1withRSA", "SHA256withRSA", "SHA512withRSA"), default=None, help="Signing algorithm of the IPA CA certificate") # dns parser.add_argument("--allow-zone-overlap", dest="allow_zone_overlap", action="store_true", help="Create DNS zone even if it already exists") parser.add_argument("--reverse-zone", dest="reverse_zones", metavar="REVERSE_ZONE", action="append", default=None, help="The reverse DNS zone to use. This option can " "be used multiple times") parser.add_argument("--no-reverse", dest="no_reverse", action="store_true", help="Do not create new reverse DNS zone") parser.add_argument("--auto-reverse", dest="auto_reverse", action="store_true", help="Create necessary reverse zones") parser.add_argument("--zonemgr", dest="zonemgr", default=None, help="DNS zone manager e-mail address. Defaults to " "hostmaster@DOMAIN") parser.add_argument("--forwarder", dest="forwarders", action="append", default=None, help="Add a DNS forwarder. This option can be used " "multiple times") parser.add_argument("--no-forwarders", dest="no_forwarders", action="store_true", help="Do not add any DNS forwarders, use root " "servers instead") parser.add_argument("--auto-forwarders", dest="auto_forwarders", action="store_true", help="Use DNS forwarders configured in " "/etc/resolv.conf") parser.add_argument("-forward-policy-", dest="forward_policy", choices=("only", "first"), default=None, help="DNS forwarding policy for global forwarders") parser.add_argument("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true", help="Disable DNSSEC validation") # ad trust parser.add_argument("--enable-compat", dest="enable_compat", action="store_true", help="Enable support for trusted domains for old " "clients") parser.add_argument("--netbios-name", dest="netbios_name", default=None, help="NetBIOS name of the IPA domain") parser.add_argument("--rid-base", dest="rid_base", default=None, type=int, help="Start value for mapping UIDs and GIDs to RIDs") parser.add_argument("--secondary-rid-base", dest="secondary_rid_base", default=None, type=int, help="Start value of the secondary range for mapping " "UIDs and GIDs to RIDs") # deprecated parser.add_argument("--domain-level", type=int, help="IPA domain level (deprecated)") # uninstall parser.add_argument("--ignore-topology-disconnect", dest="ignore_topology_disconnect", action="store_true", help="do not check whether server uninstall " "disconnects the topology (domain level 1+)") parser.add_argument("--ignore-last-of-role", dest="ignore_last_of_role", action="store_true", help="do not check whether server uninstall removes " "last CA/DNS server or DNSSec master (domain level " "1+)") # logging and output parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="print debugging information") parser.add_argument("-d", "--debug", dest="verbose", action="store_true", help="alias for --verbose (deprecated)") parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="output only errors") parser.add_argument("--log-file", dest="log_file", help="log to the given file") # ansible parser.add_argument("--ipaserver-install-packages", dest="ipaserver_install_packages", choices=("yes", "no"), default=None, help="The bool value defines if the needed packages " "are installed on the node. Default: yes") parser.add_argument("--ipaserver-setup-firewalld", dest="ipaserver_setup_firewalld", choices=("yes", "no"), default=None, help="The value defines if the needed services will " "automatically be openen in the firewall managed by " "firewalld. Default: yes") parser.add_argument("--ipaserver-external-cert-files-from-controller", dest="ipaserver_external_cert_files_from_controller", default=None, action="append", help="Files containing the IPA CA certificates and " "the external CA certificate chains on the " "controller that will be copied to the ipaserver " "host to /root folder.") parser.add_argument("--ipaserver-copy-csr-to-controller", dest="ipaserver_copy_csr_to_controller", choices=("yes", "no"), default=None, help="Copy the generated CSR from the ipaserver to " "the controller as -ipa.csr.") # playbook parser.add_argument("--playbook-dir", dest="playbook_dir", default=None, help="If defined will be used as to create inventory " "file and playbook in. The files will not be removed " "after the playbook processing ended.") parser.add_argument("--become-method", dest="become_method", default="sudo", help="privilege escalation method to use " "(default=sudo), use `ansible-doc -t become -l` to " "list valid choices.") parser.add_argument("--ansible-verbose", dest="ansible_verbose", type=int, default=None, help="privilege escalation method to use " "(default=sudo), use `ansible-doc -t become -l` to " "list valid choices.") options, args = parser.parse_known_args() if options.playbook_dir and not os.path.isdir(options.playbook_dir): parser.error("playbook dir does not exist") if options.log_file: parser.error("log_file is not supported") if len(args) < 1: parser.error("ansible host not set") elif len(args) > 1: parser.error("too many arguments: %s" % ",".join(args)) return options, args def run_cmd(args): """ Execute an external command. """ p_out = subprocess.PIPE p_err = subprocess.STDOUT try: p = subprocess.Popen(args, stdout=p_out, stderr=p_err, close_fds=True, bufsize=1, universal_newlines=True) while True: line = p.stdout.readline() if p.poll() is not None and line == "": break sys.stdout.write(line) except KeyboardInterrupt: p.wait() raise else: p.wait() return p.returncode def main(options, args): if options.playbook_dir: playbook_dir = options.playbook_dir else: temp_dir = tempfile.mkdtemp(prefix='ansible-ipa-server') playbook_dir = temp_dir inventory = os.path.join(playbook_dir, "ipaserver-inventory") playbook = os.path.join(playbook_dir, "ipaserver-playbook.yml") with open(inventory, 'w') as f: f.write("[ipaserver]\n") f.write("%s\n" % args[0]) f.write("\n") f.write("[ipaserver:vars]\n") # basic if options.dm_password: f.write("ipadm_password=%s\n" % options.dm_password) if options.admin_password: f.write("ipaadmin_password=%s\n" % options.admin_password) if options.ip_addresses: f.write("ipaserver_ip_addresses=%s\n" % ",".join(options.ip_addresses)) if options.domain: f.write("ipaserver_domain=%s\n" % options.domain) if options.realm: f.write("ipaserver_realm=%s\n" % options.realm) if options.hostname: f.write("ipaserver_hostname=%s\n" % options.hostname) if options.ca_cert_file: f.write("ipaserver_ca_cert_files=%s\n" % options.ca_cert_file) if options.pki_config_override: f.write("ipaserver_pki_config_override=yes\n") if options.no_host_dns: f.write("ipaserver_no_host_dns=yes\n") # server if options.setup_adtrust: f.write("ipaserver_setup_adtrust=yes\n") if options.setup_kra: f.write("ipaserver_setup_kra=yes\n") if options.setup_dns: f.write("ipaserver_setup_dns=yes\n") if options.idstart: f.write("ipaserver_idstart=%s\n" % options.idstart) if options.idmax: f.write("ipaserver_idmax=%s\n" % options.idmax) if options.no_hbac_allow: f.write("ipaserver_no_hbac_allow=yes\n") if options.no_pkinit: f.write("ipaserver_no_pkinit=yes\n") if options.no_ui_redirect: f.write("ipaserver_no_ui_redirect=yes\n") if options.dirsrv_config_file: f.write("ipaserver_dirsrv_config_file=%s\n" % options.dirsrv_config_file) # ssl certificate if options.dirsrv_cert_files: f.write("ipaserver_dirsrv_cert_files=%s\n" % ",".join(options.dirsrv_cert_files)) if options.http_cert_files: f.write("ipaserver_http_cert_files=%s\n" % ",".join(options.http_cert_files)) if options.pkinit_cert_files: f.write("ipaserver_pkinit_cert_files=%s\n" % ",".join(options.pkinit_cert_files)) if options.dirsrv_pin: f.write("ipaserver_dirsrv_pin=%s\n" % options.dirsrv_pin) if options.http_pin: f.write("ipaserver_http_pin=%s\n" % options.http_pin) if options.pkinit_pin: f.write("ipaserver_pkinit_pin=%s\n" % options.pkinit_pin) if options.dirsrv_cert_name: f.write("ipaserver_dirsrv_cert_name=%s\n" % options.dirsrv_cert_name) if options.http_cert_name: f.write("ipaserver_http_cert_name=%s\n" % options.http_cert_name) if options.pkinit_cert_name: f.write("ipaserver_pkinit_cert_name=%s\n" % options.pkinit_cert_name) # client if options.mkhomedir: f.write("ipaclient_mkhomedir=yes\n") if options.ntp_servers: f.write("ipaclient_ntp_servers=%s\n" % ",".join(options.ntp_servers)) if options.ntp_pool: f.write("ipaclient_ntp_pool=%s\n" % options.ntp_pool) if options.no_ntp: f.write("ipaclient_no_ntp=yes\n") if options.ssh_trust_dns: f.write("ipaclient_ssh_trust_dns=yes\n") if options.no_ssh: f.write("ipaclient_no_ssh=yes\n") if options.no_sshd: f.write("ipaclient_no_sshd=yes\n") if options.no_dns_sshfp: f.write("ipaclient_no_dns_sshfp=yes\n") # certificate system if options.external_ca: f.write("ipaserver_external_ca=yes\n") if options.external_ca_type: f.write("ipaserver_external_ca_type=%s\n" % options.external_ca_type) if options.external_ca_profile: f.write("ipaserver_external_ca_profile=%s\n" % options.external_ca_profile) if options.external_cert_files: f.write("ipaserver_external_cert_files=%s\n" % ",".join(options.external_cert_files)) if options.subject_base: f.write("ipaserver_subject_base=%s\n" % options.subject_base) if options.ca_subject: f.write("ipaserver_ca_subject=%s\n" % options.ca_subject) if options.ca_signing_algorithm: f.write("ipaserver_ca_signing_algorithm=%s\n" % options.ca_signing_algorithm) # dns if options.allow_zone_overlap: f.write("ipaserver_allow_zone_overlap=yes\n") if options.reverse_zones: f.write("ipaserver_reverse_zones=%s\n" % ",".join(options.reverse_zones)) if options.no_reverse: f.write("ipaserver_no_reverse=yes\n") if options.auto_reverse: f.write("ipaserver_auto_reverse=yes\n") if options.zonemgr: f.write("ipaserver_zonemgr=%s\n" % options.zonemgr) if options.forwarders: f.write("ipaserver_forwarders=%s\n" % ",".join(options.forwarders)) if options.no_forwarders: f.write("ipaserver_no_forwarders=yes\n") if options.auto_forwarders: f.write("ipaserver_auto_forwarders=yes\n") if options.forward_policy: f.write("ipaserver_forward_policy=%s\n" % options.forward_policy) if options.no_dnssec_validation: f.write("ipaserver_no_dnssec_validation=yes\n") # ad trust if options.enable_compat: f.write("ipaserver_enable_compat=yes\n") if options.netbios_name: f.write("ipaserver_netbios_name=%s\n" % options.netbios_name) if options.rid_base: f.write("ipaserver_rid_base=%s\n" % options.rid_base) if options.secondary_rid_base: f.write("ipaserver_secondary_rid_base=%s\n" % options.secondary_rid_base) # uninstall if options.ignore_topology_disconnect: f.write("ipaserver_ignore_topology_disconnect=yes\n") if options.ignore_last_of_role: f.write("ipaserver_ignore_last_of_role=yes\n") # ansible if options.ipaserver_install_packages: f.write("ipaserver_install_packages=%s\n" % options.ipaserver_install_packages) if options.ipaserver_setup_firewalld: f.write("ipaserver_setup_firewalld=%s\n" % options.ipaserver_setup_firewalld) if options.ipaserver_external_cert_files_from_controller: f.write("ipaserver_external_cert_files_from_controller=%s\n" % ",".join( options.ipaserver_external_cert_files_from_controller)) if options.ipaserver_copy_csr_to_controller: f.write("ipaserver_copy_csr_to_controller=%s\n" % options.ipaserver_copy_csr_to_controller) if options.uninstall: state = "absent" else: state = "present" with open(playbook, 'w') as f: f.write("---\n") f.write("- name: Playbook to configure IPA server\n") f.write(" hosts: ipaserver\n") f.write(" become: true\n") if options.become_method: f.write(" become_method: %s\n" % options.become_method) f.write("\n") f.write(" roles:\n") f.write(" - role: ipaserver\n") f.write(" state: %s\n" % state) cmd = [ 'ansible-playbook' ] if options.ansible_verbose: cmd.append("-"+"v"*options.ansible_verbose) cmd.extend(['-i', inventory, playbook]) try: returncode = run_cmd(cmd) if returncode != 0: raise RuntimeError() finally: if not options.playbook_dir: shutil.rmtree(temp_dir, ignore_errors=True) options, args = parse_options() try: main(options, args) except KeyboardInterrupt: sys.exit(1) except SystemExit as e: sys.exit(e) except RuntimeError as e: sys.exit(e) except Exception as e: if options.verbose: traceback.print_exc(file=sys.stdout) else: print("Re-run %s with --verbose option to get more information" % sys.argv[0]) print("Unexpected error: %s" % str(e)) sys.exit(1)