#! /usr/bin/python2 -E # Authors: Karl MacMillan # Simo Sorce # Rob Crittenden # # Copyright (C) 2007-2014 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 . # # requires the following packages: # fedora-ds-base # openldap-clients # nss-tools import sys import os import signal import random from optparse import OptionGroup, OptionValueError, SUPPRESS_HELP from ipaserver.install import bindinstance from ipaserver.install import installutils from ipaserver.install import server from ipaserver.install.server import ( validate_dm_password, validate_admin_password) from ipapython import version from ipalib import constants from ipapython.config import IPAOptionParser from ipalib.util import validate_domain_name from ipapython.ipa_log_manager import root_logger, standard_logging_setup from ipapython.dn import DN from ipaplatform.paths import paths VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c', 'serialnumber', 'l', 'title', 'sn', 'givenname', 'initials', 'generationqualifier', 'dc', 'mail', 'uid', 'postaladdress', 'postalcode', 'postofficebox', 'houseidentifier', 'e', 'street', 'pseudonym', 'incorporationlocality', 'incorporationstate', 'incorporationcountry', 'businesscategory'] def subject_callback(option, opt_str, value, parser): """ Make sure the certificate subject base is a valid DN """ v = unicode(value, 'utf-8') if any(ord(c) < 0x20 for c in v): raise OptionValueError("Subject base must not contain control characters") if '&' in v: raise OptionValueError("Subject base must not contain an ampersand (\"&\")") try: dn = DN(v) for rdn in dn: if rdn.attr.lower() not in VALID_SUBJECT_ATTRS: raise OptionValueError('%s=%s has invalid attribute: "%s"' % (opt_str, value, rdn.attr)) except ValueError, e: raise OptionValueError('%s=%s has invalid subject base format: %s' % (opt_str, value, e)) parser.values.subject = dn def parse_options(): # Guaranteed to give a random 200k range below the 2G mark (uint32_t limit) namespace = random.randint(1, 10000) * 200000 parser = IPAOptionParser(version=version.VERSION) basic_group = OptionGroup(parser, "basic options") basic_group.add_option("-r", "--realm", dest="realm_name", help="realm name") basic_group.add_option("-n", "--domain", dest="domain_name", help="domain name") basic_group.add_option("-p", "--ds-password", dest="dm_password", sensitive=True, help="Directory Manager password") basic_group.add_option("-P", "--master-password", dest="master_password", sensitive=True, help=SUPPRESS_HELP) basic_group.add_option("-a", "--admin-password", sensitive=True, dest="admin_password", help="admin user kerberos password") basic_group.add_option("--mkhomedir", dest="mkhomedir", action="store_true", default=False, help="create home directories for users " "on their first login") basic_group.add_option("--hostname", dest="host_name", help="fully qualified name of server") basic_group.add_option("--domain-level", dest="domainlevel", help="IPA domain level", default=constants.MAX_DOMAIN_LEVEL, type=int) basic_group.add_option("--ip-address", dest="ip_addresses", type="ip", ip_local=True, action="append", default=[], help="Master Server IP Address. This option can be used multiple times", metavar="IP_ADDRESS") basic_group.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", help="do not configure ntp", default=True) basic_group.add_option("--idstart", dest="idstart", default=namespace, type=int, help="The starting value for the IDs range (default random)") basic_group.add_option("--idmax", dest="idmax", default=0, type=int, help="The max value value for the IDs range (default: idstart+199999)") basic_group.add_option("--no_hbac_allow", dest="hbac_allow", default=False, action="store_true", help="Don't install allow_all HBAC rule") basic_group.add_option("--no-ui-redirect", dest="ui_redirect", action="store_false", default=True, help="Do not automatically redirect to the Web UI") basic_group.add_option("--ssh-trust-dns", dest="trust_sshfp", default=False, action="store_true", help="configure OpenSSH client to trust DNS SSHFP records") basic_group.add_option("--no-ssh", dest="conf_ssh", default=True, action="store_false", help="do not configure OpenSSH client") basic_group.add_option("--no-sshd", dest="conf_sshd", default=True, action="store_false", help="do not configure OpenSSH server") basic_group.add_option("-d", "--debug", dest="debug", action="store_true", default=False, help="print debugging information") basic_group.add_option("-U", "--unattended", dest="unattended", action="store_true", default=False, help="unattended (un)installation never prompts the user") parser.add_option_group(basic_group) cert_group = OptionGroup(parser, "certificate system options") cert_group.add_option("", "--external-ca", dest="external_ca", action="store_true", default=False, help="Generate a CSR for the IPA CA certificate to be signed by an external CA") cert_group.add_option("--external-ca-type", dest="external_ca_type", type="choice", choices=("generic", "ms-cs"), help="Type of the external CA") cert_group.add_option("--external-cert-file", dest="external_cert_files", action="append", metavar="FILE", help="File containing the IPA CA certificate and the external CA certificate chain") cert_group.add_option("--external_cert_file", dest="external_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--external_ca_file", dest="external_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--no-pkinit", dest="setup_pkinit", action="store_false", default=True, help="disables pkinit setup steps") cert_group.add_option("--dirsrv-cert-file", dest="dirsrv_cert_files", action="append", metavar="FILE", help="File containing the Directory Server SSL certificate and private key") cert_group.add_option("--dirsrv_pkcs12", dest="dirsrv_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--http-cert-file", dest="http_cert_files", action="append", metavar="FILE", help="File containing the Apache Server SSL certificate and private key") cert_group.add_option("--http_pkcs12", dest="http_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--pkinit-cert-file", dest="pkinit_cert_files", action="append", metavar="FILE", help="File containing the Kerberos KDC SSL certificate and private key") cert_group.add_option("--pkinit_pkcs12", dest="pkinit_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--dirsrv-pin", dest="dirsrv_pin", sensitive=True, metavar="PIN", help="The password to unlock the Directory Server private key") cert_group.add_option("--dirsrv_pin", dest="dirsrv_pin", sensitive=True, help=SUPPRESS_HELP) cert_group.add_option("--http-pin", dest="http_pin", sensitive=True, metavar="PIN", help="The password to unlock the Apache Server private key") cert_group.add_option("--http_pin", dest="http_pin", sensitive=True, help=SUPPRESS_HELP) cert_group.add_option("--pkinit-pin", dest="pkinit_pin", sensitive=True, metavar="PIN", help="The password to unlock the Kerberos KDC private key") cert_group.add_option("--pkinit_pin", dest="pkinit_pin", sensitive=True, help=SUPPRESS_HELP) cert_group.add_option("--dirsrv-cert-name", dest="dirsrv_cert_name", metavar="NAME", help="Name of the Directory Server SSL certificate to install") cert_group.add_option("--http-cert-name", dest="http_cert_name", metavar="NAME", help="Name of the Apache Server SSL certificate to install") cert_group.add_option("--pkinit-cert-name", dest="pkinit_cert_name", metavar="NAME", help="Name of the Kerberos KDC SSL certificate to install") cert_group.add_option("--ca-cert-file", dest="ca_cert_files", action="append", metavar="FILE", help="File containing CA certificates for the service certificate files") cert_group.add_option("--root-ca-file", dest="ca_cert_files", action="append", help=SUPPRESS_HELP) cert_group.add_option("--subject", action="callback", callback=subject_callback, type="string", help="The certificate subject base (default O=)") cert_group.add_option("--ca-signing-algorithm", dest="ca_signing_algorithm", type="choice", choices=('SHA1withRSA', 'SHA256withRSA', 'SHA512withRSA'), help="Signing algorithm of the IPA CA certificate") parser.add_option_group(cert_group) dns_group = OptionGroup(parser, "DNS options") dns_group.add_option("--setup-dns", dest="setup_dns", action="store_true", default=False, help="configure bind with our zone") dns_group.add_option("--forwarder", dest="forwarders", action="append", type="ip", help="Add a DNS forwarder. This option can be used multiple times") dns_group.add_option("--no-forwarders", dest="no_forwarders", action="store_true", default=False, help="Do not add any DNS forwarders, use root servers instead") dns_group.add_option("--reverse-zone", dest="reverse_zones", help="The reverse DNS zone to use. This option can be used multiple times", action="append", default=[], metavar="REVERSE_ZONE") dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true", default=False, help="Do not create reverse DNS zone") dns_group.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true", default=False, help="Disable DNSSEC validation") dns_group.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback, type="string", help="DNS zone manager e-mail address. Defaults to hostmaster@DOMAIN") dns_group.add_option("--no-host-dns", dest="no_host_dns", action="store_true", default=False, help="Do not use DNS for hostname lookup during installation") dns_group.add_option("--no-dns-sshfp", dest="create_sshfp", default=True, action="store_false", help="Do not automatically create DNS SSHFP records") parser.add_option_group(dns_group) uninstall_group = OptionGroup(parser, "uninstall options") uninstall_group.add_option("", "--uninstall", dest="uninstall", action="store_true", default=False, help="uninstall an existing installation. The uninstall can " \ "be run with --unattended option") parser.add_option_group(uninstall_group) options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) if options.dm_password is not None: try: validate_dm_password(options.dm_password) except ValueError, e: parser.error("DS admin password: " + str(e)) if options.admin_password is not None: try: validate_admin_password(options.admin_password) except ValueError, e: parser.error("Admin user password: " + str(e)) if options.domain_name is not None: try: validate_domain_name(options.domain_name) except ValueError, e: parser.error("invalid domain: " + unicode(e)) # Check that Domain Level is within the allowed range if not options.uninstall: if options.domainlevel < constants.MIN_DOMAIN_LEVEL: parser.error("Domain Level cannot be lower than {0}" .format(constants.MIN_DOMAIN_LEVEL)) elif options.domainlevel > constants.MAX_DOMAIN_LEVEL: parser.error("Domain Level cannot be higher than {0}" .format(constants.MAX_DOMAIN_LEVEL)) if not options.setup_dns: if options.forwarders: parser.error("You cannot specify a --forwarder option without the --setup-dns option") if options.no_forwarders: parser.error("You cannot specify a --no-forwarders option without the --setup-dns option") if options.reverse_zones: parser.error("You cannot specify a --reverse-zone option without the --setup-dns option") if options.no_reverse: parser.error("You cannot specify a --no-reverse option without the --setup-dns option") if options.no_dnssec_validation: parser.error("You cannot specify a --no-dnssec-validation option without the --setup-dns option") elif options.forwarders and options.no_forwarders: parser.error("You cannot specify a --forwarder option together with --no-forwarders") elif options.reverse_zones and options.no_reverse: parser.error("You cannot specify a --reverse-zone option together with --no-reverse") if options.uninstall: if (options.realm_name or options.admin_password or options.master_password): parser.error("In uninstall mode, -a, -r and -P options are not allowed") elif options.unattended: if (not options.realm_name or not options.dm_password or not options.admin_password): parser.error("In unattended mode you need to provide at least -r, -p and -a options") if options.setup_dns: if not options.forwarders and not options.no_forwarders: parser.error("You must specify at least one --forwarder option or --no-forwarders option") # If any of the key file options are selected, all are required. cert_file_req = (options.dirsrv_cert_files, options.http_cert_files) cert_file_opt = (options.pkinit_cert_files,) if any(cert_file_req + cert_file_opt) and not all(cert_file_req): parser.error("--dirsrv-cert-file and --http-cert-file are required if " "any key file options are used.") if options.unattended: if options.dirsrv_cert_files and options.dirsrv_pin is None: parser.error( "You must specify --dirsrv-pin with --dirsrv-cert-file") if options.http_cert_files and options.http_pin is None: parser.error( "You must specify --http-pin with --http-cert-file") if options.pkinit_cert_files and options.pkinit_pin is None: parser.error( "You must specify --pkinit-pin with --pkinit-cert-file") if options.external_cert_files and options.dirsrv_cert_files: parser.error("Service certificate file options cannot be used with " "the external CA options.") if options.external_ca: if options.external_cert_files: parser.error("You cannot specify --external-cert-file " "together with --external-ca") if options.dirsrv_cert_files: parser.error("You cannot specify service certificate file options " "together with --external-ca") if options.external_ca_type and not options.external_ca: parser.error( "You cannot specify --external-ca-type without --external-ca") if (options.external_cert_files and any(not os.path.isabs(path) for path in options.external_cert_files)): parser.error("--external-cert-file must use an absolute path") if options.idmax == 0: options.idmax = int(options.idstart) + 200000 - 1 if options.idmax < options.idstart: parser.error("idmax (%u) cannot be smaller than idstart (%u)" % (options.idmax, options.idstart)) #Automatically disable pkinit w/ dogtag until that is supported options.setup_pkinit = False options.dnssec_master = False return safe_options, options def signal_handler(signum, frame): raise KeyboardInterrupt def main(): safe_options, options = parse_options() if os.getegid() != 0: sys.exit("Must be root to set up server") signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) if not options.uninstall: standard_logging_setup(paths.IPASERVER_INSTALL_LOG, debug=options.debug) else: standard_logging_setup(paths.IPASERVER_UNINSTALL_LOG, debug=options.debug) root_logger.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options)) root_logger.debug("missing options might be asked for interactively later\n") root_logger.debug('IPA version %s' % version.VENDOR_VERSION) if not options.uninstall: server.install_check(options) server.install(options) else: server.uninstall_check(options) server.uninstall(options) if __name__ == '__main__': # FIXME: Common option parsing, logging setup, etc should be factored # out from all install scripts safe_options, options = parse_options() if options.uninstall: log_file_name = paths.IPASERVER_UNINSTALL_LOG else: log_file_name = paths.IPASERVER_INSTALL_LOG installutils.run_script(main, log_file_name=log_file_name, operation_name='ipa-server-install')