Blame utils/ansible-ipa-client-install

Packit 8cb997
#!/usr/bin/python
Packit 8cb997
# -*- coding: utf-8 -*-
Packit 8cb997
Packit 8cb997
# Authors:
Packit 8cb997
#   Thomas Woerner <twoerner@redhat.com>
Packit 8cb997
#
Packit 8cb997
# Copyright (C) 2019  Red Hat
Packit 8cb997
# see file 'COPYING' for use and warranty information
Packit 8cb997
#
Packit 8cb997
# This program is free software; you can redistribute it and/or modify
Packit 8cb997
# it under the terms of the GNU General Public License as published by
Packit 8cb997
# the Free Software Foundation, either version 3 of the License, or
Packit 8cb997
# (at your option) any later version.
Packit 8cb997
#
Packit 8cb997
# This program is distributed in the hope that it will be useful,
Packit 8cb997
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8cb997
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8cb997
# GNU General Public License for more details.
Packit 8cb997
#
Packit 8cb997
# You should have received a copy of the GNU General Public License
Packit 8cb997
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8cb997
Packit 8cb997
import os
Packit 8cb997
import sys
Packit 8cb997
import shutil
Packit 8cb997
import tempfile
Packit 8cb997
import argparse
Packit 8cb997
import traceback
Packit 8cb997
import subprocess
Packit 8cb997
Packit 8cb997
Packit 8cb997
def parse_options():
Packit 8cb997
    usage = "Usage: ansible-ipa-client-install [options] <ansible host>"
Packit 8cb997
Packit 8cb997
    parser = argparse.ArgumentParser(usage=usage)
Packit 8cb997
    parser.add_argument("--version", dest="version",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="show program's version number and exit")
Packit 8cb997
    parser.add_argument("-U", "--unattended", dest="unattended",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="unattended (un)installation never prompts the "
Packit 8cb997
                        "user")
Packit 8cb997
    parser.add_argument("--uninstall", dest="uninstall",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="uninstall an existing installation. The "
Packit 8cb997
                        "uninstall can be run with --unattended option")
Packit 8cb997
    # basic
Packit 8cb997
    parser.add_argument("-p", "--principal", dest="principal",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="principal to use to join the IPA realm")
Packit 8cb997
    parser.add_argument("--ca-cert-file", dest="ca_cert_file",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="load the CA certificate from this file")
Packit 8cb997
    parser.add_argument("--ip-address", dest="ip_addresses",
Packit 8cb997
                        metavar="IP_ADDRESS",
Packit 8cb997
                        action='append', default=None,
Packit 8cb997
                        help="Specify IP address that should be added to DNS. "
Packit 8cb997
                        "This option can be used multiple times")
Packit 8cb997
    parser.add_argument("--all-ip-addresses", dest="all_ip_addresses",
Packit 8cb997
                        action='store_true',
Packit 8cb997
                        help="All routable IP addresses configured on any "
Packit 8cb997
                        "interface will be added to DNS")
Packit 8cb997
    parser.add_argument("--domain", dest="domain",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="primary DNS domain of the IPA deployment (not "
Packit 8cb997
                        "necessarily related to the current hostname)")
Packit 8cb997
    parser.add_argument("--server", dest="servers",
Packit 8cb997
                        metavar="SERVER",
Packit 8cb997
                        action='append', default=None,
Packit 8cb997
                        help="FQDN of IPA server")
Packit 8cb997
    parser.add_argument("--realm", dest="realm",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="Kerberos realm name of the IPA deployment "
Packit 8cb997
                        "(typically an upper-cased name of the primary DNS "
Packit 8cb997
                        "domain)")
Packit 8cb997
    parser.add_argument("--hostname", dest="hostname",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="The hostname of this machine (FQDN). If "
Packit 8cb997
                        "specified, the hostname will be set and the system "
Packit 8cb997
                        "configuration will be updated to persist over "
Packit 8cb997
                        "reboot. By default the result of getfqdn() call "
Packit 8cb997
                        "from Python's socket module is used.")
Packit 8cb997
    # client
Packit 8cb997
    parser.add_argument("-w", "--password", dest="password",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="password to join the IPA realm (assumes bulk "
Packit 8cb997
                        "password unless principal is also set)")
