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 %dir %{python_sitelib}/ipapython/secrets
%{python_sitelib}/ipapython/secrets/*.py* %{python_sitelib}/ipapython/secrets/*.py*
%dir %{python_sitelib}/ipalib %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 %dir %{python_sitelib}/ipaplatform
%{python_sitelib}/ipaplatform/* %{python_sitelib}/ipaplatform/*
%{python_sitelib}/ipapython-*.egg-info %{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, x509,
) )
from ipalib.constants import CACERT 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.rpc import delete_persistent_client_session_data
from ipalib.util import ( from ipalib.util import (
broadcast_ip_address_warning, broadcast_ip_address_warning,
@@ -62,6 +65,8 @@ from ipapython import (
) )
from ipapython.admintool import ScriptError from ipapython.admintool import ScriptError
from ipapython.dn import DN 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.ipa_log_manager import root_logger
from ipapython.ipautil import ( from ipapython.ipautil import (
CalledProcessError, CalledProcessError,
@@ -74,6 +79,10 @@ from ipapython.ipautil import (
) )
from ipapython.ssh import SSHPublicKey from ipapython.ssh import SSHPublicKey
from . import automount
NoneType = type(None)
SUCCESS = 0 SUCCESS = 0
CLIENT_INSTALL_ERROR = 1 CLIENT_INSTALL_ERROR = 1
CLIENT_NOT_CONFIGURED = 2 CLIENT_NOT_CONFIGURED = 2
@@ -3298,3 +3307,188 @@ def uninstall(options):
if rv: if rv:
raise ScriptError(rval=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': ''}, package_dir={'ipalib': ''},
packages=[ packages=[
"ipalib", "ipalib",
"ipalib.install",
], ],
) )

View File

@@ -2,11 +2,24 @@
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license # Copyright (C) 2015 FreeIPA Contributors see COPYING for license
# #
"""
CA installer module
"""
from __future__ import print_function from __future__ import print_function
import enum
import os.path 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 import ipautil, certdb
from ipapython.admintool import ScriptError from ipapython.admintool import ScriptError
from ipaplatform import services from ipaplatform import services
@@ -17,6 +30,19 @@ from ipalib import api, certstore, x509
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger 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_cert_file = None
external_ca_file = None external_ca_file = None
@@ -270,3 +296,109 @@ def uninstall():
ca_instance.stop_tracking_certificates() ca_instance.stop_tracking_certificates()
if ca_instance.is_configured(): if ca_instance.is_configured():
ca_instance.uninstall() 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 # Copyright (C) 2015 FreeIPA Contributors see COPYING for license
# #
"""
DNS installer module
"""
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import print_function from __future__ import print_function
import enum
# absolute import is necessary because IPA module dns clashes with python-dns # absolute import is necessary because IPA module dns clashes with python-dns
from dns import resolver from dns import resolver
import six
import sys import sys
from subprocess import CalledProcessError from subprocess import CalledProcessError
@@ -14,6 +22,8 @@ from subprocess import CalledProcessError
from ipalib import api from ipalib import api
from ipalib import errors from ipalib import errors
from ipalib import util 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.paths import paths
from ipaplatform.constants import constants from ipaplatform.constants import constants
from ipaplatform import services from ipaplatform import services
@@ -21,6 +31,9 @@ from ipapython import ipautil
from ipapython import sysrestore from ipapython import sysrestore
from ipapython import dnsutil from ipapython import dnsutil
from ipapython.dn import DN 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.ipa_log_manager import root_logger
from ipapython.admintool import ScriptError from ipapython.admintool import ScriptError
from ipapython.ipautil import user_input from ipapython.ipautil import user_input
@@ -32,6 +45,9 @@ from ipaserver.install import dnskeysyncinstance
from ipaserver.install import odsexporterinstance from ipaserver.install import odsexporterinstance
from ipaserver.install import opendnssecinstance from ipaserver.install import opendnssecinstance
if six.PY3:
unicode = str
ip_addresses = [] ip_addresses = []
reverse_zones = [] reverse_zones = []
@@ -392,3 +408,119 @@ def uninstall():
dnskeysync = dnskeysyncinstance.DNSKeySyncInstance(fstore) dnskeysync = dnskeysyncinstance.DNSKeySyncInstance(fstore)
if dnskeysync.is_configured(): if dnskeysync.is_configured():
dnskeysync.uninstall() 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 # Copyright (C) 2015 FreeIPA Contributors see COPYING for license
# #
"""
KRA installer module
"""
import os import os
import shutil import shutil
@@ -15,7 +19,9 @@ from ipaserver.install import custodiainstance
from ipaserver.install import cainstance from ipaserver.install import cainstance
from ipaserver.install import krainstance from ipaserver.install import krainstance
from ipaserver.install import dsinstance 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): def install_check(api, replica_config, options):
@@ -109,7 +115,7 @@ def install(api, replica_config, options):
ra_only=ra_only, ra_only=ra_only,
promote=promote) promote=promote)
service.print_msg("Restarting the directory server") _service.print_msg("Restarting the directory server")
ds = dsinstance.DsInstance() ds = dsinstance.DsInstance()
ds.restart() ds.restart()
@@ -134,3 +140,15 @@ def uninstall(standalone):
kra.stop_tracking_certificates(stop_certmonger=not standalone) kra.stop_tracking_certificates(stop_certmonger=not standalone)
if kra.is_installed(): if kra.is_installed():
kra.uninstall() 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 # 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 .install import Server
from .replicainstall import Replica from .replicainstall import Replica
from .upgrade import upgrade_check, upgrade 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