replica install: add remote connection check over API

Add server_conncheck command which calls ipa-replica-conncheck --replica
over oddjob.

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

Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
Reviewed-By: Tomas Babej <tbabej@redhat.com>
This commit is contained in:
Jan Cholasta 2015-12-09 08:18:21 +01:00 committed by Tomas Babej
parent 00f591d4e9
commit 8d7f67e08c
20 changed files with 299 additions and 77 deletions

View File

@ -3812,6 +3812,14 @@ option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: server_conncheck
args: 2,1,3
arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
arg: Str('remote_cn', cli_name='remote_name')
option: Str('version?', exclude='webui')
output: Output('result', <type 'bool'>, None)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: server_del
args: 1,2,3
arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True)

View File

@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=161
# Last change: pvoborni - topologysuffix: change iparepltopomanagedsuffix type
IPA_API_VERSION_MINOR=162
# Last change: jcholast - replica install: add remote connection check over API

View File

@ -162,6 +162,7 @@ Requires: p11-kit
Requires: systemd-python
Requires: %{etc_systemd_dir}
Requires: gzip
Requires: oddjob
Provides: %{alt_name}-server = %{version}
Conflicts: %{alt_name}-server
@ -275,7 +276,6 @@ Requires: samba >= %{samba_version}
Requires: samba-winbind
Requires: libsss_idmap
Requires: python-libsss_nss_idmap
Requires: oddjob
Requires: python-sss
# We use alternatives to divert winbind_krb5_locator.so plugin to libkrb5
# on the installes where server-trust-ad subpackage is installed because
@ -727,6 +727,8 @@ rm -rf %{buildroot}
if [ $1 -gt 1 ] ; then
/bin/systemctl condrestart certmonger.service 2>&1 || :
fi
/bin/systemctl reload-or-try-restart dbus
/bin/systemctl reload-or-try-restart oddjobd
%posttrans server
@ -752,6 +754,8 @@ if [ $1 = 0 ]; then
# NOTE: systemd specific section
/bin/systemctl --quiet stop ipa.service || :
/bin/systemctl --quiet disable ipa.service || :
/bin/systemctl reload-or-try-restart dbus
/bin/systemctl reload-or-try-restart oddjobd
# END
fi
@ -909,6 +913,9 @@ fi
%{_libexecdir}/ipa/ipa-ods-exporter
%{_libexecdir}/ipa/ipa-httpd-kdcproxy
%dir %{_libexecdir}/ipa/oddjob
%attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.conncheck
%config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freeipa.server.conf
%config(noreplace) %{_sysconfdir}/oddjobd.conf.d/ipa-server.conf
%dir %{_libdir}/ipa/certmonger
%attr(755,root,root) %{_libdir}/ipa/certmonger/*
# NOTE: systemd specific section

View File

@ -6,14 +6,17 @@ dbusconfdir = $(sysconfdir)/dbus-1/system.d
oddjob_SCRIPTS = \
com.redhat.idm.trust-fetch-domains \
org.freeipa.server.conncheck \
$(NULL)
dbusconf_DATA = \
etc/dbus-1/system.d/oddjob-ipa-trust.conf \
etc/dbus-1/system.d/org.freeipa.server.conf \
$(NULL)
oddjobconf_DATA = \
etc/oddjobd.conf.d/oddjobd-ipa-trust.conf \
etc/oddjobd.conf.d/ipa-server.conf \
$(NULL)

View File

@ -0,0 +1,21 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.freeipa.server"/>
<allow send_destination="org.freeipa.server" send_interface="org.freeipa.server"/>
</policy>
<policy user="apache">
<allow send_destination="org.freeipa.server" send_interface="org.freeipa.server"/>
</policy>
<policy context="default">
<allow send_destination="org.freeipa.server" send_interface="org.freedesktop.DBus.Introspectable"/>
</policy>
</busconfig>

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<oddjobconfig>
<service name="org.freeipa.server">
<allow user="root"/>
<allow user="apache"/>
<object name="/">
<interface name="org.freeipa.server">
<method name="conncheck">
<helper exec="/usr/libexec/ipa/oddjob/org.freeipa.server.conncheck"
arguments="1"
prepend_user_name="no"
argument_passing_method="cmdline"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Introspectable">
<allow min_uid="0" max_uid="0"/>
</interface>
</object>
</service>
</oddjobconfig>

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/sbin/ipa-replica-conncheck --replica "$1" 2>&1

View File

@ -149,8 +149,10 @@ def install_replica(safe_options, options, filename):
config.ca_ds_port = 389
config.top_dir = tempfile.mkdtemp("ipa")
config.dir = config.top_dir
cafile = paths.IPA_CA_CRT
else:
config = create_replica_config(dirman_password, filename, options)
cafile = config.dir + '/ca.crt'
global REPLICA_INFO_TOP_DIR
REPLICA_INFO_TOP_DIR = config.top_dir
@ -176,6 +178,10 @@ def install_replica(safe_options, options, filename):
options.dm_password = config.dirman_password
options.host_name = config.host_name
options.subject = config.subject_base
if os.path.exists(cafile):
options.ca_cert_file = cafile
else:
options.ca_cert_file = None
ca.install_check(True, config, options)
if options.promote:

View File

@ -22,11 +22,12 @@ from __future__ import print_function
from ipapython.config import IPAOptionParser
from ipapython import version
from ipapython import ipautil
from ipapython import ipautil, certdb
from ipapython.ipautil import CalledProcessError
from ipalib import api, errors, x509
from ipaserver.install import installutils
import ipaclient.ipachangeconf
from optparse import OptionGroup
from optparse import OptionGroup, OptionValueError
from ipapython.ipa_log_manager import *
import sys
import os
@ -40,6 +41,7 @@ from socket import SOCK_STREAM, SOCK_DGRAM
import distutils.spawn
from ipaplatform.paths import paths
import gssapi
from nss import nss
CONNECT_TIMEOUT = 5
RESPONDERS = [ ]
@ -106,6 +108,30 @@ def print_info(msg):
print(msg)
def parse_options():
def ca_cert_file_callback(option, opt, value, parser):
if not os.path.exists(value):
raise OptionValueError(
"%s option '%s' does not exist" % (opt, value))
if not os.path.isfile(value):
raise OptionValueError(
"%s option '%s' is not a file" % (opt, value))
if not os.path.isabs(value):
raise OptionValueError(
"%s option '%s' is not an absolute file path" % (opt, value))
initialized = nss.nss_is_initialized()
try:
x509.load_certificate_list_from_file(value)
except Exception:
raise OptionValueError(
"%s option '%s' is not a valid certificate file" %
(opt, value))
finally:
if not initialized:
nss.nss_shutdown()
parser.values.ca_cert_file = value
parser = IPAOptionParser(version=version.VERSION)
replica_group = OptionGroup(parser, "on-replica options")
@ -123,6 +149,10 @@ def parse_options():
default=None, help="Principal to use to log in to remote master")
replica_group.add_option("-w", "--password", dest="password", sensitive=True,
help="Password for the principal"),
replica_group.add_option("--ca-cert-file", dest="ca_cert_file",
type="string", action="callback",
callback=ca_cert_file_callback,
help="load the CA certificate from this file")
parser.add_option_group(replica_group)
@ -418,22 +448,89 @@ def main():
if returncode != 0:
raise RuntimeError("Could not get ticket for master server: %s" % stderr)
user = principal.partition('@')[0]
ssh = SshExec(user, options.master)
try:
print_info("Check RPC connection to remote master")
print_info("Check SSH connection to remote master")
stdout, stderr, returncode = ssh('echo OK', verbose=True)
if returncode != 0:
print('Could not SSH into remote host. Error output:')
for line in stderr.splitlines():
print(' %s' % line)
raise RuntimeError('Could not SSH to remote host.')
xmlrpc_uri = ('https://%s/ipa/xml' %
ipautil.format_netloc(options.master))
api.bootstrap(context='client', xmlrpc_uri=xmlrpc_uri)
api.finalize()
print_info("Execute check on remote master")
stdout, stderr, returncode = ssh(
"/usr/sbin/ipa-replica-conncheck " +
" ".join(remote_check_opts))
print_info(stdout)
if options.ca_cert_file:
nss_dir = None
else:
nss_dir = paths.IPA_NSSDB_DIR
with certdb.NSSDatabase(nss_dir) as nss_db:
if options.ca_cert_file:
nss_dir = nss_db.secdir
password = ipautil.ipa_generate_password()
password_file = ipautil.write_tmp_file(password)
nss_db.create_db(password_file.name)
ca_certs = x509.load_certificate_list_from_file(
options.ca_cert_file, dbdir=nss_db.secdir)
for ca_cert in ca_certs:
nss_db.add_cert(
ca_cert.der_data, str(ca_cert.subject), 'C,,')
del ca_cert
del ca_certs
else:
nss_dir = None
try:
api.Backend.rpcclient.connect(nss_dir=nss_dir)
api.Command.ping()
except Exception as e:
print_info(
"Could not connect to the remote host: %s" % e)
raise
print_info("Execute check on remote master")
try:
result = api.Backend.rpcclient.forward(
'server_conncheck',
ipautil.fsdecode(options.master),
ipautil.fsdecode(options.hostname),
version=u'2.162',
)
except (errors.CommandError, errors.NetworkError) as e:
print_info(
"Remote master does not support check over RPC: "
"%s" % e)
raise
except errors.PublicError as e:
returncode = 1
stderr = e
else:
for message in result['messages']:
print_info(message['message'])
returncode = int(not result['result'])
stderr = ("ipa-replica-conncheck returned non-zero "
"exit code")
finally:
if api.Backend.rpcclient.isconnected():
api.Backend.rpcclient.disconnect()
except Exception:
print_info("Retrying using SSH...")
user = principal.partition('@')[0]
ssh = SshExec(user, options.master)
print_info("Check SSH connection to remote master")
stdout, stderr, returncode = ssh('echo OK', verbose=True)
if returncode != 0:
print('Could not SSH into remote host. Error output:')
for line in stderr.splitlines():
print(' %s' % line)
raise RuntimeError('Could not SSH to remote host.')
print_info("Execute check on remote master")
stdout, stderr, returncode = ssh(
"/usr/sbin/ipa-replica-conncheck " +
" ".join(remote_check_opts))
print_info(stdout)
if returncode != 0:
raise RuntimeError("Remote master check failed with following error message(s):\n%s" % stderr)
else:

View File

@ -19,4 +19,3 @@ plugin: update_managed_post
plugin: update_managed_permissions
plugin: update_idrange_baserid
plugin: update_passync_privilege_update
plugin: update_oddjobd_for_adtrust

View File

@ -321,6 +321,16 @@ class CommandDeprecatedWarning(PublicMessage):
format = _(u"'%(command)s' is deprecated. %(additional_info)s")
class ExternalCommandOutput(PublicMessage):
"""
**13016** Line of output from an external command.
"""
errno = 13016
type = "info"
format = _("%(line)s")
def iter_messages(variables, base):
"""Return a tuple with all subclasses
"""

View File

@ -5,11 +5,15 @@
import string
import os
from ipalib import api
import dbus
import dbus.mainloop.glib
from ipalib import api, crud, errors, messages
from ipalib import Int, Str
from ipalib.plugable import Registry
from ipalib.plugins.baseldap import *
from ipalib.plugins import baseldap
from ipalib.request import context
from ipalib import _, ngettext
__doc__ = _("""
@ -188,3 +192,67 @@ class server_del(LDAPDelete):
__doc__ = _('Delete IPA server.')
NO_CLI = True
msg_summary = _('Deleted IPA server "%(value)s"')
@register()
class server_conncheck(crud.PKQuery):
__doc__ = _("Check connection to remote IPA server.")
NO_CLI = True
takes_args = (
Str(
'remote_cn',
cli_name='remote_name',
label=_('Remote server name'),
doc=_('Remote IPA server hostname'),
),
)
has_output = output.standard_value
def execute(self, *keys, **options):
# the server must be the local host
if keys[-2] != api.env.host:
raise errors.ValidationError(
name='cn', error=_("must be \"%s\"") % api.env.host)
# the server entry must exist
try:
self.obj.get_dn_if_exists(*keys[:-1])
except errors.NotFound:
self.obj.handle_not_found(keys[-2])
# the user must have the Replication Administrators privilege
privilege = u'Replication Administrators'
privilege_dn = self.api.Object.privilege.get_dn(privilege)
ldap = self.obj.backend
filter = ldap.make_filter(
{'krbprincipalname': context.principal, 'memberof': privilege_dn},
rules=ldap.MATCH_ALL)
try:
ldap.find_entries(base_dn=self.api.env.basedn, filter=filter)
except errors.NotFound:
raise errors.ACIError(
info=_("not allowed to perform server connection check"))
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
obj = bus.get_object('org.freeipa.server', '/',
follow_name_owner_changes=True)
server = dbus.Interface(obj, 'org.freeipa.server')
ret, stdout, stderr = server.conncheck(keys[-1])
result = dict(
result=(ret == 0),
value=keys[-2],
)
for line in stdout.splitlines():
messages.add_message(options['version'],
result,
messages.ExternalCommandOutput(line=line))
return result

View File

@ -726,9 +726,6 @@ class ADTRUSTInstance(service.Service):
except Exception as e:
root_logger.critical("Enabling nsswitch support in slapi-nis failed with error '%s'" % e)
def __enable_and_start_oddjobd(self):
installutils.enable_and_start_oddjobd(self.sstore)
def __start(self):
try:
self.start()
@ -881,7 +878,6 @@ class ADTRUSTInstance(service.Service):
self.step("adding Default Trust View", self.__add_default_trust_view)
self.step("setting SELinux booleans", \
self.__configure_selinux_for_smbd)
self.step("enabling oddjobd", self.__enable_and_start_oddjobd)
self.step("starting CIFS services", self.__start)
if self.add_sids:
@ -911,21 +907,6 @@ class ADTRUSTInstance(service.Service):
except Exception:
pass
# Restore oddjobd to its original state
oddjobd = services.service('oddjobd')
if not self.sstore.restore_state('oddjobd', 'running'):
try:
oddjobd.stop()
except Exception:
pass
if not self.sstore.restore_state('oddjobd', 'enabled'):
try:
oddjobd.disable()
except Exception:
pass
# Since we do not guarantee restoring back to working samba state,
# we should not restore smb.conf

View File

@ -40,7 +40,7 @@ def install_check(standalone, replica_config, options):
replica_conn_check(
replica_config.master_host_name, host_name, realm_name, True,
replica_config.ca_ds_port, options.admin_password,
principal=principal)
principal=principal, ca_cert_file=options.ca_cert_file)
if options.skip_schema_check or options.promote:
root_logger.info("Skipping CA DS schema check")

View File

@ -163,6 +163,7 @@ class HTTPInstance(service.Service):
self.step("enable KDC proxy", self.enable_kdcproxy)
self.step("restarting httpd", self.__start)
self.step("configuring httpd to start on boot", self.__enable)
self.step("enabling oddjobd", self.enable_and_start_oddjobd)
self.start_creation(runtime=60)
@ -441,6 +442,17 @@ class HTTPInstance(service.Service):
f.write(http_txt)
os.chmod(target_fname, 0o644)
def enable_and_start_oddjobd(self):
oddjobd = services.service('oddjobd')
self.sstore.backup_state('oddjobd', 'running', oddjobd.is_running())
self.sstore.backup_state('oddjobd', 'enabled', oddjobd.is_enabled())
try:
oddjobd.enable()
oddjobd.start()
except Exception as e:
root_logger.critical("Unable to start oddjobd: {0}".format(str(e)))
def uninstall(self):
if self.is_configured():
self.print_msg("Unconfiguring web server")
@ -448,6 +460,20 @@ class HTTPInstance(service.Service):
running = self.restore_state("running")
enabled = self.restore_state("enabled")
# Restore oddjobd to its original state
oddjobd = services.service('oddjobd')
if not self.sstore.restore_state('oddjobd', 'running'):
try:
oddjobd.stop()
except Exception:
pass
if not self.sstore.restore_state('oddjobd', 'enabled'):
try:
oddjobd.disable()
except Exception:
pass
self.stop_tracking_certificates()

View File

@ -1103,18 +1103,6 @@ def realm_to_ldapi_uri(realm_name):
return 'ldapi://' + ldapurl.ldapUrlEscape(socketname)
def enable_and_start_oddjobd(sstore):
oddjobd = services.service('oddjobd')
sstore.backup_state('oddjobd', 'running', oddjobd.is_running())
sstore.backup_state('oddjobd', 'enabled', oddjobd.is_enabled())
try:
oddjobd.enable()
oddjobd.start()
except Exception as e:
root_logger.critical("Unable to start oddjobd: {0}".format(str(e)))
def install_service_keytab(principal, server, path, force_service_add=False):
try:

View File

@ -164,26 +164,5 @@ class update_default_trust_view(Updater):
return False, [update]
class update_oddjobd_for_adtrust(Updater):
"""
Enables and starts oddjobd daemon if ipa-adtrust-install has been run
on this system.
"""
def execute(self, **options):
adtrust_is_enabled = self.api.Command['adtrust_is_enabled']()['result']
if adtrust_is_enabled:
self.log.debug('Try to enable and start oddjobd')
sstore = sysrestore.StateFile(paths.SYSRESTORE)
installutils.enable_and_start_oddjobd(sstore)
else:
self.log.debug('ADTrust not configured on this server, do not '
'start and enable oddjobd')
return False, []
api.register(update_default_range)
api.register(update_default_trust_view)
api.register(update_oddjobd_for_adtrust)

View File

@ -67,7 +67,7 @@ STRIP_ATTRS = ('modifiersName',
def replica_conn_check(master_host, host_name, realm, check_ca,
dogtag_master_ds_port, admin_password=None,
principal="admin"):
principal="admin", ca_cert_file=None):
"""
Check the ports used by the replica both locally and remotely to be sure
that replication will work.
@ -89,6 +89,10 @@ def replica_conn_check(master_host, host_name, realm, check_ca,
if check_ca and dogtag_master_ds_port == 7389:
args.append('--check-ca')
if ca_cert_file:
args.extend(["--ca-cert-file", ca_cert_file])
(stdin, stderr, returncode) = ipautil.run(
args, raiseonerr=False, capture_output=False, nolog=nolog)

View File

@ -668,7 +668,8 @@ def install_check(installer):
if not options.skip_conncheck:
replica_conn_check(
config.master_host_name, config.host_name, config.realm_name,
options.setup_ca, config.ca_ds_port, options.admin_password)
options.setup_ca, config.ca_ds_port, options.admin_password,
ca_cert_file=cafile)
installer._remote_api = remote_api
installer._fstore = fstore
@ -1206,7 +1207,8 @@ def promote_check(installer):
replica_conn_check(
config.master_host_name, config.host_name, config.realm_name,
options.setup_ca, 389,
options.admin_password, principal=options.principal)
options.admin_password, principal=options.principal,
ca_cert_file=cafile)
finally:
os.environ['KRB5CCNAME'] = ccache

View File

@ -1478,6 +1478,7 @@ def upgrade_configuration():
http.configure_selinux_for_httpd()
http.change_mod_nss_port_from_http()
http.configure_certmonger_renewal_guard()
http.enable_and_start_oddjobd()
ds.configure_dirsrv_ccache()