# # Copyright (C) 2015 FreeIPA Contributors see COPYING for license # """ Server installer module """ from __future__ import print_function import collections import os.path import random from ipaclient.install import client from ipalib import constants from ipalib.install.service import (enroll_only, installs_master, installs_replica, master_install_only, prepares, prepare_only, replica_install_only) from ipapython import ipautil from ipapython.dnsutil import check_zone_overlap from ipapython.install import typing from ipapython.install.core import knob from ipapython.install.common import step from .install import validate_admin_password, validate_dm_password from .install import init as master_init from .install import install as master_install from .install import install_check as master_install_check from .install import uninstall, uninstall_check from .replicainstall import init as replica_init from .replicainstall import install as replica_install from .replicainstall import install_check as replica_install_check from .replicainstall import promote_check as replica_promote_check from .upgrade import upgrade_check, upgrade from .. import ca, conncheck, dns, kra class ServerInstallInterface(client.ClientInstallInterface, ca.CAInstallInterface, kra.KRAInstallInterface, dns.DNSInstallInterface, conncheck.ConnCheckInterface): """ Interface of server installers Knobs defined here will be available in: * ipa-server-install * ipa-replica-prepare * ipa-replica-install """ force_join = False kinit_attempts = 1 fixed_primary = True ntp_servers = None force_ntpd = False permit = False enable_dns_updates = False no_krb5_offline_passwords = False preserve_sssd = False domain_name = knob( bases=client.ClientInstallInterface.domain_name, # pylint: disable=no-member cli_names=(list(client.ClientInstallInterface.domain_name.cli_names) + ['-n']), ) servers = knob( bases=client.ClientInstallInterface.servers, description="fully qualified name of IPA server to enroll to", ) servers = enroll_only(servers) realm_name = knob( bases=client.ClientInstallInterface.realm_name, cli_names=(list(client.ClientInstallInterface.realm_name.cli_names) + ['-r']), ) host_name = knob( bases=client.ClientInstallInterface.host_name, description="fully qualified name of this host", ) ca_cert_files = knob( bases=client.ClientInstallInterface.ca_cert_files, description="File containing CA certificates for the service " "certificate files", cli_deprecated_names='--root-ca-file', ) ca_cert_files = prepare_only(ca_cert_files) dm_password = knob( bases=client.ClientInstallInterface.dm_password, description="Directory Manager password", ) ip_addresses = knob( bases=client.ClientInstallInterface.ip_addresses, description="Server IP Address. This option can be used multiple " "times", ) principal = knob( bases=client.ClientInstallInterface.principal, description="User Principal allowed to promote replicas and join IPA " "realm", cli_names=(list(client.ClientInstallInterface.principal.cli_names) + ['-P']), ) principal = replica_install_only(principal) master_password = knob( str, None, sensitive=True, deprecated=True, description="kerberos master password (normally autogenerated)", ) master_password = master_install_only(master_password) domain_level = knob( int, constants.MAX_DOMAIN_LEVEL, description="IPA domain level", deprecated=True, ) domain_level = master_install_only(domain_level) @domain_level.validator def domain_level(self, value): # Check that Domain Level is within the allowed range if value < constants.MIN_DOMAIN_LEVEL: raise ValueError( "Domain Level cannot be lower than {0}".format( constants.MIN_DOMAIN_LEVEL)) elif value > constants.MAX_DOMAIN_LEVEL: raise ValueError( "Domain Level cannot be higher than {0}".format( constants.MAX_DOMAIN_LEVEL)) setup_ca = knob( None, description="configure a dogtag CA", ) setup_ca = enroll_only(setup_ca) setup_kra = knob( None, description="configure a dogtag KRA", ) setup_kra = enroll_only(setup_kra) setup_dns = knob( None, description="configure bind with our zone", ) setup_dns = enroll_only(setup_dns) idstart = knob( int, random.randint(1, 10000) * 200000, description="The starting value for the IDs range (default random)", ) idstart = master_install_only(idstart) idmax = knob( int, description=("The max value for the IDs range (default: " "idstart+199999)"), ) idmax = master_install_only(idmax) @idmax.default_getter def idmax(self): return self.idstart + 200000 - 1 no_hbac_allow = knob( None, description="Don't install allow_all HBAC rule", cli_deprecated_names='--no_hbac_allow', ) no_hbac_allow = master_install_only(no_hbac_allow) ignore_topology_disconnect = knob( None, description="do not check whether server uninstall disconnects the " "topology (domain level 1+)", ) ignore_topology_disconnect = master_install_only(ignore_topology_disconnect) ignore_last_of_role = knob( None, description="do not check whether server uninstall removes last " "CA/DNS server or DNSSec master (domain level 1+)", ) ignore_last_of_role = master_install_only(ignore_last_of_role) no_pkinit = knob( None, description="disables pkinit setup steps", ) no_pkinit = prepare_only(no_pkinit) no_ui_redirect = knob( None, description="Do not automatically redirect to the Web UI", ) no_ui_redirect = enroll_only(no_ui_redirect) ssh_trust_dns = knob( None, description="configure OpenSSH client to trust DNS SSHFP records", ) ssh_trust_dns = enroll_only(ssh_trust_dns) no_ssh = knob( None, description="do not configure OpenSSH client", ) no_ssh = enroll_only(no_ssh) no_sshd = knob( None, description="do not configure OpenSSH server", ) no_sshd = enroll_only(no_sshd) no_dns_sshfp = knob( None, description="Do not automatically create DNS SSHFP records", ) no_dns_sshfp = enroll_only(no_dns_sshfp) dirsrv_config_file = knob( str, None, description="The path to LDIF file that will be used to modify " "configuration of dse.ldif during installation of the " "directory server instance", cli_metavar='FILE', ) dirsrv_config_file = enroll_only(dirsrv_config_file) @dirsrv_config_file.validator def dirsrv_config_file(self, value): if not os.path.exists(value): raise ValueError("File %s does not exist." % value) dirsrv_cert_files = knob( # pylint: disable=invalid-sequence-index typing.List[str], None, description=("File containing the Directory Server SSL certificate " "and private key"), cli_names='--dirsrv-cert-file', cli_deprecated_names='--dirsrv_pkcs12', cli_metavar='FILE', ) dirsrv_cert_files = prepare_only(dirsrv_cert_files) http_cert_files = knob( # pylint: disable=invalid-sequence-index typing.List[str], None, description=("File containing the Apache Server SSL certificate and " "private key"), cli_names='--http-cert-file', cli_deprecated_names='--http_pkcs12', cli_metavar='FILE', ) http_cert_files = prepare_only(http_cert_files) pkinit_cert_files = knob( # pylint: disable=invalid-sequence-index typing.List[str], None, description=("File containing the Kerberos KDC SSL certificate and " "private key"), cli_names='--pkinit-cert-file', cli_deprecated_names='--pkinit_pkcs12', cli_metavar='FILE', ) pkinit_cert_files = prepare_only(pkinit_cert_files) dirsrv_pin = knob( str, None, sensitive=True, description="The password to unlock the Directory Server private key", cli_deprecated_names='--dirsrv_pin', cli_metavar='PIN', ) dirsrv_pin = prepare_only(dirsrv_pin) http_pin = knob( str, None, sensitive=True, description="The password to unlock the Apache Server private key", cli_deprecated_names='--http_pin', cli_metavar='PIN', ) http_pin = prepare_only(http_pin) pkinit_pin = knob( str, None, sensitive=True, description="The password to unlock the Kerberos KDC private key", cli_deprecated_names='--pkinit_pin', cli_metavar='PIN', ) pkinit_pin = prepare_only(pkinit_pin) dirsrv_cert_name = knob( str, None, description="Name of the Directory Server SSL certificate to install", cli_metavar='NAME', ) dirsrv_cert_name = prepare_only(dirsrv_cert_name) http_cert_name = knob( str, None, description="Name of the Apache Server SSL certificate to install", cli_metavar='NAME', ) http_cert_name = prepare_only(http_cert_name) pkinit_cert_name = knob( str, None, description="Name of the Kerberos KDC SSL certificate to install", cli_metavar='NAME', ) pkinit_cert_name = prepare_only(pkinit_cert_name) def __init__(self, **kwargs): super(ServerInstallInterface, self).__init__(**kwargs) # If any of the key file options are selected, all are required. cert_file_req = (self.dirsrv_cert_files, self.http_cert_files) cert_file_opt = (self.pkinit_cert_files,) if any(cert_file_req + cert_file_opt) and not all(cert_file_req): raise RuntimeError( "--dirsrv-cert-file and --http-cert-file are required if any " "key file options are used.") if not self.interactive: if self.dirsrv_cert_files and self.dirsrv_pin is None: raise RuntimeError( "You must specify --dirsrv-pin with --dirsrv-cert-file") if self.http_cert_files and self.http_pin is None: raise RuntimeError( "You must specify --http-pin with --http-cert-file") if self.pkinit_cert_files and self.pkinit_pin is None: raise RuntimeError( "You must specify --pkinit-pin with --pkinit-cert-file") if not self.setup_dns: if self.forwarders: raise RuntimeError( "You cannot specify a --forwarder option without the " "--setup-dns option") if self.auto_forwarders: raise RuntimeError( "You cannot specify a --auto-forwarders option without " "the --setup-dns option") if self.no_forwarders: raise RuntimeError( "You cannot specify a --no-forwarders option without the " "--setup-dns option") if self.forward_policy: raise RuntimeError( "You cannot specify a --forward-policy option without the " "--setup-dns option") if self.reverse_zones: raise RuntimeError( "You cannot specify a --reverse-zone option without the " "--setup-dns option") if self.auto_reverse: raise RuntimeError( "You cannot specify a --auto-reverse option without the " "--setup-dns option") if self.no_reverse: raise RuntimeError( "You cannot specify a --no-reverse option without the " "--setup-dns option") if self.no_dnssec_validation: raise RuntimeError( "You cannot specify a --no-dnssec-validation option " "without the --setup-dns option") elif self.forwarders and self.no_forwarders: raise RuntimeError( "You cannot specify a --forwarder option together with " "--no-forwarders") elif self.auto_forwarders and self.no_forwarders: raise RuntimeError( "You cannot specify a --auto-forwarders option together with " "--no-forwarders") elif self.reverse_zones and self.no_reverse: raise RuntimeError( "You cannot specify a --reverse-zone option together with " "--no-reverse") elif self.auto_reverse and self.no_reverse: raise RuntimeError( "You cannot specify a --auto-reverse option together with " "--no-reverse") if not hasattr(self, 'replica_file'): if self.external_cert_files and self.dirsrv_cert_files: raise RuntimeError( "Service certificate file options cannot be used with the " "external CA options.") if self.external_ca_type and not self.external_ca: raise RuntimeError( "You cannot specify --external-ca-type without " "--external-ca") if self.uninstalling: if (self.realm_name or self.admin_password or self.master_password): raise RuntimeError( "In uninstall mode, -a, -r and -P options are not " "allowed") elif not self.interactive: if (not self.realm_name or not self.dm_password or not self.admin_password): raise RuntimeError( "In unattended mode you need to provide at least -r, " "-p and -a options") if self.setup_dns: if (not self.forwarders and not self.no_forwarders and not self.auto_forwarders): raise RuntimeError( "You must specify at least one of --forwarder, " "--auto-forwarders, or --no-forwarders options") any_ignore_option_true = any( [self.ignore_topology_disconnect, self.ignore_last_of_role]) if any_ignore_option_true and not self.uninstalling: raise RuntimeError( "'--ignore-topology-disconnect/--ignore-last-of-role' " "options can be used only during uninstallation") if self.idmax < self.idstart: raise RuntimeError( "idmax (%s) cannot be smaller than idstart (%s)" % (self.idmax, self.idstart)) else: cert_file_req = (self.dirsrv_cert_files, self.http_cert_files) cert_file_opt = (self.pkinit_cert_files,) if self.replica_file is None: # If any of the PKCS#12 options are selected, all are required. if any(cert_file_req + cert_file_opt) and not all(cert_file_req): raise RuntimeError( "--dirsrv-cert-file and --http-cert-file are required " "if any PKCS#12 options are used") if self.servers and not self.domain_name: raise RuntimeError( "The --server option cannot be used without providing " "domain via the --domain option") else: if not ipautil.file_exists(self.replica_file): raise RuntimeError( "Replica file %s does not exist" % self.replica_file) if any(cert_file_req + cert_file_opt): raise RuntimeError( "You cannot specify any of --dirsrv-cert-file, " "--http-cert-file, or --pkinit-cert-file together " "with replica file") CLIKnob = collections.namedtuple('CLIKnob', ('value', 'name')) conflicting_knobs = ( CLIKnob(self.realm_name, '--realm'), CLIKnob(self.domain_name, '--domain'), CLIKnob(self.host_name, '--hostname'), CLIKnob(self.servers, '--server'), CLIKnob(self.principal, '--principal'), ) if any([k.value is not None for k in conflicting_knobs]): conflicting_knob_names = [ knob.name for knob in conflicting_knobs if knob.value is not None ] raise RuntimeError( "You cannot specify '{0}' option(s) with replica file." .format(", ".join(conflicting_knob_names)) ) if self.setup_dns: if (not self.forwarders and not self.no_forwarders and not self.auto_forwarders): raise RuntimeError( "You must specify at least one of --forwarder, " "--auto-forwarders, or --no-forwarders options") # Automatically enable pkinit w/ dogtag self.no_pkinit = not self.setup_ca ServerMasterInstallInterface = installs_master(ServerInstallInterface) class ServerMasterInstall(ServerMasterInstallInterface): """ Server master installer """ servers = None no_wait_for_dns = True host_password = None keytab = None setup_ca = True setup_kra = False domain_name = knob( bases=ServerMasterInstallInterface.domain_name, ) @domain_name.validator def domain_name(self, value): if (self.setup_dns and not self.allow_zone_overlap): print("Checking DNS domain %s, please wait ..." % value) check_zone_overlap(value, False) dm_password = knob( bases=ServerMasterInstallInterface.dm_password, ) @dm_password.validator def dm_password(self, value): validate_dm_password(value) admin_password = knob( bases=ServerMasterInstallInterface.admin_password, description="admin user kerberos password", ) @admin_password.validator def admin_password(self, value): validate_admin_password(value) def __init__(self, **kwargs): super(ServerMasterInstall, self).__init__(**kwargs) master_init(self) @step() def main(self): master_install_check(self) yield master_install(self) @main.uninstaller def main(self): uninstall_check(self) yield uninstall(self) ServerReplicaInstallInterface = installs_replica(ServerInstallInterface) class ServerReplicaInstall(ServerReplicaInstallInterface): """ Server replica installer """ subject_base = None ca_subject = None admin_password = knob( bases=ServerReplicaInstallInterface.admin_password, description="Kerberos password for the specified admin principal", ) def __init__(self, **kwargs): super(ServerReplicaInstall, self).__init__(**kwargs) replica_init(self) @step() def main(self): if self.replica_file is None: replica_promote_check(self) else: replica_install_check(self) yield replica_install(self)