Packit 8cb997
    parser.add_argument("-W", dest="password_prompt",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Prompt for a password to join the IPA realm")
Packit 8cb997
    parser.add_argument("-f", "--force", dest="force",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="force setting of LDAP/Kerberos conf")
Packit 8cb997
    parser.add_argument("--configure-firefox", dest="configure_firefox",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="configure Firefox to use IPA domain credentials")
Packit 8cb997
    parser.add_argument("--firefox-dir", dest="firefox_dir",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="specify directory where Firefox is installed "
Packit 8cb997
                        "(for example: '/usr/lib/firefox')")
Packit 8cb997
    parser.add_argument("-k", "--keytab", dest="keytab",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="path to backed up keytab from previous "
Packit 8cb997
                        "enrollment")
Packit 8cb997
    parser.add_argument("--mkhomedir", dest="mkhomedir",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="create home directories for users on their "
Packit 8cb997
                        "first login")
Packit 8cb997
    parser.add_argument("--force-join", dest="force_join",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Force client enrollment even if already "
Packit 8cb997
                        "enrolled")
Packit 8cb997
    parser.add_argument("--ntp-server", dest="ntp_servers",
Packit 8cb997
                        metavar="NTP_SERVER",
Packit 8cb997
                        action='append', default=None,
Packit 8cb997
                        help="ntp server to use. This option can be used "
Packit 8cb997
                        "multiple times")
Packit 8cb997
    parser.add_argument("--ntp-pool", dest="ntp_pool",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="ntp server pool to use")
Packit 8cb997
    parser.add_argument("-N", "--no-ntp", dest="no_ntp",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not configure ntp")
Packit 8cb997
    parser.add_argument("--nisdomain", dest="nisdomain",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="NIS domain name")
Packit 8cb997
    parser.add_argument("--no-nisdomain", dest="no_nisdomain",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not configure NIS domain name")
Packit 8cb997
    parser.add_argument("--ssh-trust-dns", dest="ssh_trust_dns",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="configure OpenSSH client to trust DNS SSHFP "
Packit 8cb997
                        "records")
Packit 8cb997
    parser.add_argument("--no-ssh", dest="no_ssh",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not configure OpenSSH client")
Packit 8cb997
    parser.add_argument("--no-sshd", dest="no_sshd",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not configure OpenSSH server")
Packit 8cb997
    parser.add_argument("--no-sudo", dest="no_sudo",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not configure SSSD as data source for sudo")
Packit 8cb997
    parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="do not automatically create DNS SSHFP records")
Packit 8cb997
    parser.add_argument("--kinit-attempts", dest="kinit_attempts",
Packit 8cb997
                        type=int, default=None,
Packit 8cb997
                        help="number of attempts to obtain host TGT (defaults "
Packit 8cb997
                        "to 5)")
Packit 8cb997
    # sssd
Packit 8cb997
    parser.add_argument("--fixed-primary", dest="fixed_primary",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Configure sssd to use fixed server as primary "
Packit 8cb997
                        "IPA server")
Packit 8cb997
    parser.add_argument("--permit", dest="permit",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="disable access rules by default, permit all "
Packit 8cb997
                        "access")
Packit 8cb997
    parser.add_argument("--enable-dns-updates", dest="enable_dns_updates",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Configures the machine to attempt dns updates "
Packit 8cb997
                        "when the ip address changes")
Packit 8cb997
    parser.add_argument("--no-krb5-offline-passwords",
Packit 8cb997
                        dest="no_krb5_offline_passwords",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Configure SSSD not to store user password when "
Packit 8cb997
                        "the server is offline")
Packit 8cb997
    parser.add_argument("--preserve-sssd", dest="preserve_sssd",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="Preserve old SSSD configuration if possible")
Packit 8cb997
Packit 8cb997
    # automount
Packit 8cb997
    parser.add_argument("--automount-location", dest="automount_location",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="Automount location")
Packit 8cb997
    # logging and output
Packit 8cb997
    parser.add_argument("-v", "--verbose", dest="verbose",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="print debugging information")
Packit 8cb997
    parser.add_argument("-d", "--debug", dest="verbose",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="alias for --verbose (deprecated)")
Packit 8cb997
    parser.add_argument("-q", "--quiet", dest="quiet",
Packit 8cb997
                        action="store_true",
Packit 8cb997
                        help="output only errors")
Packit 8cb997
    parser.add_argument("--log-file", dest="log_file",
Packit 8cb997
                        help="log to the given file")
Packit 8cb997
    # ansible
Packit 8cb997
    parser.add_argument("--ipaclient-use-otp", dest="ipaclient_use_otp",
Packit 8cb997
                        choices=("yes", "no"), default=None,
Packit 8cb997
                        help="The bool value defines if a one-time password "
Packit 8cb997
                        "will be generated to join a new or existing host. "
Packit 8cb997
                        "Default: no")
Packit 8cb997
    parser.add_argument("--ipaclient-allow-repair",
Packit 8cb997
                        dest="ipaclient_allow_repair",
Packit 8cb997
                        choices=("yes", "no"), default=None,
Packit 8cb997
                        help="The bool value defines if an already joined or "
Packit 8cb997
                        "partly set-up client can be repaired. Default: no")
Packit 8cb997
    parser.add_argument("--ipaclient-install-packages",
Packit 8cb997
                        dest="ipaclient_install_packages",
Packit 8cb997
                        choices=("yes", "no"), default=None,
Packit 8cb997
                        help="The bool value defines if the needed packages "
Packit 8cb997
                        "are installed on the node. Default: yes")
Packit 8cb997
    # playbook
Packit 8cb997
    parser.add_argument("--playbook-dir",
Packit 8cb997
                        dest="playbook_dir",
Packit 8cb997
                        default=None,
Packit 8cb997
                        help="If defined will be used as to create inventory "
Packit 8cb997
                        "file and playbook in. The files will not be removed "
Packit 8cb997
                        "after the playbook processing ended.")
Packit 8cb997
    parser.add_argument("--become-method",
Packit 8cb997
                        dest="become_method",
Packit 8cb997
                        default="sudo",
Packit 8cb997
                        help="privilege escalation method to use "
Packit 8cb997
                        "(default=sudo), use `ansible-doc -t become -l` to "
Packit 8cb997
                        "list valid choices.")
Packit 8cb997
    parser.add_argument("--ansible-verbose",
Packit 8cb997
                        dest="ansible_verbose",
Packit 8cb997
                        type=int, default=None,
Packit 8cb997
                        help="privilege escalation method to use "
Packit 8cb997
                        "(default=sudo), use `ansible-doc -t become -l` to "
Packit 8cb997
                        "list valid choices.")
Packit 8cb997
Packit 8cb997
    options, args = parser.parse_known_args()
Packit 8cb997
Packit 8cb997
    if options.playbook_dir and not os.path.isdir(options.playbook_dir):
Packit 8cb997
        parser.error("playbook dir does not exist")
Packit 8cb997
Packit 8cb997
    if options.password_prompt:
Packit 8cb997
        parser.error("password prompt is not possible with ansible")
Packit 8cb997
    if options.log_file:
Packit 8cb997
        parser.error("log_file is not supported")
Packit 8cb997
Packit 8cb997
    if len(args) < 1:
Packit 8cb997
        parser.error("ansible host not set")
Packit 8cb997
    elif len(args) > 1:
Packit 8cb997
        parser.error("too many arguments: %s" % ",".join(args))
Packit 8cb997
Packit 8cb997
    return options, args
Packit 8cb997
Packit 8cb997
Packit 8cb997
def run_cmd(args):
Packit 8cb997
    """
Packit 8cb997
    Execute an external command.
Packit 8cb997
    """
Packit 8cb997
    p_out = subprocess.PIPE
Packit 8cb997
    p_err = subprocess.STDOUT
Packit 8cb997
    try:
Packit 8cb997
        p = subprocess.Popen(args, stdout=p_out, stderr=p_err,
Packit 8cb997
                             close_fds=True, bufsize=1,
Packit 8cb997
                             universal_newlines=True)
Packit 8cb997
        while True:
Packit 8cb997
            line = p.stdout.readline()
Packit 8cb997
            if p.poll() is not None and line == "":
Packit 8cb997
                break
Packit 8cb997
            sys.stdout.write(line)
Packit 8cb997
    except KeyboardInterrupt:
Packit 8cb997
        p.wait()
Packit 8cb997
        raise
Packit 8cb997
    else:
Packit 8cb997
        p.wait()
Packit 8cb997
        return p.returncode
Packit 8cb997
Packit 8cb997
Packit 8cb997
def main(options, args):
Packit 8cb997
    if options.playbook_dir:
Packit 8cb997
        playbook_dir = options.playbook_dir
Packit 8cb997
    else:
Packit 8cb997
        temp_dir = tempfile.mkdtemp(prefix='ansible-ipa-client')
Packit 8cb997
        playbook_dir = temp_dir
Packit 8cb997
Packit 8cb997
    inventory = os.path.join(playbook_dir, "ipaclient-inventory")
Packit 8cb997
    playbook = os.path.join(playbook_dir, "ipaclient-playbook.yml")
Packit 8cb997
Packit 8cb997
    with open(inventory, 'w') as f:
Packit 8cb997
        if options.servers:
Packit 8cb997
            f.write("[ipaservers]\n")
Packit 8cb997
            for server in options.servers:
Packit 8cb997
                f.write("%s\n" % server)
Packit 8cb997
            f.write("\n")
Packit 8cb997
        f.write("[ipaclients]\n")
Packit 8cb997
        f.write("%s\n" % args[0])
Packit 8cb997
        f.write("\n")
Packit 8cb997
        f.write("[ipaclients:vars]\n")
Packit 8cb997
        # basic
Packit 8cb997
        if options.principal:
Packit 8cb997
            f.write("ipaadmin_principal=%s\n" % options.principal)
Packit 8cb997
        if options.ca_cert_file:
Packit 8cb997
            f.write("ipaclient_ca_cert_file=%s\n" % options.ca_cert_file)
Packit 8cb997
        if options.ip_addresses:
Packit 8cb997
            f.write("ipaclient_ip_addresses=%s\n" %
Packit 8cb997
                    ",".join(options.ip_addresses))
Packit 8cb997
        if options.all_ip_addresses:
Packit 8cb997
            f.write("ipaclient_all_ip_addresses=yes\n")
Packit 8cb997
        if options.domain:
Packit 8cb997
            f.write("ipaclient_domain=%s\n" % options.domain)
Packit 8cb997
        # --servers are handled above
Packit 8cb997
        if options.realm:
Packit 8cb997
            f.write("ipaclient_realm=%s\n" % options.realm)
Packit 8cb997
        if options.hostname:
Packit 8cb997
            f.write("ipaclient_hostname=%s\n" % options.hostname)
Packit 8cb997
        # client
Packit 8cb997
        if options.password:
Packit 8cb997
            f.write("ipaadmin_password=%s\n" % options.password)
Packit 8cb997
        if options.force:
Packit 8cb997
            f.write("ipaclient_force=yes\n")
Packit 8cb997
        if options.configure_firefox:
Packit 8cb997
            f.write("ipaclient_configure_firefox=yes\n")
Packit 8cb997
        if options.firefox_dir:
Packit 8cb997
            f.write("ipaclient_firefox_dir=%s\n" % options.firefox_dir)
Packit 8cb997
        if options.keytab:
Packit 8cb997
            f.write("ipaclient_keytab=%s\n" % options.keytab)
Packit 8cb997
        if options.mkhomedir:
Packit 8cb997
            f.write("ipaclient_mkhomedir=yes\n")
Packit 8cb997
        if options.force_join:
Packit 8cb997
            f.write("ipaclient_force_join=%s\n" % options.force_join)
Packit 8cb997
        if options.ntp_servers:
Packit 8cb997
            f.write("ipaclient_ntp_servers=%s\n" %
Packit 8cb997
                    ",".join(options.ntp_servers))
Packit 8cb997
        if options.ntp_pool:
Packit 8cb997
            f.write("ipaclient_ntp_pool=%s\n" % options.ntp_pool)
Packit 8cb997
        if options.no_ntp:
Packit 8cb997
            f.write("ipaclient_no_ntp=yes\n")
Packit 8cb997
        if options.nisdomain:
Packit 8cb997
            f.write("ipaclient_nisdomain=%s\n" % options.nisdomain)
Packit 8cb997
        if options.no_nisdomain:
Packit 8cb997
            f.write("ipaclient_no_nisdomain=yes\n")
Packit 8cb997
        if options.ssh_trust_dns:
Packit 8cb997
            f.write("ipaclient_ssh_trust_dns=yes\n")
Packit 8cb997
        if options.no_ssh:
Packit 8cb997
            f.write("ipaclient_no_ssh=yes\n")
Packit 8cb997
        if options.no_sshd:
Packit 8cb997
            f.write("ipaclient_no_sshd=yes\n")
Packit 8cb997
        if options.no_sudo:
Packit 8cb997
            f.write("ipaclient_no_sudo=yes\n")
Packit 8cb997
        if options.no_dns_sshfp:
Packit 8cb997
            f.write("ipaclient_no_dns_sshfp=yes\n")
Packit 8cb997
        if options.kinit_attempts:
Packit 8cb997
            f.write("ipaclient_kinit_attempts=%d\n" % options.kinit_attempts)
Packit 8cb997
        # sssd
Packit 8cb997
        if options.fixed_primary:
Packit 8cb997
            f.write("ipasssd_fixed_primary=yes\n")
Packit 8cb997
        if options.permit:
Packit 8cb997
            f.write("ipasssd_permit=yes\n")
Packit 8cb997
        if options.enable_dns_updates:
Packit 8cb997
            f.write("ipasssd_enable_dns_updates=yes\n")
Packit 8cb997
        if options.no_krb5_offline_passwords:
Packit 8cb997
            f.write("ipasssd_no_krb5_offline_passwords=yes\n")
Packit 8cb997
        if options.preserve_sssd:
Packit 8cb997
            f.write("ipasssd_preserve_sssd=yes\n")
Packit 8cb997
        # automount
Packit 8cb997
        if options.automount_location:
Packit 8cb997
            f.write("ipaclient_automount_location=%s\n" %
Packit 8cb997
                    options.automount_location)
Packit 8cb997
        # ansible
Packit 8cb997
        if options.ipaclient_use_otp:
Packit 8cb997
            f.write("ipaclient_use_otp=%s\n" % options.ipaclient_use_otp)
Packit 8cb997
        if options.ipaclient_allow_repair:
Packit 8cb997
            f.write("ipaclient_allow_repair=%s\n" %
Packit 8cb997
                    options.ipaclient_allow_repair)
Packit 8cb997
        if options.ipaclient_install_packages:
Packit 8cb997
            f.write("ipaclient_install_packages=%s\n" %
Packit 8cb997
                    options.ipaclient_install_packages)
Packit 8cb997
Packit 8cb997
    if options.uninstall:
Packit 8cb997
        state = "absent"
Packit 8cb997
    else:
Packit 8cb997
        state = "present"
Packit 8cb997
Packit 8cb997
    with open(playbook, 'w') as f:
Packit 8cb997
        f.write("---\n")
Packit 8cb997
        f.write("- name: Playbook to configure IPA clients\n")
Packit 8cb997
        f.write("  hosts: ipaclients\n")
Packit 8cb997
        f.write("  become: true\n")
Packit 8cb997
        if options.become_method:
Packit 8cb997
            f.write("  become_method: %s\n" % options.become_method)
Packit 8cb997
        f.write("\n")
Packit 8cb997
        f.write("  roles:\n")
Packit 8cb997
        f.write("  - role: ipaclient\n")
Packit 8cb997
        f.write("    state: %s\n" % state)
Packit 8cb997
Packit 8cb997
    cmd = [ 'ansible-playbook' ]
Packit 8cb997
    if options.ansible_verbose:
Packit 8cb997
        cmd.append("-"+"v"*options.ansible_verbose)
Packit 8cb997
    cmd.extend(['-i', inventory, playbook])
Packit 8cb997
    try:
Packit 8cb997
        returncode = run_cmd(cmd)
Packit 8cb997
        if returncode != 0:
Packit 8cb997
            raise RuntimeError()
Packit 8cb997
    finally:
Packit 8cb997
        if not options.playbook_dir:
Packit 8cb997
            shutil.rmtree(temp_dir, ignore_errors=True)
Packit 8cb997
Packit 8cb997
Packit 8cb997
options, args = parse_options()
Packit 8cb997
try:
Packit 8cb997
    main(options, args)
Packit 8cb997
except KeyboardInterrupt:
Packit 8cb997
    sys.exit(1)
Packit 8cb997
except SystemExit as e:
Packit 8cb997
    sys.exit(e)
Packit 8cb997
except RuntimeError as e:
Packit 8cb997
    sys.exit(e)
Packit 8cb997
except Exception as e:
Packit 8cb997
    if options.verbose:
Packit 8cb997
        traceback.print_exc(file=sys.stdout)
Packit 8cb997
    else:
Packit 8cb997
        print("Re-run %s with --verbose option to get more information" %
Packit 8cb997
              sys.argv[0])
Packit 8cb997
Packit 8cb997
    print("Unexpected error: %s" % str(e))
Packit 8cb997
    sys.exit(1)