diff --git a/utils/ansible-ipa-client-install b/utils/ansible-ipa-client-install new file mode 100755 index 0000000000000000000000000000000000000000..f074e1662b19f74b2c30e08fc3fc17c5989d0e78 --- /dev/null +++ b/utils/ansible-ipa-client-install @@ -0,0 +1,377 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# 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 <http://www.gnu.org/licenses/>. + +import os +import sys +import shutil +import tempfile +import argparse +import traceback +import subprocess + + +def parse_options(): + usage = "Usage: ansible-ipa-client-install [options] <ansible host>" + + 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", "--principal", dest="principal", + default=None, + help="principal to use to join the IPA realm") + parser.add_argument("--ca-cert-file", dest="ca_cert_file", + default=None, + help="load the CA certificate from this file") + parser.add_argument("--ip-address", dest="ip_addresses", + metavar="IP_ADDRESS", + action='append', default=None, + help="Specify IP address that should be added to DNS. " + "This option can be used multiple times") + parser.add_argument("--all-ip-addresses", dest="all_ip_addresses", + action='store_true', + help="All routable IP addresses configured on any " + "interface will be added to DNS") + parser.add_argument("--domain", dest="domain", + default=None, + help="primary DNS domain of the IPA deployment (not " + "necessarily related to the current hostname)") + parser.add_argument("--server", dest="servers", + metavar="SERVER", + action='append', default=None, + help="FQDN of IPA server") + parser.add_argument("--realm", dest="realm", + default=None, + help="Kerberos realm name of the IPA deployment " + "(typically an upper-cased name of the primary DNS " + "domain)") + parser.add_argument("--hostname", dest="hostname", + default=None, + help="The hostname of this machine (FQDN). If " + "specified, the hostname will be set and the system " + "configuration will be updated to persist over " + "reboot. By default the result of getfqdn() call " + "from Python's socket module is used.") + # client + parser.add_argument("-w", "--password", dest="password", + default=None, + help="password to join the IPA realm (assumes bulk " + "password unless principal is also set)") + parser.add_argument("-W", dest="password_prompt", + action="store_true", + help="Prompt for a password to join the IPA realm") + parser.add_argument("-f", "--force", dest="force", + action="store_true", + help="force setting of LDAP/Kerberos conf") + parser.add_argument("--configure-firefox", dest="configure_firefox", + action="store_true", + help="configure Firefox to use IPA domain credentials") + parser.add_argument("--firefox-dir", dest="firefox_dir", + default=None, + help="specify directory where Firefox is installed " + "(for example: '/usr/lib/firefox')") + parser.add_argument("-k", "--keytab", dest="keytab", + default=None, + help="path to backed up keytab from previous " + "enrollment") + parser.add_argument("--mkhomedir", dest="mkhomedir", + action="store_true", + help="create home directories for users on their " + "first login") + parser.add_argument("--force-join", dest="force_join", + action="store_true", + help="Force client enrollment even if already " + "enrolled") + 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("--nisdomain", dest="nisdomain", + default=None, + help="NIS domain name") + parser.add_argument("--no-nisdomain", dest="no_nisdomain", + action="store_true", + help="do not configure NIS domain name") + 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-sudo", dest="no_sudo", + action="store_true", + help="do not configure SSSD as data source for sudo") + parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp", + action="store_true", + help="do not automatically create DNS SSHFP records") + parser.add_argument("--kinit-attempts", dest="kinit_attempts", + type=int, default=None, + help="number of attempts to obtain host TGT (defaults " + "to 5)") + # sssd + parser.add_argument("--fixed-primary", dest="fixed_primary", + action="store_true", + help="Configure sssd to use fixed server as primary " + "IPA server") + parser.add_argument("--permit", dest="permit", + action="store_true", + help="disable access rules by default, permit all " + "access") + parser.add_argument("--enable-dns-updates", dest="enable_dns_updates", + action="store_true", + help="Configures the machine to attempt dns updates " + "when the ip address changes") + parser.add_argument("--no-krb5-offline-passwords", + dest="no_krb5_offline_passwords", + action="store_true", + help="Configure SSSD not to store user password when " + "the server is offline") + parser.add_argument("--preserve-sssd", dest="preserve_sssd", + action="store_true", + help="Preserve old SSSD configuration if possible") + + # automount + parser.add_argument("--automount-location", dest="automount_location", + default=None, + help="Automount location") + # 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("--ipaclient-use-otp", dest="ipaclient_use_otp", + choices=("yes", "no"), default=None, + help="The bool value defines if a one-time password " + "will be generated to join a new or existing host. " + "Default: no") + parser.add_argument("--ipaclient-allow-repair", + dest="ipaclient_allow_repair", + choices=("yes", "no"), default=None, + help="The bool value defines if an already joined or " + "partly set-up client can be repaired. Default: no") + 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") + + options, args = parser.parse_known_args() + + if options.password_prompt: + parser.error("password prompt is not possible with ansible") + 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") + + 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) + with p.stdout: + for line in p.stdout: + sys.stdout.write(line) + except KeyboardInterrupt: + p.wait() + raise + + return p.returncode + + +def main(options, args): + temp_dir = tempfile.mkdtemp(prefix='ansible-ipa') + + inventory = os.path.join(temp_dir, "client-inventory") + playbook = os.path.join(temp_dir, "client-playbook.yml") + + with open(inventory, 'w') as f: + if options.servers: + f.write("[ipaservers]\n") + for server in options.servers: + f.write("%s\n" % server) + f.write("\n") + f.write("[ipaclients]\n") + f.write("%s\n" % args[0]) + f.write("\n") + f.write("[ipaclients:vars]\n") + # basic + if options.principal: + f.write("ipaadmin_principal=%s\n" % options.principal) + if options.ca_cert_file: + f.write("ipaclient_ca_cert_file=%s\n" % options.ca_cert_file) + if options.ip_addresses: + f.write("ipaclient_ip_addresses=%s\n" % + ",".join(options.ip_addresses)) + if options.all_ip_addresses: + f.write("ipaclient_all_ip_addresses=yes\n") + if options.domain: + f.write("ipaclient_domain=%s\n" % options.domain) + # --servers are handled above + if options.realm: + f.write("ipaclient_realm=%s\n" % options.realm) + if options.hostname: + f.write("ipaclient_hostname=%s\n" % options.hostname) + # client + if options.password: + f.write("ipaadmin_password=%s\n" % options.password) + if options.force: + f.write("ipaclient_force=yes\n") + if options.configure_firefox: + f.write("ipaclient_configure_firefox=yes\n") + if options.firefox_dir: + f.write("ipaclient_firefox_dir=%s\n" % options.firefox_dir) + if options.keytab: + f.write("ipaclient_keytab=%s\n" % options.keytab) + if options.mkhomedir: + f.write("ipaclient_mkhomedir=yes\n") + if options.force_join: + f.write("ipaclient_force_join=%s\n" % options.force_join) + 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.nisdomain: + f.write("ipaclient_nisdomain=%s\n" % options.nisdomain) + if options.no_nisdomain: + f.write("ipaclient_no_nisdomain=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_sudo: + f.write("ipaclient_no_sudo=yes\n") + if options.no_dns_sshfp: + f.write("ipaclient_no_dns_sshfp=yes\n") + if options.kinit_attempts: + f.write("ipaclient_kinit_attempts=%d\n" % options.kinit_attempts) + # sssd + if options.fixed_primary: + f.write("ipassd_fixed_primary=yes\n") + if options.permit: + f.write("ipassd_permit=yes\n") + if options.enable_dns_updates: + f.write("ipassd_enable_dns_updates=yes\n") + if options.no_krb5_offline_passwords: + f.write("ipassd_no_krb5_offline_passwords=yes\n") + if options.preserve_sssd: + f.write("ipassd_preserve_sssd=yes\n") + # automount + if options.automount_location: + f.write("ipaclient_automount_location=%s\n" % + options.automount_location) + # ansible + if options.ipaclient_use_otp: + f.write("ipaclient_use_otp=%s\n" % options.ipaclient_use_otp) + if options.ipaclient_allow_repair: + f.write("ipaclient_allow_repair=%s\n" % + options.ipaclient_allow_repair) + if options.ipaclient_install_packages: + f.write("ipaclient_install-packages=%s\n" % + options.ipaclient_install_packages) + + if options.uninstall: + state = "absent" + else: + state = "present" + + with open(playbook, 'w') as f: + f.write("""--- +- name: Playbook to configure IPA clients + hosts: ipaclients + become: true + + roles: + - role: ipaclient + state: %s +""" % state) + + try: + returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook]) + if returncode != 0: + raise RuntimeError() + finally: + 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) diff --git a/utils/ansible-ipa-replica-install b/utils/ansible-ipa-replica-install new file mode 100755 index 0000000000000000000000000000000000000000..5c8eab4999a5fbab949b257e267ff6d077fc9449 --- /dev/null +++ b/utils/ansible-ipa-replica-install @@ -0,0 +1,493 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# 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 <http://www.gnu.org/licenses/>. + +import os +import sys +import shutil +import tempfile +import argparse +import traceback +import subprocess + + +def parse_options(): + usage = "Usage: anisble-ipa-replica-install [options] <ansible host>" + + 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") + # basic + parser.add_argument("-w", "--admin-password", dest="admin_password", + default=None, + help="Kerberos password for the specified admin " + "principal") + parser.add_argument("--ip-address", dest="ip_addresses", + metavar="IP_ADDRESS", + action='append', default=None, + help="Replica 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("--server", dest="servers", + metavar="SERVER", + action='append', default=None, + help="fully qualified name of IPA server to enroll to") + 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("-P", "--principal", dest="principal", + default=None, + help="User Principal allowed to promote replicas and " + "join IPA realm") + 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") + parser.add_argument("--skip-conncheck", dest="skip_conncheck", + action="store_true", + help="skip connection check to remote master") + # server + parser.add_argument("-p", "--password", dest="dm_password", + default=None, + help="Password to join the IPA realm. Assumes bulk " + "password unless principal is also set. (domain " + "level 1+) Directory Manager (existing master) " + "password. (domain level 0)") + parser.add_argument("--hidden-replica", dest="hidden_replica", + action="store_true", + help="Install a hidden replica") + parser.add_argument("--setup-adtrust", dest="setup_adtrust", + action="store_true", + help="configure AD trust capability") + parser.add_argument("--setup-ca", dest="setup_ca", + action="store_true", + help="configure a dogtag CA") + 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("--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("-k", "--keytab", dest="keytab", + default=None, + help="path to backed up keytab from previous " + "enrollment") + parser.add_argument("--mkhomedir", dest="mkhomedir", + action="store_true", + help="create home directories for users on their " + "first login") + parser.add_argument("--force-join", dest="force_join", + action="store_true", + help="Force client enrollment even if already " + "enrolled") + 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("--skip-schema-check", dest="skip_schema_check", + action="store_true", + help="skip check for updated CA DS schema on the " + "remote master") + # 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("--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("--add-sids", dest="add_sids", + action="store_true", + help="Add SIDs for existing users and groups as the " + "final step") + parser.add_argument("--add-agents", dest="add_agents", + action="store_true", + help="Add IPA masters to a list of hosts allowed to " + "serve information about users from trusted forests") + 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") + # 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("--ipareplica-install-packages", + dest="ipareplica_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("--ipareplica-setup-firewalld", + dest="ipareplica_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") + + options, args = parser.parse_known_args() + + 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") + + 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) + with p.stdout: + for line in p.stdout: + sys.stdout.write(line) + except KeyboardInterrupt: + p.wait() + raise + + return p.returncode + + +def main(options, args): + temp_dir = tempfile.mkdtemp(prefix='ansible-ipa') + + inventory = os.path.join(temp_dir, "replica-inventory") + playbook = os.path.join(temp_dir, "replica-playbook.yml") + + with open(inventory, 'w') as f: + if options.servers: + f.write("[ipaservers]\n") + for server in options.servers: + f.write("%s\n" % server) + f.write("\n") + f.write("[ipareplica]\n") + f.write("%s\n" % args[0]) + f.write("\n") + f.write("[ipareplica:vars]\n") + # basic + if options.admin_password: + f.write("ipaadmin_password=%s\n" % options.admin_password) + if options.ip_addresses: + f.write("ipareplica_ip_addresses=%s\n" % + ",".join(options.ip_addresses)) + if options.domain: + f.write("ipareplica_domain=%s\n" % options.domain) + # --servers are handled above + if options.realm: + f.write("ipareplica_realm=%s\n" % options.realm) + if options.hostname: + f.write("ipareplica_hostname=%s\n" % options.hostname) + if options.principal: + f.write("ipaadmin_principal=%s\n" % options.principal) + if options.pki_config_override: + f.write("ipareplica_pki_config_override=yes\n") + if options.no_host_dns: + f.write("ipareplica_no_host_dns=yes\n") + if options.skip_conncheck: + f.write("ipareplica_skip_conncheck=yes\n") + # server + if options.dm_password: + f.write("ipadm_password=%s\n" % options.dm_password) + if options.hidden_replica: + f.write("ipareplica_hidden_replica=yes\n") + if options.setup_adtrust: + f.write("ipareplica_setup_adtrust=yes\n") + if options.setup_ca: + f.write("ipareplica_setup_ca=yes\n") + if options.setup_kra: + f.write("ipareplica_setup_kra=yes\n") + if options.setup_dns: + f.write("ipareplica_setup_dns=yes\n") + if options.no_pkinit: + f.write("ipareplica_no_pkinit=yes\n") + if options.no_ui_redirect: + f.write("ipareplica_no_ui_redirect=yes\n") + # ssl certificate + if options.dirsrv_cert_files: + f.write("ipareplica_dirsrv_cert_files=%s\n" % + ",".join(options.dirsrv_cert_files)) + if options.http_cert_files: + f.write("ipareplica_http_cert_files=%s\n" % + ",".join(options.http_cert_files)) + if options.pkinit_cert_files: + f.write("ipareplica_pkinit_cert_files=%s\n" % + ",".join(options.pkinit_cert_files)) + if options.dirsrv_pin: + f.write("ipareplica_dirsrv_pin=%s\n" % options.dirsrv_pin) + if options.http_pin: + f.write("ipareplica_http_pin=%s\n" % options.http_pin) + if options.pkinit_pin: + f.write("ipareplica_pkinit_pin=%s\n" % options.pkinit_pin) + if options.dirsrv_cert_name: + f.write("ipareplica_dirsrv_cert_name=%s\n" % + options.dirsrv_cert_name) + if options.http_cert_name: + f.write("ipareplica_http_cert_name=%s\n" % options.http_cert_name) + if options.pkinit_cert_name: + f.write("ipareplica_pkinit_cert_name=%s\n" % + options.pkinit_cert_name) + # client + if options.keytab: + f.write("ipaclient_keytab=%s\n" % options.keytab) + if options.mkhomedir: + f.write("ipaclient_mkhomedir=yes\n") + if options.force_join: + f.write("ipaclient_force_join=yes\n") + if options.ntp_servers: + f.write("ipaclient_ntp_server=%s\n" % + ",".join(options.ntp_replicas)) + 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.skip_schema_check: + f.write("ipareplica_skip_schema_check=yes\n") + # dns + if options.allow_zone_overlap: + f.write("ipareplica_allow_zone_overlap=yes\n") + if options.reverse_zones: + f.write("ipareplica_reverse_zones=%s\n" % + ",".join(options.reverse_zones)) + if options.no_reverse: + f.write("ipareplica_no_reverse=yes\n") + if options.auto_reverse: + f.write("ipareplica_auto_reverse=yes\n") + if options.forwarders: + f.write("ipareplica_forwarders=%s\n" % + ",".join(options.forwarders)) + if options.no_forwarders: + f.write("ipareplica_no_forwarders=yes\n") + if options.auto_forwarders: + f.write("ipareplica_auto_forwarders=yes\n") + if options.forward_policy: + f.write("ipareplica_forward_policy=%s\n" % options.forward_policy) + if options.no_dnssec_validation: + f.write("ipareplica_no_dnssec_validation=yes\n") + # ad trust + if options.add_sids: + f.write("ipareplica_add_sids=yes\n") + if options.add_agents: + f.write("ipareplica_add_agents=yes\n") + if options.enable_compat: + f.write("ipareplica_enable_compat=yes\n") + if options.netbios_name: + f.write("ipareplica_netbios_name=%s\n" % options.netbios_name) + if options.rid_base: + f.write("ipareplica_rid_base=%s\n" % options.rid_base) + if options.secondary_rid_base: + f.write("ipareplica_secondary_rid_base=%s\n" % + options.secondary_rid_base) + # ansible + if options.ipareplica_install_packages: + f.write("ipareplica_install-packages=%s\n" % + options.ipareplica_install_packages) + if options.ipareplica_setup_firewalld: + f.write("ipareplica_setup_firewalld=%s\n" % + options.ipareplica_setup_firewalld) + + # uninstall done with ipaserver role + state = "present" + + with open(playbook, 'w') as f: + f.write("""--- +- name: Playbook to configure IPA replica + hosts: ipareplica + become: true + + roles: + - role: ipareplica + state: %s +""" % state) + + try: + returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook]) + if returncode != 0: + raise RuntimeError() + finally: + 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) diff --git a/utils/ansible-ipa-server-install b/utils/ansible-ipa-server-install new file mode 100755 index 0000000000000000000000000000000000000000..299adebd11b3134fb16aa2db361b4ed415d5a4e4 --- /dev/null +++ b/utils/ansible-ipa-server-install @@ -0,0 +1,550 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# 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 <http://www.gnu.org/licenses/>. + +import os +import sys +import shutil +import tempfile +import argparse +import traceback +import subprocess + + +def parse_options(): + usage = "Usage: anisble-ipa-server-install [options] <ansible host>" + + 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=<realm-name>). 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=<realm-name>). 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") + # 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 <hostname>-ipa.csr.") + + options, args = parser.parse_known_args() + + 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") + + 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) + with p.stdout: + for line in p.stdout: + sys.stdout.write(line) + except KeyboardInterrupt: + p.wait() + raise + + return p.returncode + + +def main(options, args): + temp_dir = tempfile.mkdtemp(prefix='ansible-ipa') + + inventory = os.path.join(temp_dir, "server-inventory") + playbook = os.path.join(temp_dir, "server-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("""--- +- name: Playbook to configure IPA server + hosts: ipaserver + become: true + + roles: + - role: ipaserver + state: %s +""" % state) + + try: + returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook]) + if returncode != 0: + raise RuntimeError() + finally: + 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)