ipa-adtrust-install: run remote configuration for new agents

When ipa-adtrust-install is run, the tool detects masters that are
not enabled as trust agents and propose to configure them. With the
current code, the Schema Compat plugin is not enabled on these new
trust agents and a manual restart of LDAP server + SSSD is required.

With this commit, ipa-adtrust-install now calls remote code on the new
agents through JSON RPC api, in order to configure the missing parts.
On the remote agent, the command is using DBus and oddjob to launch
a new command,
/usr/libexec/ipa/oddjob/org.freeipa.server.trust-enable-agent [--enable-compat]
This command configures the Schema Compat plugin if --enable-compat is
provided, then restarts LDAP server and SSSD.

If the remote agent is an older version and does not support remote
enablement, or if the remote server is not responding, the tool
ipa-adtrust-install prints a WARNING explaining the steps that need
to be manually executed in order to complete the installation, and
exits successfully (keeping the current behavior).

Fixes: https://pagure.io/freeipa/issue/7600
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Sergey Orlov <sorlov@redhat.com>
This commit is contained in:
Florence Blanc-Renaud
2020-02-18 16:24:32 +01:00
parent 68c72e344a
commit 911992b8bf
10 changed files with 249 additions and 5 deletions

View File

@@ -5857,6 +5857,14 @@ option: Str('version?')
output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value')
command: trust_enable_agent/1
args: 1,2,3
arg: Str('remote_cn', cli_name='remote_name')
option: Flag('enable_compat', autofill=True, default=False)
option: Str('version?')
output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: trust_fetch_domains/1
args: 1,7,4
arg: Str('cn', cli_name='realm')
@@ -7124,6 +7132,7 @@ default: topologysuffix_verify/1
default: trust/1
default: trust_add/1
default: trust_del/1
default: trust_enable_agent/1
default: trust_fetch_domains/1
default: trust_find/1
default: trust_mod/1

View File

@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
# #
########################################################
define(IPA_API_VERSION_MAJOR, 2)
define(IPA_API_VERSION_MINOR, 235)
# Last change: Add memberManager to groups.
define(IPA_API_VERSION_MINOR, 236)
# Last change: Add trust_enable_agent.
########################################################
# Following values are auto-generated from values above

View File

@@ -1123,6 +1123,7 @@ fi
%{_libexecdir}/ipa/ipa-otpd
%dir %{_libexecdir}/ipa/oddjob
%attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.conncheck
%attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.trust-enable-agent
%config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freeipa.server.conf
%config(noreplace) %{_sysconfdir}/oddjobd.conf.d/ipa-server.conf
%dir %{_libexecdir}/ipa/certmonger

View File

@@ -6,6 +6,7 @@ dbusconfdir = $(sysconfdir)/dbus-1/system.d
dist_noinst_DATA = \
com.redhat.idm.trust-fetch-domains.in \
org.freeipa.server.trust-enable-agent.in \
etc/oddjobd.conf.d/oddjobd-ipa-trust.conf.in \
etc/oddjobd.conf.d/ipa-server.conf.in \
$(NULL)
@@ -16,6 +17,7 @@ dist_oddjob_SCRIPTS = \
nodist_oddjob_SCRIPTS = \
com.redhat.idm.trust-fetch-domains \
org.freeipa.server.trust-enable-agent \
$(NULL)

View File

@@ -11,6 +11,12 @@
prepend_user_name="no"
argument_passing_method="cmdline"/>
</method>
<method name="trust_enable_agent">
<helper exec="@ODDJOBDIR@/org.freeipa.server.trust-enable-agent"
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"/>

View File

@@ -0,0 +1,8 @@
#!/usr/bin/python3
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
from ipaserver.install.ipa_trust_enable_agent import IPATrustEnableAgent
IPATrustEnableAgent.run_cli()

View File

