summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <simo@redhat.com>2015-06-11 15:45:38 -0400
committerSimo Sorce <simo@redhat.com>2015-10-01 16:20:48 -0400
commit70bd0ec94c87069b0f4d8777332ac62bbd541ab6 (patch)
treee2ff5901c1cef8b583aec008d0d83ff64ad4f582
parent4622d20742d60bf57d5fda6effa534c2ed2a48d8 (diff)
downloadfreeipa-70bd0ec94c87069b0f4d8777332ac62bbd541ab6.tar.gz
freeipa-70bd0ec94c87069b0f4d8777332ac62bbd541ab6.tar.xz
freeipa-70bd0ec94c87069b0f4d8777332ac62bbd541ab6.zip
Implement replica promotion functionality
This patch implements a new flag --promote for the ipa-replica-install command that allows an administrative user to 'promote' an already joined client to become a full ipa server. The only credentials used are that of an administrator. This code relies on ipa-custodia being available on the peer master as well as a number of other patches to allow a computer account to request certificates for its services. Therefore this feature is marked to work only with domain level 1 and above servers. Ticket: https://fedorahosted.org/freeipa/ticket/2888 Signed-off-by: Simo Sorce <simo@redhat.com>
-rwxr-xr-xinstall/tools/ipa-replica-install1
-rw-r--r--ipaplatform/base/paths.py1
-rw-r--r--ipapython/install/cli.py11
-rw-r--r--ipaserver/install/cainstance.py24
-rw-r--r--ipaserver/install/certs.py12
-rw-r--r--ipaserver/install/custodiainstance.py42
-rw-r--r--ipaserver/install/dsinstance.py93
-rw-r--r--ipaserver/install/httpinstance.py20
-rw-r--r--ipaserver/install/installutils.py29
-rw-r--r--ipaserver/install/krbinstance.py13
-rw-r--r--ipaserver/install/replication.py90
-rw-r--r--ipaserver/install/server/install.py6
-rw-r--r--ipaserver/install/server/replicainstall.py633
-rw-r--r--ipaserver/install/server/upgrade.py4
14 files changed, 921 insertions, 58 deletions
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 10a10827e..60a853b41 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -30,6 +30,7 @@ ReplicaInstall = cli.install_tool(
usage='%prog [options] REPLICA_FILE',
log_file_name=paths.IPAREPLICA_INSTALL_LOG,
debug_option=True,
+ use_private_ccache=False,
)
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index efbcc0f96..98aa5f9b7 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -358,6 +358,7 @@ class BasePathNamespace(object):
IPA_CUSTODIA_CONF = '/etc/ipa/custodia/custodia.conf'
IPA_CUSTODIA_SOCKET = '/run/httpd/ipa-custodia.sock'
IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log'
+ IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
path_namespace = BasePathNamespace
diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
index ce64baa5f..6d66e1551 100644
--- a/ipapython/install/cli.py
+++ b/ipapython/install/cli.py
@@ -20,6 +20,7 @@ __all__ = ['install_tool', 'uninstall_tool']
def install_tool(configurable_class, command_name, log_file_name,
positional_arguments=None, usage=None, debug_option=False,
+ use_private_ccache=True,
uninstall_log_file_name=None,
uninstall_positional_arguments=None, uninstall_usage=None):
if (uninstall_log_file_name is not None or
@@ -47,6 +48,7 @@ def install_tool(configurable_class, command_name, log_file_name,
usage=usage,
debug_option=debug_option,
uninstall_kwargs=uninstall_kwargs,
+ use_private_ccache=use_private_ccache,
)
)
@@ -71,6 +73,7 @@ class ConfigureTool(admintool.AdminTool):
configurable_class = None
debug_option = False
positional_arguments = None
+ use_private_ccache = True
@staticmethod
def _transform(configurable_class):
@@ -300,10 +303,12 @@ class ConfigureTool(admintool.AdminTool):
signal.signal(signal.SIGTERM, self.__signal_handler)
- # Use private ccache
- with private_ccache():
+ if self.use_private_ccache:
+ with private_ccache():
+ super(ConfigureTool, self).run()
+ cfgr.run()
+ else:
super(ConfigureTool, self).run()
-
cfgr.run()
@staticmethod
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index c4788816a..ebf9a7ea5 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -253,6 +253,30 @@ def is_step_one_done():
return False
+def find_ca_server(host_name, conn, api=api):
+ """
+ :param host_name: the preferred server
+ :param conn: a connection to the LDAP server
+ :return: the selected host name
+
+ Find a server that is a CA.
+ """
+ base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+ api.env.basedn)
+ query_filter = conn.make_filter({'objectClass': 'ipaConfigObject',
+ 'ipaConfigString': 'enabledService',
+ 'cn': 'CA'}, rules='&')
+ entries, trunc = conn.find_entries(filter=query_filter, base_dn=base_dn)
+ if len(entries):
+ if host_name is not None:
+ for entry in entries:
+ if entry.dn[1].value == host_name:
+ return host_name
+ # if the preferred is not found, return the first in the list
+ return entries[0].dn[1].value
+ return None
+
+
def is_ca_installed_locally():
"""Check if CA is installed locally by checking for existence of CS.cfg
:return:True/False
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 3e07ee398..68cf9ce4e 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -652,6 +652,18 @@ class CertDB(object):
def export_pem_cert(self, nickname, location):
return self.nssdb.export_pem_cert(nickname, location)
+ def request_service_cert(self, nickname, principal, host, pwdconf=False):
+ self.create_from_cacert(paths.IPA_CA_CRT)
+ if pwdconf:
+ self.create_password_conf()
+ reqid = certmonger.request_cert(nssdb=self.secdir,
+ nickname=nickname,
+ principal=principal,
+ subject=host,
+ passwd_fname=self.passwd_fname)
+ # Now wait for the cert to appear. Check three times then abort
+ certmonger.wait_for_request(reqid, timeout=15)
+
class _CrossProcessLock(object):
_DATETIME_FORMAT = '%Y%m%d%H%M%S%f'
diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py
index c21b4537d..f506ba163 100644
--- a/ipaserver/install/custodiainstance.py
+++ b/ipaserver/install/custodiainstance.py
@@ -1,6 +1,7 @@
# Copyright (C) 2015 FreeIPa Project Contributors, see 'COPYING' for license.
from ipapython.secrets.kem import IPAKEMKeys
+from ipapython.secrets.client import CustodiaClient
from ipaplatform.paths import paths
from service import SimpleServiceInstance
from ipapython import ipautil
@@ -9,11 +10,14 @@ import os
class CustodiaInstance(SimpleServiceInstance):
- def __init__(self):
+ def __init__(self, host_name=None, realm=None):
super(CustodiaInstance, self).__init__("ipa-custodia")
self.config_file = paths.IPA_CUSTODIA_CONF
self.server_keys = os.path.join(paths.IPA_CUSTODIA_CONF_DIR,
'server.keys')
+ self.ldap_uri = None
+ self.fqdn = host_name
+ self.realm = realm
def __config_file(self):
template_file = os.path.basename(self.config_file) + '.template'
@@ -28,22 +32,48 @@ class CustodiaInstance(SimpleServiceInstance):
fd.flush()
fd.close()
- def create_instance(self, *args, **kwargs):
+ def create_instance(self, dm_password=None):
+ suffix = ipautil.realm_to_suffix(self.realm)
self.step("Generating ipa-custodia config file", self.__config_file)
self.step("Generating ipa-custodia keys", self.__gen_keys)
- super(CustodiaInstance, self).create_instance(*args, **kwargs)
+ super(CustodiaInstance, self).create_instance(gensvc_name='KEYS',
+ fqdn=self.fqdn,
+ dm_password=dm_password,
+ ldap_suffix=suffix,
+ realm=self.realm)
def __gen_keys(self):
- KeyStore = IPAKEMKeys({'server_keys': self.server_keys})
+ KeyStore = IPAKEMKeys({'server_keys': self.server_keys,
+ 'ldap_uri': self.ldap_uri})
KeyStore.generate_server_keys()
- def upgrade_instance(self, realm):
- self.realm = realm
+ def upgrade_instance(self):
if not os.path.exists(self.config_file):
self.__config_file()
if not os.path.exists(self.server_keys):
self.__gen_keys()
+ def create_replica(self, master_host_name):
+ suffix = ipautil.realm_to_suffix(self.realm)
+ self.ldap_uri = 'ldap://%s' % master_host_name
+ self.master_host_name = master_host_name
+
+ self.step("Generating ipa-custodia config file", self.__config_file)
+ self.step("Generating ipa-custodia keys", self.__gen_keys)
+ self.step("Importing RA Key", self.__import_ra_key)
+ super(CustodiaInstance, self).create_instance(gensvc_name='KEYS',
+ fqdn=self.fqdn,
+ ldap_suffix=suffix,
+ realm=self.realm)
+
+ def __import_ra_key(self):
+ cli = CustodiaClient(self.fqdn, self.master_host_name, self.realm)
+ cli.fetch_key('ra/ipaCert')
+
+ def import_dm_password(self, master_host_name):
+ cli = CustodiaClient(self.fqdn, master_host_name, self.realm)
+ cli.fetch_key('dm/DMHash')
+
def __start(self):
super(CustodiaInstance, self).__start()
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 312188273..f3a837baa 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -255,8 +255,8 @@ class DsInstance(service.Service):
self.step("configure autobind for root", self.__root_autobind)
self.step("configure new location for managed entries", self.__repoint_managed_entries)
self.step("configure dirsrv ccache", self.configure_dirsrv_ccache)
- self.step("enable SASL mapping fallback", self.__enable_sasl_mapping_fallback)
- self.step("restarting directory server", self.__restart_instance)
+ self.step("enabling SASL mapping fallback",
+ self.__enable_sasl_mapping_fallback)
def __common_post_setup(self):
self.step("initializing group membership", self.init_memberof)
@@ -301,6 +301,7 @@ class DsInstance(service.Service):
subject_base, idstart, idmax, pkcs12_info, ca_file=ca_file)
self.__common_setup()
+ self.step("restarting directory server", self.__restart_instance)
self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings)
self.step("adding default layout", self.__add_default_layout)
@@ -314,6 +315,8 @@ class DsInstance(service.Service):
if hbac_allow:
self.step("creating default HBAC rule allow_all", self.add_hbac)
self.step("creating default CA ACL rule", self.add_caacl)
+ self.step("adding sasl mappings to the directory",
+ self.__configure_sasl_mappings)
self.step("adding entries for topology management", self.__add_topology_entries)
self.__common_post_setup()
@@ -331,7 +334,8 @@ class DsInstance(service.Service):
def create_replica(self, realm_name, master_fqdn, fqdn,
domain_name, dm_password, subject_base,
- pkcs12_info=None, ca_file=None, ca_is_configured=None):
+ pkcs12_info=None, ca_file=None,
+ ca_is_configured=None, promote=False):
# idstart and idmax are configured so that the range is seen as
# depleted by the DNA plugin and the replica will go and get a
# new range from the master.
@@ -353,8 +357,15 @@ class DsInstance(service.Service):
self.master_fqdn = master_fqdn
if ca_is_configured is not None:
self.ca_is_configured = ca_is_configured
+ self.promote = promote
- self.__common_setup(True)
+ self.__common_setup(enable_ssl=(not self.promote))
+ self.step("restarting directory server", self.__restart_instance)
+
+ if self.promote:
+ self.step("creating DS keytab", self.__get_ds_keytab)
+ self.step("retriving DS Certificate", self.__get_ds_cert)
+ self.step("restarting directory server", self.__restart_instance)
self.step("setting up initial replication", self.__setup_replica)
self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings)
@@ -374,14 +385,25 @@ class DsInstance(service.Service):
self.realm,
self.dm_password)
+ # Always connect to self over ldapi
+ conn = ipaldap.IPAdmin(self.fqdn, ldapi=True, realm=self.realm)
+ conn.do_external_bind('root')
repl = replication.ReplicationManager(self.realm,
self.fqdn,
- self.dm_password)
- repl.setup_replication(self.master_fqdn,
- r_binddn=DN(('cn', 'Directory Manager')),
- r_bindpw=self.dm_password)
+ self.dm_password, conn=conn)
+ if self.promote:
+ repl.setup_promote_replication(self.master_fqdn)
+ else:
+ repl.setup_replication(self.master_fqdn,
+ r_binddn=DN(('cn', 'Directory Manager')),
+ r_bindpw=self.dm_password)
self.run_init_memberof = repl.needs_memberof_fixup()
+ # Now that the server is up make sure all changes happen against
+ # the local server (as repica pomotion does not have the DM password.
+ if self.admin_conn:
+ self.ldap_disconnect()
+ self.ldapi = True
def __configure_sasl_mappings(self):
# we need to remove any existing SASL mappings in the directory as otherwise they
@@ -1128,3 +1150,58 @@ class DsInstance(service.Service):
# Create global domain level entry and set the domain level
if self.domainlevel is not None:
self._ldap_mod("domainlevel.ldif", self.sub_dict)
+
+ def __get_ds_keytab(self):
+
+ self.fstore.backup_file(paths.DS_KEYTAB)
+ try:
+ os.unlink(paths.DS_KEYTAB)
+ except OSError:
+ pass
+
+ installutils.install_service_keytab(self.principal,
+ self.master_fqdn,
+ paths.DS_KEYTAB)
+
+ # Configure DS to use the keytab
+ vardict = {"KRB5_KTNAME": paths.DS_KEYTAB}
+ ipautil.config_replace_variables(paths.SYSCONFIG_DIRSRV,
+ replacevars=vardict)
+
+ # Keytab must be owned by DS itself
+ pent = pwd.getpwnam(DS_USER)
+ os.chown(paths.DS_KEYTAB, pent.pw_uid, pent.pw_gid)
+
+ def __get_ds_cert(self):
+ subject = DN(('O', self.realm))
+ nssdb_dir = config_dirname(self.serverid)
+ db = certs.CertDB(self.realm, nssdir=nssdb_dir, subject_base=subject)
+ db.request_service_cert(self.nickname, self.principal, self.fqdn)
+ db.create_pin_file()
+
+ # Connect to self over ldapi as Directory Manager and configure SSL
+ conn = ipaldap.IPAdmin(self.fqdn, ldapi=True, realm=self.realm)
+ conn.do_external_bind('root')
+
+ mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"),
+ (ldap.MOD_REPLACE, "nsSSL3Ciphers", "+all"),
+ (ldap.MOD_REPLACE, "allowWeakCipher", "off")]
+ conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod)
+
+ mod = [(ldap.MOD_ADD, "nsslapd-security", "on")]
+ conn.modify_s(DN(('cn', 'config')), mod)
+
+ entry = conn.make_entry(
+ DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
+ objectclass=["top", "nsEncryptionModule"],
+ cn=["RSA"],
+ nsSSLPersonalitySSL=[self.nickname],
+ nsSSLToken=["internal (software)"],
+ nsSSLActivation=["on"],
+ )
+ conn.add_entry(entry)
+
+ conn.unbind()
+
+ # check for open secure port 636 from now on
+ self.open_ports.append(636)
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 4269d3697..ba16a61b2 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -112,7 +112,7 @@ class HTTPInstance(service.Service):
def create_instance(self, realm, fqdn, domain_name, dm_password=None,
autoconfig=True, pkcs12_info=None,
subject_base=None, auto_redirect=True, ca_file=None,
- ca_is_configured=None):
+ ca_is_configured=None, promote=False):
self.fqdn = fqdn
self.realm = realm
self.domain = domain_name
@@ -132,6 +132,7 @@ class HTTPInstance(service.Service):
self.ca_file = ca_file
if ca_is_configured is not None:
self.ca_is_configured = ca_is_configured
+ self.promote = promote
# get a connection to the DS
self.ldap_connect()
@@ -147,12 +148,13 @@ class HTTPInstance(service.Service):
if self.ca_is_configured:
self.step("configure certmonger for renewals",
self.configure_certmonger_renewal_guard)
+ self.step("setting up httpd keytab", self.__create_http_keytab)
self.step("setting up ssl", self.__setup_ssl)
self.step("importing CA certificates from LDAP", self.__import_ca_certs)
if autoconfig:
self.step("setting up browser autoconfig", self.__setup_autoconfig)
- self.step("publish CA cert", self.__publish_ca_cert)
- self.step("creating a keytab for httpd", self.__create_http_keytab)
+ if not self.promote:
+ self.step("publish CA cert", self.__publish_ca_cert)
self.step("clean up any existing httpd ccache", self.remove_httpd_ccache)
self.step("configuring SELinux for httpd", self.configure_selinux_for_httpd)
if not self.is_kdcproxy_configured():
@@ -183,10 +185,10 @@ class HTTPInstance(service.Service):
self.print_msg(e.format_service_warning('web interface'))
def __create_http_keytab(self):
- installutils.kadmin_addprinc(self.principal)
- installutils.create_keytab(paths.IPA_KEYTAB, self.principal)
- self.move_service(self.principal)
- self.add_cert_to_service()
+ if not self.promote:
+ installutils.kadmin_addprinc(self.principal)
+ installutils.create_keytab(paths.IPA_KEYTAB, self.principal)
+ self.move_service(self.principal)
pent = pwd.getpwnam("apache")
os.chown(paths.IPA_KEYTAB, pent.pw_uid, pent.pw_gid)
@@ -309,14 +311,16 @@ class HTTPInstance(service.Service):
db.track_server_cert(nickname, self.principal, db.passwd_fname, 'restart_httpd')
self.__set_mod_nss_nickname(nickname)
- else:
+ self.add_cert_to_service()
+ elif not self.promote:
db.create_password_conf()
self.dercert = db.create_server_cert(self.cert_nickname, self.fqdn,
ca_db)
db.track_server_cert(self.cert_nickname, self.principal,
db.passwd_fname, 'restart_httpd')
db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
+ self.add_cert_to_service()
# Fix the database permissions
os.chmod(certs.NSS_DIR + "/cert8.db", 0o660)
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index acf309e78..7fca84384 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -47,12 +47,14 @@ from ipapython.admintool import ScriptError
from ipapython.ipa_log_manager import root_logger, log_mgr
from ipalib.util import validate_hostname
from ipapython import config
-from ipalib import errors, x509
+from ipalib import api, errors, x509
from ipapython.dn import DN
from ipaserver.install import certs, service, sysupgrade
from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
+from ipapython import certmonger
+
if six.PY3:
unicode = str
@@ -1115,3 +1117,28 @@ def enable_and_start_oddjobd(sstore):
oddjobd.start()
except Exception as e:
root_logger.critical("Unable to start oddjobd: {0}".format(str(e)))
+
+
+def install_service_keytab(principal, server, path):
+
+ try:
+ api.Backend.rpcclient.connect()
+
+ # Create services if none exists (we use the .forward method
+ # here so that we can control the client version number and avoid
+ # errors. This is a workaround until the API becomes version
+ # independent: FIXME
+
+ api.Backend.rpcclient.forward(
+ 'service_add',
+ krbprincipalname=principal,
+ version=u'2.112' # All the way back to 3.0 servers
+ )
+ except errors.DuplicateEntry:
+ pass
+ finally:
+ if api.Backend.rpcclient.isconnected():
+ api.Backend.rpcclient.disconnect()
+
+ args = [paths.IPA_GETKEYTAB, '-k', path, '-p', principal, '-s', server]
+ ipautil.run(args)
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index 864615d96..1dd807c71 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -173,7 +173,7 @@ class KrbInstance(service.Service):
master_fqdn, host_name,
domain_name, admin_password,
setup_pkinit=False, pkcs12_info=None,
- subject_base=None):
+ subject_base=None, promote=False):
self.pkcs12_info = pkcs12_info
self.subject_base = subject_base
self.master_fqdn = master_fqdn
@@ -181,12 +181,17 @@ class KrbInstance(service.Service):
self.__common_setup(realm_name, host_name, domain_name, admin_password)
self.step("configuring KDC", self.__configure_instance)
- self.step("creating a keytab for the directory", self.__create_ds_keytab)
- self.step("creating a keytab for the machine", self.__create_host_keytab)
+ if not promote:
+ self.step("creating a keytab for the directory",
+ self.__create_ds_keytab)
+ self.step("creating a keytab for the machine",
+ self.__create_host_keytab)
self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
if setup_pkinit:
self.step("installing X509 Certificate for PKINIT", self.__setup_pkinit)
- self.step("enable GSSAPI for replication", self.__convert_to_gssapi_replication)
+ if not promote:
+ self.step("enable GSSAPI for replication",
+ self.__convert_to_gssapi_replication)
self.__common_post_setup()
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index b50dc0584..858e3f36b 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -1605,6 +1605,96 @@ class ReplicationManager(object):
except errors.EmptyModlist:
pass
+ def join_replication_managers(self, conn):
+ """
+ Create a pseudo user to use for replication.
+ """
+ dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
+ ('cn', 'etc'), self.suffix)
+ mydn = DN(('krbprincipalname', 'ldap/%s@%s' % (self.hostname,
+ self.realm)),
+ ('cn', 'services'), ('cn', 'accounts'), self.suffix)
+
+ entry = conn.get_entry(dn)
+ if mydn not in entry['member']:
+ entry['member'].append(mydn)
+
+ try:
+ conn.update_entry(entry)
+ except errors.EmptyModlist:
+ pass
+
+ def add_temp_sasl_mapping(self, conn, r_hostname):
+ """
+ Create a special user to let SASL Mapping find a valid user
+ on first replication.
+ """
+ name = 'ldap/%s@%s' % (r_hostname, self.realm)
+ replica_binddn = DN(('cn', name), ('cn', 'config'))
+ entry = conn.make_entry(
+ replica_binddn,
+ objectclass=["top", "person"],
+ cn=[name],
+ sn=["replication manager pseudo user"]
+ )
+ conn.add_entry(entry)
+
+ entry = conn.get_entry(self.replica_dn())
+ entry['nsDS5ReplicaBindDN'].append(replica_binddn)
+ conn.update_entry(entry)
+
+ entry = conn.make_entry(
+ DN(('cn', 'Peer Master'), ('cn', 'mapping'), ('cn', 'sasl'),
+ ('cn', 'config')),
+ objectclass=["top", "nsSaslMapping"],
+ cn=["Peer Master"],
+ nsSaslMapRegexString=['^[^:@]+$'],
+ nsSaslMapBaseDNTemplate=[DN(('cn', 'config'))],
+ nsSaslMapFilterTemplate=['(cn=&@%s)' % self.realm],
+ nsSaslMapPriority=['1'],
+ )
+ conn.add_entry(entry)
+
+ def remove_temp_replication_user(self, conn, r_hostname):
+ """
+ Remove the special SASL Mapping user created in a previous step.
+ """
+ name = 'ldap/%s@%s' % (r_hostname, self.realm)
+ replica_binddn = DN(('cn', name), ('cn', 'config'))
+ conn.delete_entry(replica_binddn)
+
+ entry = conn.get_entry(self.replica_dn())
+ while replica_binddn in entry['nsDS5ReplicaBindDN']:
+ entry['nsDS5ReplicaBindDN'].remove(replica_binddn)
+ conn.update_entry(entry)
+
+ def setup_promote_replication(self, r_hostname):
+ # note - there appears to be a bug in python-ldap - it does not
+ # allow connections using two different CA certs
+ r_conn = ipaldap.IPAdmin(r_hostname, port=389, protocol='ldap')
+ r_conn.do_sasl_gssapi_bind()
+
+ # Setup the first half
+ l_id = self._get_replica_id(self.conn, r_conn)
+ self.basic_replication_setup(self.conn, l_id, self.repl_man_dn, None)
+ self.add_temp_sasl_mapping(self.conn, r_hostname)
+
+ # Now setup the other half
+ r_id = self._get_replica_id(r_conn, r_conn)
+ self.basic_replication_setup(r_conn, r_id, self.repl_man_dn, None)
+ self.join_replication_managers(r_conn)
+
+ self.setup_agreement(r_conn, self.conn.host, isgssapi=True)
+ self.setup_agreement(self.conn, r_hostname, isgssapi=True)
+
+ # Finally start replication
+ ret = self.start_replication(r_conn, master=False)
+ if ret != 0:
+ raise RuntimeError("Failed to start replication")
+
+ self.remove_temp_replication_user(self.conn, r_hostname)
+
+
class CSReplicationManager(ReplicationManager):
"""ReplicationManager specific to CA agreements
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index e936b6798..3164d0b94 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -814,10 +814,8 @@ def install(installer):
otpd.create_instance('OTPD', host_name, dm_password,
ipautil.realm_to_suffix(realm_name))
- custodia = custodiainstance.CustodiaInstance()
- custodia.create_instance('KEYS', host_name, dm_password,
- ipautil.realm_to_suffix(realm_name),
- realm_name)
+ custodia = custodiainstance.CustodiaInstance(host_name, realm_name)
+ custodia.create_instance(dm_password)
# Create a HTTP instance
http = httpinstance.HTTPInstance(fstore)
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index c0b0761eb..109874877 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -8,13 +8,15 @@ import dns.exception as dnsexception
import dns.name as dnsname
import dns.resolver as dnsresolver
import dns.reversename as dnsreversename
+import getpass
+import gssapi
import os
import shutil
import socket
import sys
import tempfile
-from ipapython import dogtag, ipautil, sysrestore
+from ipapython import certmonger, dogtag, ipaldap, ipautil, sysrestore
from ipapython.dn import DN
from ipapython.install import common, core
from ipapython.install.common import step
@@ -24,14 +26,19 @@ from ipaplatform import services
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
from ipalib import api, certstore, constants, create_api, errors, x509
+import ipaclient.ipachangeconf
import ipaclient.ntpconf
from ipaserver.install import (
bindinstance, ca, cainstance, certs, dns, dsinstance, httpinstance,
installutils, kra, krbinstance, memcacheinstance, ntpinstance,
otpdinstance, custodiainstance, service)
from ipaserver.install.installutils import create_replica_config
+from ipaserver.install.installutils import ReplicaConfig
from ipaserver.install.replication import (
ReplicationManager, replica_conn_check)
+import SSSDConfig
+from subprocess import CalledProcessError
+from binascii import hexlify
from .common import BaseServer
@@ -60,7 +67,29 @@ def make_pkcs12_info(directory, cert_name, password_name):
return None
-def install_replica_ds(config):
+def install_http_certs(config, fstore):
+
+ # Obtain keytab for the HTTP service
+ fstore.backup_file(paths.IPA_KEYTAB)
+ try:
+ os.unlink(paths.IPA_KEYTAB)
+ except OSError:
+ pass
+
+ principal = 'HTTP/%s@%s' % (config.host_name, config.realm_name)
+ installutils.install_service_keytab(principal,
+ config.master_host_name,
+ paths.IPA_KEYTAB)
+
+ # Obtain certificate for the HTTP service
+ nssdir = certs.NSS_DIR
+ subject = DN(('O', config.realm_name))
+ db = certs.CertDB(config.realm_name, nssdir=nssdir, subject_base=subject)
+ db.request_service_cert('Server-Cert', principal, config.host_name, True)
+ # FIXME: need Signing-Cert too ?
+
+
+def install_replica_ds(config, promote=False):
dsinstance.check_ports()
# if we have a pkcs12 file, create the cert db from
@@ -79,12 +108,13 @@ def install_replica_ds(config):
pkcs12_info=pkcs12_info,
ca_is_configured=ipautil.file_exists(config.dir + "/cacert.p12"),
ca_file=config.dir + "/ca.crt",
+ promote=promote,
)
return ds
-def install_krb(config, setup_pkinit=False):
+def install_krb(config, setup_pkinit=False, promote=False):
krb = krbinstance.KrbInstance()
# pkinit files
@@ -94,7 +124,7 @@ def install_krb(config, setup_pkinit=False):
krb.create_replica(config.realm_name,
config.master_host_name, config.host_name,
config.domain_name, config.dirman_password,
- setup_pkinit, pkcs12_info)
+ setup_pkinit, pkcs12_info, promote=promote)
return krb
@@ -115,7 +145,7 @@ def install_ca_cert(ldap, base_dn, realm, cafile):
sys.exit(1)
-def install_http(config, auto_redirect):
+def install_http(config, auto_redirect, promote=False):
# if we have a pkcs12 file, create the cert db from
# that. Otherwise the ds setup will create the CA
# cert
@@ -131,7 +161,8 @@ def install_http(config, auto_redirect):
config.realm_name, config.host_name, config.domain_name,
config.dirman_password, False, pkcs12_info,
auto_redirect=auto_redirect, ca_file=config.dir + "/ca.crt",
- ca_is_configured=ipautil.file_exists(config.dir + "/cacert.p12"))
+ ca_is_configured=ipautil.file_exists(config.dir + "/cacert.p12"),
+ promote=promote)
# Now copy the autoconfiguration files
try:
@@ -153,9 +184,10 @@ def install_http(config, auto_redirect):
def install_dns_records(config, options, remote_api):
if not bindinstance.dns_container_exists(
- config.master_host_name,
+ config.host_name,
ipautil.realm_to_suffix(config.realm_name),
- dm_password=config.dirman_password):
+ realm=config.realm_name, ldapi=True,
+ autobind=ipaldap.AUTOBIND_ENABLED):
return
try:
@@ -283,6 +315,43 @@ def check_dns_resolution(host_name, dns_servers):
return no_errors
+def check_ca_enabled(api):
+ try:
+ api.Backend.rpcclient.connect()
+ result = api.Backend.rpcclient.forward(
+ 'ca_is_enabled',
+ version=u'2.112' # All the way back to 3.0 servers
+ )
+ return result['result']
+ finally:
+ if api.Backend.rpcclient.isconnected():
+ api.Backend.rpcclient.disconnect()
+
+
+def configure_certmonger():
+ messagebus = services.knownservices.messagebus
+ try:
+ messagebus.start()
+ except Exception, e:
+ print("Messagebus service unavailable: %s" % str(e))
+ sys.exit(3)
+
+ # Ensure that certmonger has been started at least once to generate the
+ # cas files in /var/lib/certmonger/cas.
+ cmonger = services.knownservices.certmonger
+ try:
+ cmonger.restart()
+ except Exception, e:
+ print("Certmonger service unavailable: %s" % str(e))
+ sys.exit(3)
+
+ try:
+ cmonger.enable()
+ except Exception, e:
+ print("Failed to enable Certmonger: %s" % str(e))
+ sys.exit(3)
+
+
def remove_replica_info_dir(installer):
# always try to remove decrypted replica file
try:
@@ -311,6 +380,37 @@ def common_cleanup(func):
return decorated
+def promote_sssd(host_name):
+ sssdconfig = SSSDConfig.SSSDConfig()
+ sssdconfig.import_config()
+ domains = sssdconfig.list_active_domains()
+
+ ipa_domain = None
+
+ for name in domains:
+ domain = sssdconfig.get_domain(name)
+ try:
+ hostname = domain.get_option('ipa_hostname')
+ if hostname == host_name:
+ ipa_domain = domain
+ except SSSDConfig.NoOptionError:
+ continue
+
+ if ipa_domain is None:
+ raise RuntimeError("Couldn't find IPA domain in sssd.conf")
+ else:
+ domain.set_option('ipa_server', host_name)
+ domain.set_option('ipa_server_mode', True)
+ sssdconfig.save_domain(domain)
+ sssdconfig.write()
+
+ sssd = services.service('sssd')
+ try:
+ sssd.restart()
+ except CalledProcessError:
+ root_logger.warning("SSSD service restart was unsuccessful.")
+
+
@common_cleanup
def install_check(installer):
options = installer
@@ -433,6 +533,14 @@ def install_check(installer):
# available
current = 0
+ if current != 0:
+ raise RuntimeError(
+ "You cannot use a replica file to join a replica when the "
+ "domain is above level 0. Please join the system to the "
+ "domain by running ipa-client-install first, the try again "
+ "without a replica file."
+ )
+
# Detect if current level is out of supported range
# for this IPA version
under_lower_bound = current < constants.MIN_DOMAIN_LEVEL
@@ -596,12 +704,9 @@ def install(installer):
CA.import_ra_cert(config.dir + "/ra.p12")
CA.fix_ra_perms()
- # FIXME: must be done earlier in replica to fetch keys for CA/ldap server
- # before they are configured
- custodia = custodiainstance.CustodiaInstance()
- custodia.create_instance('KEYS', config.host_name,
- config.dirman_password,
- ipautil.realm_to_suffix(config.realm_name))
+ custodia = custodiainstance.CustodiaInstance(config.host_name,
+ config.realm_name)
+ custodia.create_instance(config.dirman_password)
# The DS instance is created before the keytab, add the SSL cert we
# generated
@@ -662,6 +767,475 @@ def install(installer):
remove_replica_info_dir(installer)
+@common_cleanup
+def promote_check(installer):
+ options = installer
+
+ # FIXME: to implement yet
+ if options.setup_ca:
+ raise NotImplementedError
+ if options.setup_kra:
+ raise NotImplementedError
+ if options.setup_dns:
+ raise NotImplementedError
+
+ tasks.check_selinux_status()
+
+ client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+ if not client_fstore.has_files():
+ sys.exit("IPA client is not configured on this system.\n"
+ "You must use a replica file or join the system "
+ "using 'ipa-client-install'.")
+
+ sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+ fstore = sysrestore.FileStore(paths.SYSRESTORE)
+
+ # Check to see if httpd is already configured to listen on 443
+ if httpinstance.httpd_443_configured():
+ sys.exit("Aborting installation")
+
+ check_dirsrv()
+
+ if not options.no_ntp:
+ try:
+ ipaclient.ntpconf.check_timedate_services()
+ except ipaclient.ntpconf.NTPConflictingService, e:
+ print("WARNING: conflicting time&date synchronization service '%s'"
+ " will" % e.conflicting_service)
+ print("be disabled in favor of ntpd")
+ print("")
+ except ipaclient.ntpconf.NTPConfigurationError:
+ pass
+
+ api.bootstrap(context='installer')
+ api.finalize()
+
+ config = ReplicaConfig()
+ config.realm_name = api.env.realm
+ config.host_name = api.env.host
+ config.domain_name = api.env.domain
+ config.master_host_name = api.env.server
+ config.setup_ca = options.setup_ca
+ config.setup_kra = options.setup_kra
+
+ installutils.verify_fqdn(config.host_name, options.no_host_dns)
+ installutils.verify_fqdn(config.master_host_name, options.no_host_dns)
+
+ # Check if ccache is available
+ try:
+ root_logger.debug('KRB5CCNAME set to %s' %
+ os.environ.get('KRB5CCNAME', None))
+ # get default creds, will raise if none found
+ default_cred = gssapi.creds.Credentials()
+ principal = str(default_cred.name)
+ except gssapi.raw.misc.GSSError as e:
+ root_logger.debug('Failed to find default ccache: %s' % e)
+ principal = None
+
+ # Check if the principal matches the requested one (if any)
+ if principal is not None and options.principal is not None:
+ op = options.principal
+ if op.find('@') == -1:
+ op = '%s@%s' % (op, config.realm_name)
+ if principal != op:
+ root_logger.debug('Specified principal %s does not match '
+ 'available credentials (%s)' %
+ (options.principal, principal))
+ principal = None
+
+ if principal is None:
+ (ccache_fd, ccache_name) = tempfile.mkstemp()
+ os.close(ccache_fd)
+
+ if options.principal is not None:
+ principal = options.principal
+ else:
+ principal = 'admin'
+ stdin = None
+ if principal.find('@') == -1:
+ principal = '%s@%s' % (principal, config.realm_name)
+ if options.password is not None:
+ stdin = options.password
+ else:
+ if not options.unattended:
+ try:
+ stdin = getpass.getpass("Password for %s: " % principal)
+ except EOFError:
+ stdin = None
+ if not stdin:
+ raise RuntimeError("Password must be provided for %s."
+ % principal)
+ else:
+ if sys.stdin.isatty():
+ root_logger.info("Password must be provided in " +
+ "non-interactive mode. " +
+ "This can be done via " +
+ "echo password | ipa-client-install " +
+ "... or with the -w option.")
+ raise RuntimeError("Password must be provided in " +
+ "non-interactive mode.")
+ else:
+ stdin = sys.stdin.readline()
+
+ try:
+ ipautil.kinit_password(principal, stdin, ccache_name)
+ except RuntimeError as e:
+ raise RuntimeError("Kerberos authentication failed: %s" % e)
+
+ os.environ['KRB5CCNAME'] = ccache_name
+
+ cafile = paths.IPA_CA_CRT
+ if not ipautil.file_exists(cafile):
+ raise RuntimeError("CA cert file is not available! Please reinstall"
+ "the client and try again.")
+
+ ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
+ remote_api = create_api(mode=None)
+ remote_api.bootstrap(in_server=True, context='installer',
+ ldap_uri=ldapuri)
+ remote_api.finalize()
+ conn = remote_api.Backend.ldap2
+ replman = None
+ try:
+ # Try out authentication
+ conn.connect()
+ replman = ReplicationManager(config.realm_name,
+ config.master_host_name, None)
+
+ # Check that we don't already have a replication agreement
+ try:
+ (acn, adn) = replman.agreement_dn(config.host_name)
+ entry = conn.get_entry(adn, ['*'])
+ except errors.NotFound:
+ pass
+ else:
+ root_logger.info('Error: A replication agreement for this '
+ 'host already exists.')
+ print('A replication agreement for this host already exists. '
+ 'It needs to be removed.')
+ print("Run this command:")
+ print(" %% ipa-replica-manage del %s --force" %
+ config.host_name)
+ sys.exit(3)
+
+ # Detect the current domain level
+ try:
+ current = remote_api.Command['domainlevel_get']()['result']
+ except errors.NotFound:
+ # If we're joining an older master, domain entry is not
+ # available
+ current = 0
+
+ if current == 0:
+ raise RuntimeError(
+ "You must provide a file generated by ipa-replica-prepare to "
+ "create a replica when the domain is at level 0."
+ )
+
+ # Detect if current level is out of supported range
+ # for this IPA version
+ under_lower_bound = current < constants.MIN_DOMAIN_LEVEL
+ above_upper_bound = current > constants.MAX_DOMAIN_LEVEL
+
+ if under_lower_bound or above_upper_bound:
+ message = ("This version of FreeIPA does not support "
+ "the Domain Level which is currently set for "
+ "this domain. The Domain Level needs to be "
+ "raised before installing a replica with "
+ "this version is allowed to be installed "
+ "within this domain.")
+ root_logger.error(message)
+ sys.exit(3)
+
+ # Detect if the other master can handle replication managers
+ # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX
+ dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
+ ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name))
+ try:
+ entry = conn.get_entry(dn)
+ except errors.NotFound:
+ msg = ("The Replication Managers group is not available in "
+ "the domain. Replica promotion requires the use of "
+ "Replication Managers to be able to replicate data. "
+ "Upgrade the peer master or use the ipa-replica-prepare "
+ "command on the master and use a prep file to install "
+ "this replica.")
+ root_logger.error(msg)
+ sys.exit(3)
+
+ dns_masters = remote_api.Object['dnsrecord'].get_dns_masters()
+ if dns_masters:
+ if not options.no_host_dns:
+ root_logger.debug('Check forward/reverse DNS resolution')
+ resolution_ok = (
+ check_dns_resolution(config.master_host_name,
+ dns_masters) and
+ check_dns_resolution(config.host_name, dns_masters))
+ if not resolution_ok and installer.interactive:
+ if not ipautil.user_input("Continue?", False):
+ sys.exit(0)
+ else:
+ root_logger.debug('No IPA DNS servers, '
+ 'skipping forward/reverse resolution check')
+
+ entry_attrs = conn.get_ipa_config()
+ subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0]
+ if subject_base is not None:
+ config.subject_base = DN(subject_base)
+
+ # Find if any server has a CA
+ ca_host = cainstance.find_ca_server(api.env.server, conn)
+ if ca_host is not None:
+ config.ca_host_name = ca_host
+ ca_enabled = True
+ else:
+ # FIXME: add way to pass in certificates
+ root_logger.error("The remote master does not have a CA "
+ "installed, can't proceed without certs")
+ sys.exit(3)
+
+ if options.setup_ca:
+ if not ca_enabled:
+ root_logger.error("The remote master does not have a CA "
+ "installed, can't set up CA")
+ sys.exit(3)
+
+ options.realm_name = config.realm_name
+ options.host_name = config.host_name
+ options.subject = config.subject_base
+ ca.install_check(False, None, options)
+
+ if config.setup_kra:
+ try:
+ kra.install_check(remote_api, config, options)
+ except RuntimeError as e:
+ print(str(e))
+ sys.exit(1)
+ except errors.ACIError:
+ sys.exit("\nInsufficiently privileges to promote the server.")
+ except errors.LDAPError:
+ sys.exit("\nUnable to connect to LDAP server %s" %
+ config.master_host_name)
+ finally:
+ if replman and replman.conn:
+ replman.conn.unbind()
+ if conn.isconnected():
+ conn.disconnect()
+
+ if options.setup_dns:
+ dns.install_check(False, True, options, config.host_name)
+ else:
+ config.ips = installutils.get_server_ip_address(
+ config.host_name, not installer.interactive,
+ False, options.ip_addresses)
+
+ # check connection
+ if not options.skip_conncheck:
+ replica_conn_check(
+ config.master_host_name, config.host_name, config.realm_name,
+ options.setup_ca, dogtag.Dogtag10Constants.DS_PORT)
+
+ if not ipautil.file_exists(cafile):
+ raise RuntimeError("CA cert file is not available.")
+
+ installer._ca_enabled = ca_enabled
+ installer._remote_api = remote_api
+ installer._fstore = fstore
+ installer._sstore = sstore
+ installer._config = config
+
+
+@common_cleanup
+def promote(installer):
+ options = installer
+ fstore = installer._fstore
+ sstore = installer._sstore
+ config = installer._config
+
+ # Save client file and merge in server directives
+ target_fname = paths.IPA_DEFAULT_CONF
+ fstore.backup_file(target_fname)
+ ipaconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Replica Promote")
+ ipaconf.setOptionAssignment(" = ")
+ ipaconf.setSectionNameDelimiters(("[", "]"))
+
+ config.promote = installer.promote
+ config.dirman_password = hexlify(ipautil.ipa_generate_password())
+
+ dogtag_constants = dogtag.install_constants
+
+ # FIXME: allow to use passed in certs instead
+ if installer._ca_enabled:
+ configure_certmonger()
+
+ # Create DS user/group if it doesn't exist yet
+ dsinstance.create_ds_user()
+
+ # Configure ntpd
+ if not options.no_ntp:
+ ipaclient.ntpconf.force_ntpd(sstore)
+ ntp = ntpinstance.NTPInstance()
+ ntp.create_instance()
+
+ # Configure dirsrv
+ ds = install_replica_ds(config, promote=True)
+
+ # Always try to install DNS records
+ install_dns_records(config, options, api)
+
+ # Must install http certs before changing ipa configuration file
+ # or certmonger will fail to contact the peer master
+ install_http_certs(config, fstore)
+
+ # Create the management framework config file
+ gopts = [
+ ipaconf.setOption('host', config.host_name),
+ ipaconf.rmOption('server'),
+ ipaconf.setOption('xmlrpc_uri',
+ 'https://%s/ipa/xml' %
+ ipautil.format_netloc(config.host_name)),
+ ipaconf.setOption('ldap_uri',
+ installutils.realm_to_ldapi_uri(config.realm_name)),
+ ipaconf.setOption('mode', 'production'),
+ ipaconf.setOption('enable_ra', 'True'),
+ ipaconf.setOption('ra_plugin', 'dogtag'),
+ ipaconf.setOption('dogtag_version',
+ dogtag.install_constants.DOGTAG_VERSION)]
+ opts = [ipaconf.setSection('global', gopts)]
+
+ ipaconf.changeConf(target_fname, opts)
+ os.chmod(target_fname, 0o644) # must be readable for httpd
+
+ custodia = custodiainstance.CustodiaInstance(config.host_name,
+ config.realm_name)
+ custodia.create_replica(config.master_host_name)
+
+ if config.setup_ca:
+ options.realm_name = config.realm_name
+ options.domain_name = config.domain_name
+ options.host_name = config.host_name
+ options.dm_password = config.dirman_password
+
+ ca.install(False, config, options)
+
+ krb = install_krb(config,
+ setup_pkinit=not options.no_pkinit,
+ promote=True)
+
+ http = install_http(config,
+ auto_redirect=not options.no_ui_redirect,
+ promote=True)
+
+ otpd = otpdinstance.OtpdInstance()
+ otpd.create_instance('OTPD', config.host_name, config.dirman_password,
+ ipautil.realm_to_suffix(config.realm_name))
+
+ CA = cainstance.CAInstance(
+ config.realm_name, certs.NSS_DIR,
+ dogtag_constants=dogtag_constants)
+ CA.dm_password = config.dirman_password
+ CA.configure_certmonger_renewal()
+ CA.fix_ra_perms()
+
+ # Apply any LDAP updates. Needs to be done after the replica is synced-up
+ service.print_msg("Applying LDAP updates")
+ ds.apply_updates()
+
+ if options.setup_kra:
+ kra.install(api, config, options)
+ else:
+ service.print_msg("Restarting the directory server")
+ ds.restart()
+
+ service.print_msg("Restarting the KDC")
+ krb.restart()
+
+ if config.setup_ca:
+ dogtag_service = services.knownservices[dogtag_constants.SERVICE_NAME]
+ dogtag_service.restart(dogtag_constants.PKI_INSTANCE_NAME)
+
+ if options.setup_dns:
+ api.Backend.ldap2.connect(autobind=True)
+ dns.install(False, True, options)
+
+ # Restart httpd to pick up the new IPA configuration
+ service.print_msg("Restarting the web server")
+ http.restart()
+
+ ds.replica_populate()
+
+ custodia.import_dm_password(config.master_host_name)
+
+ promote_sssd(config.host_name)
+
+ # Everything installed properly, activate ipa service.
+ services.knownservices.ipa.enable()
+
+
+class ReplicaCA(common.Installable, core.Group, core.Composite):
+ description = "certificate system"
+
+ no_pkinit = Knob(
+ bool, False,
+ description="disables pkinit setup steps",
+ )
+
+ skip_schema_check = Knob(
+ bool, False,
+ description="skip check for updated CA DS schema on the remote master",
+ )
+
+
+class ReplicaDNS(common.Installable, core.Group, core.Composite):
+ description = "DNS"
+
+ setup_dns = Knob(
+ bool, False,
+ description="configure bind with our zone",
+ )
+
+ forwarders = Knob(
+ (list, 'ip'), None,
+ description=("Add a DNS forwarder. This option can be used multiple "
+ "times"),
+ cli_name='forwarder',
+ )
+
+ no_forwarders = Knob(
+ bool, False,
+ description="Do not add any DNS forwarders, use root servers instead",
+ )
+
+ reverse_zones = Knob(
+ (list, str), [],
+ description=("The reverse DNS zone to use. This option can be used "
+ "multiple times"),
+ cli_name='reverse-zone',
+ cli_metavar='REVERSE_ZONE',
+ )
+
+ no_reverse = Knob(
+ bool, False,
+ description="Do not create new reverse DNS zone",
+ )
+
+ no_dnssec_validation = Knob(
+ bool, False,
+ description="Disable DNSSEC validation",
+ )
+
+ no_host_dns = Knob(
+ bool, False,
+ description="Do not use DNS for hostname lookup during installation",
+ )
+
+ no_dns_sshfp = Knob(
+ bool, False,
+ description="do not automatically create DNS SSHFP records",
+ )
+
+
class Replica(BaseServer):
replica_file = Knob(
str, None,
@@ -710,6 +1284,15 @@ class Replica(BaseServer):
description="skip connection check to remote master",
)
+ principal = Knob(
+ str, None,
+ sensitive=True,
+ description="User Principal allowed to promote replicas",
+ cli_short_name='P',
+ )
+
+ promote = False
+
# ca
external_ca = None
external_ca_type = None
@@ -742,11 +1325,11 @@ class Replica(BaseServer):
self._update_hosts_file = False
if self.replica_file is None:
- raise RuntimeError(
- "you must provide a file generated by ipa-replica-prepare")
- if not ipautil.file_exists(self.replica_file):
- raise RuntimeError(
- "Replica file %s does not exist" % self.replica_file)
+ self.promote = True
+ else:
+ if not ipautil.file_exists(self.replica_file):
+ raise RuntimeError("Replica file %s does not exist"
+ % self.replica_file)
if self.setup_dns:
#pylint: disable=no-member
@@ -759,6 +1342,12 @@ class Replica(BaseServer):
@step()
def main(self):
- install_check(self)
- yield
- install(self)
+ if self.promote:
+ promote_check(self)
+ yield
+ promote(self)
+ else:
+ with ipautil.private_ccache():
+ install_check(self)
+ yield
+ install(self)
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 42c9cf0f5..3cafb4e3c 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1515,8 +1515,8 @@ def upgrade_configuration():
except ipautil.CalledProcessError as e:
root_logger.error("Failed to restart %s: %s", bind.service_name, e)
- custodia = custodiainstance.CustodiaInstance()
- custodia.upgrade_instance(api.env.realm)
+ custodia = custodiainstance.CustodiaInstance(api.env.host, api.env.realm)
+ custodia.upgrade_instance()
ca_restart = any([
ca_restart,