install: introduce installer class hierarchy

Add class hierarchy which allows inherting knob definitions between the
various client and server install scripts.

https://fedorahosted.org/freeipa/ticket/6392

Reviewed-By: Martin Basti <mbasti@redhat.com>
This commit is contained in:
Jan Cholasta 2016-11-09 12:44:22 +01:00
parent 08a446a6bc
commit a8fdb8de82
13 changed files with 1332 additions and 4 deletions

View File

@ -1311,7 +1311,9 @@ fi
%dir %{python_sitelib}/ipapython/secrets
%{python_sitelib}/ipapython/secrets/*.py*
%dir %{python_sitelib}/ipalib
%{python_sitelib}/ipalib/*
%{python_sitelib}/ipalib/*.py*
%dir %{python_sitelib}/ipalib/install
%{python_sitelib}/ipalib/install/*.py*
%dir %{python_sitelib}/ipaplatform
%{python_sitelib}/ipaplatform/*
%{python_sitelib}/ipapython-*.egg-info

View File

@ -0,0 +1,27 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Automount installer module
"""
from ipalib.install import service
from ipalib.install.service import enroll_only
from ipapython.install.core import knob
class AutomountInstallInterface(service.ServiceInstallInterface):
"""
Interface of the automount installer
Knobs defined here will be available in:
* ipa-client-install
* ipa-client-automount
"""
automount_location = knob(
str, 'default',
description="Automount location",
)
automount_location = enroll_only(automount_location)

View File

@ -43,6 +43,9 @@ from ipalib import (
x509,
)
from ipalib.constants import CACERT
from ipalib.install import hostname as hostname_
from ipalib.install import service
from ipalib.install.service import enroll_only, prepare_only
from ipalib.rpc import delete_persistent_client_session_data
from ipalib.util import (
broadcast_ip_address_warning,
@ -62,6 +65,8 @@ from ipapython import (
)
from ipapython.admintool import ScriptError
from ipapython.dn import DN
from ipapython.install import typing
from ipapython.install.core import knob
from ipapython.ipa_log_manager import root_logger
from ipapython.ipautil import (
CalledProcessError,
@ -74,6 +79,10 @@ from ipapython.ipautil import (
)
from ipapython.ssh import SSHPublicKey
from . import automount
NoneType = type(None)
SUCCESS = 0
CLIENT_INSTALL_ERROR = 1
CLIENT_NOT_CONFIGURED = 2
@ -3298,3 +3307,188 @@ def uninstall(options):
if rv:
raise ScriptError(rval=rv)
class ClientInstallInterface(hostname_.HostNameInstallInterface,
service.ServiceAdminInstallInterface):
"""
Interface of the client installer
Knobs defined here will be available in:
* ipa-client-install
* ipa-server-install
* ipa-replica-prepare
* ipa-replica-install
"""
fixed_primary = knob(
None,
description="Configure sssd to use fixed server as primary IPA server",
)
fixed_primary = enroll_only(fixed_primary)
principal = knob(
bases=service.ServiceAdminInstallInterface.principal,
description="principal to use to join the IPA realm",
)
principal = enroll_only(principal)
host_password = knob(
str, None,
sensitive=True,
)
host_password = enroll_only(host_password)
keytab = knob(
str, None,
description="path to backed up keytab from previous enrollment",
cli_names=[None, '-k'],
)
keytab = enroll_only(keytab)
mkhomedir = knob(
None,
description="create home directories for users on their first login",
)
mkhomedir = enroll_only(mkhomedir)
force_join = knob(
None,
description="Force client enrollment even if already enrolled",
)
force_join = enroll_only(force_join)
ntp_servers = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description="ntp server to use. This option can be used multiple "
"times",
cli_names='--ntp-server',
cli_metavar='NTP_SERVER',
)
ntp_servers = enroll_only(ntp_servers)
no_ntp = knob(
None,
description="do not configure ntp",
cli_names=[None, '-N'],
)
no_ntp = enroll_only(no_ntp)
force_ntpd = knob(
None,
description="Stop and disable any time&date synchronization services "
"besides ntpd",
)
force_ntpd = enroll_only(force_ntpd)
nisdomain = knob(
str, None,
description="NIS domain name",
)
nisdomain = enroll_only(nisdomain)
no_nisdomain = knob(
None,
description="do not configure NIS domain name",
)
no_nisdomain = enroll_only(no_nisdomain)
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_sudo = knob(
None,
description="do not configure SSSD as data source for sudo",
)
no_sudo = enroll_only(no_sudo)
no_dns_sshfp = knob(
None,
description="do not automatically create DNS SSHFP records",
)
no_dns_sshfp = enroll_only(no_dns_sshfp)
kinit_attempts = knob(
int, 5,
description="number of attempts to obtain host TGT (defaults to 5).",
)
kinit_attempts = enroll_only(kinit_attempts)
@kinit_attempts.validator
def kinit_attempts(self, value):
if value < 1:
raise ValueError("expects an integer greater than 0.")
request_cert = knob(
None,
description="request certificate for the machine",
)
request_cert = prepare_only(request_cert)
permit = knob(
None,
description="disable access rules by default, permit all access.",
)
permit = enroll_only(permit)
enable_dns_updates = knob(
None,
description="Configures the machine to attempt dns updates when the "
"ip address changes.",
)
enable_dns_updates = enroll_only(enable_dns_updates)
no_krb5_offline_passwords = knob(
None,
description="Configure SSSD not to store user password when the "
"server is offline",
)
no_krb5_offline_passwords = enroll_only(no_krb5_offline_passwords)
preserve_sssd = knob(
None,
description="Preserve old SSSD configuration if possible",
)
preserve_sssd = enroll_only(preserve_sssd)
def __init__(self, **kwargs):
super(ClientInstallInterface, self).__init__(**kwargs)
if self.servers and not self.domain_name:
raise RuntimeError(
"--server cannot be used without providing --domain")
if self.force_ntpd and self.no_ntp:
raise RuntimeError(
"--force-ntpd cannot be used together with --no-ntp")
if self.no_nisdomain and self.nisdomain:
raise RuntimeError(
"--no-nisdomain cannot be used together with --nisdomain")
if self.ip_addresses:
if self.enable_dns_updates:
raise RuntimeError(
"--ip-address cannot be used together with"
" --enable-dns-updates")
if self.all_ip_addresses:
raise RuntimeError(
"--ip-address cannot be used together with"
"--all-ip-addresses")

View File

@ -0,0 +1,3 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#

View File

@ -0,0 +1,59 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Host name installer module
"""
from ipapython.install import typing
from ipapython.install.core import knob
from ipapython.ipautil import CheckedIPAddress
from . import service
from .service import prepare_only
class HostNameInstallInterface(service.ServiceInstallInterface):
"""
Interface common to all service installers which create DNS address
records for `host_name`
"""
ip_addresses = knob(
# pylint: disable=invalid-sequence-index
typing.List[CheckedIPAddress], None,
description="Specify IP address that should be added to DNS. This "
"option can be used multiple times",
cli_names='--ip-address',
cli_metavar='IP_ADDRESS',
)
ip_addresses = prepare_only(ip_addresses)
@ip_addresses.validator
def ip_addresses(self, values):
for value in values:
try:
CheckedIPAddress(value, match_local=True)
except Exception as e:
raise ValueError("invalid IP address {0}: {1}".format(
value, e))
all_ip_addresses = knob(
None,
description="All routable IP addresses configured on any inteface "
"will be added to DNS",
)
all_ip_addresses = prepare_only(all_ip_addresses)
no_host_dns = knob(
None,
description="Do not use DNS for hostname lookup during installation",
)
no_host_dns = prepare_only(no_host_dns)
no_wait_for_dns = knob(
None,
description="do not wait until the host is resolvable in DNS",
)
no_wait_for_dns = prepare_only(no_wait_for_dns)

178
ipalib/install/service.py Normal file
View File

@ -0,0 +1,178 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Base service installer module
"""
from ipalib.util import validate_domain_name
from ipapython.install import common, core, typing
from ipapython.install.core import knob
def prepare_only(obj):
"""
Decorator which makes an installer attribute appear only in the prepare
phase of the install
"""
obj.__exclude__ = getattr(obj, '__exclude__', set()) | {'enroll'}
return obj
def enroll_only(obj):
"""
Decorator which makes an installer attribute appear only in the enroll
phase of the install
"""
obj.__exclude__ = getattr(obj, '__exclude__', set()) | {'prepare'}
return obj
def master_install_only(obj):
"""
Decorator which makes an installer attribute appear only in master install
"""
obj.__exclude__ = getattr(obj, '__exclude__', set()) | {'replica_install'}
return obj
def replica_install_only(obj):
"""
Decorator which makes an installer attribute appear only in replica install
"""
obj.__exclude__ = getattr(obj, '__exclude__', set()) | {'master_install'}
return obj
def _does(cls, arg):
def remove(name):
def removed(self):
raise AttributeError(name)
return property(removed)
return type(
cls.__name__,
(cls,),
{
n: remove(n) for n in dir(cls)
if arg in getattr(getattr(cls, n), '__exclude__', set())
}
)
def prepares(cls):
"""
Returns installer class stripped of attributes not related to the prepare
phase of the install
"""
return _does(cls, 'prepare')
def enrolls(cls):
"""
Returns installer class stripped of attributes not related to the enroll
phase of the install
"""
return _does(cls, 'enroll')
def installs_master(cls):
"""
Returns installer class stripped of attributes not related to master
install
"""
return _does(cls, 'master_install')
def installs_replica(cls):
"""
Returns installer class stripped of attributes not related to replica
install
"""
return _does(cls, 'replica_install')
class ServiceInstallInterface(common.Installable,
common.Interactive,
core.Composite):
"""
Interface common to all service installers
"""
domain_name = knob(
str, None,
description="domain name",
cli_names='--domain',
)
@domain_name.validator
def domain_name(self, value):
validate_domain_name(value)
servers = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description="FQDN of IPA server",
cli_names='--server',
cli_metavar='SERVER',
)
realm_name = knob(
str, None,
description="realm name",
cli_names='--realm',
)
host_name = knob(
str, None,
description="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.",
cli_names='--hostname',
)
ca_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description="load the CA certificate from this file",
cli_names='--ca-cert-file',
cli_metavar='FILE',
)
replica_file = knob(
str, None,
description="a file generated by ipa-replica-prepare",
)
replica_file = enroll_only(replica_file)
replica_file = replica_install_only(replica_file)
dm_password = knob(
str, None,
sensitive=True,
description="Directory Manager password (for the existing master)",
)
dm_password = enroll_only(dm_password)
dm_password = replica_install_only(dm_password)
class ServiceAdminInstallInterface(ServiceInstallInterface):
"""
Interface common to all service installers which require admin user
authentication
"""
principal = knob(
str, None,
)
principal = enroll_only(principal)
principal = replica_install_only(principal)
admin_password = knob(
str, None,
sensitive=True,
)
admin_password = enroll_only(admin_password)
admin_password = replica_install_only(admin_password)

View File

@ -34,5 +34,6 @@ if __name__ == '__main__':
package_dir={'ipalib': ''},
packages=[
"ipalib",
"ipalib.install",
],
)

View File

@ -2,11 +2,24 @@
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
"""
CA installer module
"""
from __future__ import print_function
import enum
import os.path
from ipaserver.install import cainstance, custodiainstance, dsinstance, bindinstance
import six
from ipalib.install.service import enroll_only, master_install_only, replica_install_only
from ipapython.install import typing
from ipapython.install.core import knob
from ipaserver.install import (cainstance,
custodiainstance,
dsinstance,
bindinstance)
from ipapython import ipautil, certdb
from ipapython.admintool import ScriptError
from ipaplatform import services
@ -17,6 +30,19 @@ from ipalib import api, certstore, x509
from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger
from . import conncheck, dogtag
if six.PY3:
unicode = str
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']
external_cert_file = None
external_ca_file = None
@ -270,3 +296,109 @@ def uninstall():
ca_instance.stop_tracking_certificates()
if ca_instance.is_configured():
ca_instance.uninstall()
class ExternalCAType(enum.Enum):
GENERIC = 'generic'
MS_CS = 'ms-cs'
class CASigningAlgorithm(enum.Enum):
SHA1_WITH_RSA = 'SHA1withRSA'
SHA_256_WITH_RSA = 'SHA256withRSA'
SHA_512_WITH_RSA = 'SHA512withRSA'
class CAInstallInterface(dogtag.DogtagInstallInterface,
conncheck.ConnCheckInterface):
"""
Interface of the CA installer
Knobs defined here will be available in:
* ipa-server-install
* ipa-replica-prepare
* ipa-replica-install
* ipa-ca-install
"""
principal = knob(
bases=conncheck.ConnCheckInterface.principal,
description="User allowed to manage replicas",
cli_names=(
list(conncheck.ConnCheckInterface.principal.cli_names) + ['-P']),
)
principal = enroll_only(principal)
principal = replica_install_only(principal)
admin_password = knob(
bases=conncheck.ConnCheckInterface.admin_password,
description="Admin user Kerberos password used for connection check",
cli_names=(
list(conncheck.ConnCheckInterface.admin_password.cli_names) +
['-w']),
)
admin_password = enroll_only(admin_password)
admin_password = replica_install_only(admin_password)
external_ca = knob(
None,
description=("Generate a CSR for the IPA CA certificate to be signed "
"by an external CA"),
)
external_ca = master_install_only(external_ca)
external_ca_type = knob(
ExternalCAType, None,
description="Type of the external CA",
)
external_ca_type = master_install_only(external_ca_type)
external_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description=("File containing the IPA CA certificate and the external "
"CA certificate chain"),
cli_names='--external-cert-file',
cli_deprecated_names=['--external_cert_file', '--external_ca_file'],
cli_metavar='FILE',
)
external_cert_files = master_install_only(external_cert_files)
@external_cert_files.validator
def external_cert_files(self, value):
if any(not os.path.isabs(path) for path in value):
raise ValueError("must use an absolute path")
subject = knob(
str, None,
description="The certificate subject base (default O=<realm-name>)",
)
subject = master_install_only(subject)
@subject.validator
def subject(self, value):
v = unicode(value, 'utf-8')
if any(ord(c) < 0x20 for c in v):
raise ValueError("must not contain control characters")
if '&' in v:
raise ValueError("must not contain an ampersand (\"&\")")
try:
dn = DN(v)
for rdn in dn:
if rdn.attr.lower() not in VALID_SUBJECT_ATTRS:
raise ValueError("invalid attribute: \"%s\"" % rdn.attr)
except ValueError as e:
raise ValueError("invalid subject base format: %s" % e)
ca_signing_algorithm = knob(
CASigningAlgorithm, None,
description="Signing algorithm of the IPA CA certificate",
)
ca_signing_algorithm = master_install_only(ca_signing_algorithm)
skip_schema_check = knob(
None,
description="skip check for updated CA DS schema on the remote master",
)
skip_schema_check = enroll_only(skip_schema_check)
skip_schema_check = replica_install_only(skip_schema_check)

View File

@ -0,0 +1,25 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Connection check module
"""
from ipalib.install import service
from ipalib.install.service import enroll_only, replica_install_only
from ipapython.install.core import knob
class ConnCheckInterface(service.ServiceAdminInstallInterface):
"""
Interface common to all installers which perform connection check to the
remote master.
"""
skip_conncheck = knob(
None,
description="skip connection check to remote master",
)
skip_conncheck = enroll_only(skip_conncheck)
skip_conncheck = replica_install_only(skip_conncheck)

View File

@ -2,11 +2,19 @@
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
"""
DNS installer module
"""
from __future__ import absolute_import
from __future__ import print_function
import enum
# absolute import is necessary because IPA module dns clashes with python-dns
from dns import resolver
import six
import sys
from subprocess import CalledProcessError
@ -14,6 +22,8 @@ from subprocess import CalledProcessError
from ipalib import api
from ipalib import errors
from ipalib import util
from ipalib.install import hostname
from ipalib.install.service import enroll_only, prepare_only
from ipaplatform.paths import paths
from ipaplatform.constants import constants
from ipaplatform import services
@ -21,6 +31,9 @@ from ipapython import ipautil
from ipapython import sysrestore
from ipapython import dnsutil
from ipapython.dn import DN
from ipapython.dnsutil import check_zone_overlap
from ipapython.install import typing
from ipapython.install.core import knob
from ipapython.ipa_log_manager import root_logger
from ipapython.admintool import ScriptError
from ipapython.ipautil import user_input
@ -32,6 +45,9 @@ from ipaserver.install import dnskeysyncinstance
from ipaserver.install import odsexporterinstance
from ipaserver.install import opendnssecinstance
if six.PY3:
unicode = str
ip_addresses = []
reverse_zones = []
@ -392,3 +408,119 @@ def uninstall():
dnskeysync = dnskeysyncinstance.DNSKeySyncInstance(fstore)
if dnskeysync.is_configured():
dnskeysync.uninstall()
class DNSForwardPolicy(enum.Enum):
ONLY = 'only'
FIRST = 'first'
class DNSInstallInterface(hostname.HostNameInstallInterface):
"""
Interface of the DNS installer
Knobs defined here will be available in:
* ipa-server-install
* ipa-replica-prepare
* ipa-replica-install
* ipa-dns-install
"""
allow_zone_overlap = knob(
None,
description="Create DNS zone even if it already exists",
)
allow_zone_overlap = prepare_only(allow_zone_overlap)
reverse_zones = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], [],
description=("The reverse DNS zone to use. This option can be used "
"multiple times"),
cli_names='--reverse-zone',
cli_metavar='REVERSE_ZONE',
)
reverse_zones = prepare_only(reverse_zones)
@reverse_zones.validator
def reverse_zones(self, values):
if not self.allow_zone_overlap:
for zone in values:
check_zone_overlap(zone)
no_reverse = knob(
None,
description="Do not create new reverse DNS zone",
)
no_reverse = prepare_only(no_reverse)
auto_reverse = knob(
None,
description="Create necessary reverse zones",
)
auto_reverse = prepare_only(auto_reverse)
zonemgr = knob(
str, None,
description=("DNS zone manager e-mail address. Defaults to "
"hostmaster@DOMAIN"),
)
zonemgr = prepare_only(zonemgr)
@zonemgr.validator
def zonemgr(self, value):
# validate the value first
try:
# IDNA support requires unicode
encoding = getattr(sys.stdin, 'encoding', None)
if encoding is None:
encoding = 'utf-8'
value = value.decode(encoding)
bindinstance.validate_zonemgr_str(value)
except ValueError as e:
# FIXME we can do this in better way
# https://fedorahosted.org/freeipa/ticket/4804
# decode to proper stderr encoding
stderr_encoding = getattr(sys.stderr, 'encoding', None)
if stderr_encoding is None:
stderr_encoding = 'utf-8'
error = unicode(e).encode(stderr_encoding)
raise ValueError(error)
forwarders = knob(
# pylint: disable=invalid-sequence-index
typing.List[ipautil.CheckedIPAddress], None,
description=("Add a DNS forwarder. This option can be used multiple "
"times"),
cli_names='--forwarder',
)
forwarders = enroll_only(forwarders)
no_forwarders = knob(
None,
description="Do not add any DNS forwarders, use root servers instead",
)
no_forwarders = enroll_only(no_forwarders)
auto_forwarders = knob(
None,
description="Use DNS forwarders configured in /etc/resolv.conf",
)
auto_forwarders = enroll_only(auto_forwarders)
forward_policy = knob(
DNSForwardPolicy, None,
description=("DNS forwarding policy for global forwarders"),
)
forward_policy = enroll_only(forward_policy)
no_dnssec_validation = knob(
None,
description="Disable DNSSEC validation",
)
no_dnssec_validation = enroll_only(no_dnssec_validation)
dnssec_master = False
disable_dnssec_master = False
kasp_db_file = None
force = False

View File

@ -0,0 +1,25 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Dogtag-based service installer module
"""
from ipalib.install import service
from ipalib.install.service import prepare_only, replica_install_only
from ipapython.install.core import knob
class DogtagInstallInterface(service.ServiceInstallInterface):
"""
Interface common to all Dogtag-based service installers
"""
ca_file = knob(
str, None,
description="location of CA PKCS#12 file",
cli_metavar='FILE',
)
ca_file = prepare_only(ca_file)
ca_file = replica_install_only(ca_file)

View File

@ -2,6 +2,10 @@
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
"""
KRA installer module
"""
import os
import shutil
@ -15,7 +19,9 @@ from ipaserver.install import custodiainstance
from ipaserver.install import cainstance
from ipaserver.install import krainstance
from ipaserver.install import dsinstance
from ipaserver.install import service
from ipaserver.install import service as _service
from . import dogtag
def install_check(api, replica_config, options):
@ -109,7 +115,7 @@ def install(api, replica_config, options):
ra_only=ra_only,
promote=promote)
service.print_msg("Restarting the directory server")
_service.print_msg("Restarting the directory server")
ds = dsinstance.DsInstance()
ds.restart()
@ -134,3 +140,15 @@ def uninstall(standalone):
kra.stop_tracking_certificates(stop_certmonger=not standalone)
if kra.is_installed():
kra.uninstall()
class KRAInstallInterface(dogtag.DogtagInstallInterface):
"""
Interface of the KRA installer
Knobs defined here will be available in:
* ipa-server-install
* ipa-replica-prepare
* ipa-replica-install
* ipa-kra-install
"""

View File

@ -2,7 +2,539 @@
# 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,
master_install_only,
prepare_only,
replica_install_only)
from ipalib.util import validate_domain_name
from ipapython import ipautil
from ipapython.dnsutil import check_zone_overlap
from ipapython.install import typing
from ipapython.install.core import knob
from .install import validate_admin_password, validate_dm_password
from .install import Server
from .replicainstall import Replica
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']),
)
domain_name = replica_install_only(domain_name)
new_domain_name = knob(
bases=client.ClientInstallInterface.domain_name,
cli_names=['--domain', '-n'],
cli_metavar='DOMAIN_NAME',
)
new_domain_name = master_install_only(new_domain_name)
@new_domain_name.validator
def new_domain_name(self, value):
validate_domain_name(value)
if (self.setup_dns and
not self.allow_zone_overlap): # pylint: disable=no-member
print("Checking DNS domain %s, please wait ..." % value)
check_zone_overlap(value, False)
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)
new_dm_password = knob(
str, None,
sensitive=True,
description="Directory Manager password",
cli_names='--dm-password',
cli_metavar='DM_PASSWORD',
)
new_dm_password = master_install_only(new_dm_password)
@new_dm_password.validator
def new_dm_password(self, value):
validate_dm_password(value)
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)
admin_password = knob(
bases=client.ClientInstallInterface.admin_password,
description="Kerberos password for the specified admin principal",
)
admin_password = replica_install_only(admin_password)
new_admin_password = knob(
str, None,
sensitive=True,
description="admin user kerberos password",
cli_names='--admin-password',
cli_metavar='ADMIN_PASSWORD',
)
new_admin_password = master_install_only(new_admin_password)
@new_admin_password.validator
def new_admin_password(self, value):
validate_admin_password(value)
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.new_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.new_dm_password or
not self.new_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 disable pkinit w/ dogtag until that is supported
self.no_pkinit = True