@@ -334,6 +334,7 @@ class BasePathNamespace:
IPASERVER_KRA_INSTALL_LOG = "/var/log/ipaserver-kra-install.log"
IPASERVER_UNINSTALL_LOG = "/var/log/ipaserver-uninstall.log"
IPAUPGRADE_LOG = "/var/log/ipaupgrade.log"
IPATRUSTENABLEAGENT_LOG = "/var/log/ipatrust-enable-agent.log"
KADMIND_LOG = "/var/log/kadmind.log"
KRB5KDC_LOG = "/var/log/krb5kdc.log"
MESSAGES = "/var/log/messages"

View File

@@ -14,6 +14,7 @@ import os
import six
from ipalib.constants import MIN_DOMAIN_LEVEL
from ipalib import create_api, rpc
from ipalib import errors
from ipalib.install.service import ServiceAdminInstallInterface
from ipalib.install.service import replica_install_only
@@ -344,13 +345,68 @@ def add_new_adtrust_agents(api, options):
if new_agents:
add_hosts_to_adtrust_agents(api, new_agents)
print("""
# The method trust_enable_agent was added on API version 2.236
# Specifically request this version in the remote call
kwargs = {u'version': u'2.236',
u'enable_compat': options.enable_compat}
failed_agents = []
for agent in new_agents:
# Try to run the ipa-trust-enable-agent script on the agent
# If the agent is too old and does not support this,
# print a msg
logger.info("Execute trust_enable_agent on remote server %s",
agent)
client = None
try:
xmlrpc_uri = 'https://{}/ipa/xml'.format(
ipautil.format_netloc(agent))
remote_api = create_api(mode=None)
remote_api.bootstrap(context='installer',
confdir=paths.ETC_IPA,
xmlrpc_uri=xmlrpc_uri,
fallback=False)
client = rpc.jsonclient(remote_api)
client.finalize()
client.connect()
result = client.forward(
u'trust_enable_agent',
ipautil.fsdecode(agent),
**kwargs)
except errors.CommandError as e:
logger.debug(
"Remote server %s does not support agent enablement "
"over RPC: %s", agent, e)
failed_agents.append(agent)
except (errors.PublicError, ConnectionRefusedError) as e:
logger.debug(
"Remote call to trust_enable_agent failed on server %s: "
"%s", agent, e)
failed_agents.append(agent)
else:
for message in result.get('messages'):
logger.debug('%s', message['message'])
if not int(result['result']):
logger.debug(
"ipa-trust-enable-agent returned non-zero exit code "
" on server %s", agent)
failed_agents.append(agent)
finally:
if client and client.isconnected():
client.disconnect()
# if enablement failed on some agents, print a WARNING:
if failed_agents:
if options.enable_compat:
print("""
WARNING: you MUST manually enable the Schema compatibility Plugin and """)
print("""
WARNING: you MUST restart (both "ipactl restart" and "systemctl restart sssd")
the following IPA masters in order to activate them to serve information about
users from trusted forests:
""")
for x in new_agents:
print(x)
for x in failed_agents:
print(x)
def install_check(standalone, options, api):

View File

@@ -0,0 +1,93 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
from __future__ import print_function, absolute_import
import logging
from ipalib import api
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython.admintool import AdminTool, ScriptError
from ipapython.dn import DN
from ipapython.ipautil import CalledProcessError
from ipaserver.install import installutils
logger = logging.getLogger(__name__)
class IPATrustEnableAgent(AdminTool):
command_name = "ipa-trust-enable-agent"
log_file_name = paths.IPATRUSTENABLEAGENT_LOG
usage = "%prog"
description = "Enable this server as a trust agent"
@classmethod
def add_options(cls, parser):
super(IPATrustEnableAgent, cls).add_options(parser)
parser.add_option(
"--enable-compat",
dest="enable_compat", default=False, action="store_true",
help="Enable support for trusted domains for old clients")
def validate_options(self):
super(IPATrustEnableAgent, self).validate_options(needs_root=True)
installutils.check_server_configuration()
def _enable_compat_tree(self):
logger.info("Enabling Schema Compatibility plugin")
compat_plugin_dn = DN("cn=Schema Compatibility,cn=plugins,cn=config")
lookup_nsswitch_name = "schema-compat-lookup-nsswitch"
for config in (("cn=users", "user"), ("cn=groups", "group")):
entry_dn = DN(config[0], compat_plugin_dn)
current = api.Backend.ldap2.get_entry(entry_dn)
lookup_nsswitch = current.get(lookup_nsswitch_name, [])
if not(config[1] in lookup_nsswitch):
logger.debug("Enabling Schema Compatibility plugin "
"for %s", config[0])
current[lookup_nsswitch_name] = [config[1]]
api.Backend.ldap2.update_entry(current)
else:
logger.debug("Schema Compatibility plugin already enabled "
"for %s", config[0])
def run(self):
api.bootstrap(in_server=True, confdir=paths.ETC_IPA)
api.finalize()
try:
api.Backend.ldap2.connect() # ensure DS is up
# If required, enable Schema compat plugin on users/groups
if self.options.enable_compat:
try:
self._enable_compat_tree()
except Exception as e:
raise ScriptError(
"Enabling Schema Compatibility plugin "
"failed: {}".format(e))
# Restart 389-ds and sssd
logger.info("Restarting Directory Server")
try:
services.knownservices.dirsrv.restart()
except Exception as e:
raise ScriptError(
"Directory Server restart was unsuccessful: {}".format(e))
logger.info("Restarting SSSD service")
try:
sssd = services.service('sssd', api)
sssd.restart()
except CalledProcessError as e:
raise ScriptError(
"SSSD service restart was unsuccessful: {}".format(e))
finally:
if api.Backend.ldap2.isconnected():
api.Backend.ldap2.disconnect()
return 0

View File

@@ -26,6 +26,7 @@ from ipalib.messages import (
add_message,
BrokenTrust)
from ipalib.plugable import Registry
from ipalib.request import context
from .baseldap import (
pkey_to_value,
entry_to_dict,
@@ -42,6 +43,7 @@ from ipapython.ipautil import realm_to_suffix
from ipalib import api, Str, StrEnum, Password, Bool, _, ngettext, Int, Flag
from ipalib import Command
from ipalib import errors
from ipalib import messages
from ipalib import output
from ldap import SCOPE_SUBTREE
from time import sleep
@@ -52,6 +54,7 @@ from ipaserver.dcerpc_common import (TRUST_ONEWAY,
trust_type_string,
trust_direction_string,
trust_status_string)
from ipaserver.plugins.privilege import principal_has_privilege
if six.PY3:
unicode = str
@@ -1828,6 +1831,71 @@ class trust_fetch_domains(LDAPRetrieve):
return result
@register()
class trust_enable_agent(Command):
__doc__ = _("Configure this server as a trust agent.")
NO_CLI = True
has_output = output.standard_value
takes_args = (
Str(
'remote_cn',
cli_name='remote_name',
label=_('Remote server name'),
doc=_('Remote IPA server hostname'),
),
)
takes_options = (
Flag('enable_compat',
doc=_('Enable support for trusted domains for old clients'),
default=False),
)
def execute(self, *keys, **options):
# the server must be the local host
# This check is needed because the forward method may failover
# to a master different from the one specified
if keys[0] != api.env.host:
raise errors.ValidationError(
name='cn', error=_("must be \"%s\"") % api.env.host)
# the user must have the Replication Administrators privilege
privilege = u'Replication Administrators'
if not principal_has_privilege(self.api, context.principal, privilege):
raise errors.ACIError(
info=_("not allowed to remotely add agent"))
# Trust must be configured
if options[u'enable_compat']:
method_arguments = "--enable-compat"
else:
method_arguments = ""
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.trust_enable_agent(method_arguments)
result = dict(
result=(ret == 0),
value=keys[0],
)
for line in stdout.splitlines() + stderr.splitlines():
messages.add_message(options['version'],
result,
messages.ExternalCommandOutput(line=line))
return result
@register()
class trustdomain_enable(LDAPQuery):
__doc__ = _('Allow use of IPA resources by the domain of the trust')