From 911992b8bf33b40f650ec406444ca8b9b847b54e Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Tue, 18 Feb 2020 16:24:32 +0100 Subject: [PATCH] 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 Reviewed-By: Rob Crittenden Reviewed-By: Sergey Orlov --- API.txt | 9 ++ VERSION.m4 | 4 +- freeipa.spec.in | 1 + install/oddjob/Makefile.am | 2 + .../etc/oddjobd.conf.d/ipa-server.conf.in | 6 ++ .../org.freeipa.server.trust-enable-agent.in | 8 ++ ipaplatform/base/paths.py | 1 + ipaserver/install/adtrust.py | 62 ++++++++++++- ipaserver/install/ipa_trust_enable_agent.py | 93 +++++++++++++++++++ ipaserver/plugins/trust.py | 68 ++++++++++++++ 10 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 install/oddjob/org.freeipa.server.trust-enable-agent.in create mode 100644 ipaserver/install/ipa_trust_enable_agent.py diff --git a/API.txt b/API.txt index 58c080ebd..9459762c4 100644 --- a/API.txt +++ b/API.txt @@ -5857,6 +5857,14 @@ option: Str('version?') output: Output('result', type=[]) output: Output('summary', type=[, ]) 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=[]) +output: Output('summary', type=[, ]) +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 diff --git a/VERSION.m4 b/VERSION.m4 index 36c1e1db2..2d61abec3 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -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 diff --git a/freeipa.spec.in b/freeipa.spec.in index f1d9bc3e3..7c9f0d321 100755 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -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 diff --git a/install/oddjob/Makefile.am b/install/oddjob/Makefile.am index 28c3ff07a..7aeb406a2 100644 --- a/install/oddjob/Makefile.am +++ b/install/oddjob/Makefile.am @@ -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) diff --git a/install/oddjob/etc/oddjobd.conf.d/ipa-server.conf.in b/install/oddjob/etc/oddjobd.conf.d/ipa-server.conf.in index 5b96a18fc..640b510aa 100644 --- a/install/oddjob/etc/oddjobd.conf.d/ipa-server.conf.in +++ b/install/oddjob/etc/oddjobd.conf.d/ipa-server.conf.in @@ -11,6 +11,12 @@ prepend_user_name="no" argument_passing_method="cmdline"/> + + + diff --git a/install/oddjob/org.freeipa.server.trust-enable-agent.in b/install/oddjob/org.freeipa.server.trust-enable-agent.in new file mode 100644 index 000000000..55f4259b2 --- /dev/null +++ b/install/oddjob/org.freeipa.server.trust-enable-agent.in @@ -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() diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index f3a95500e..5f0335f6f 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -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" diff --git a/ipaserver/install/adtrust.py b/ipaserver/install/adtrust.py index 6c14e8444..ac0ef05fd 100644 --- a/ipaserver/install/adtrust.py +++ b/ipaserver/install/adtrust.py @@ -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): diff --git a/ipaserver/install/ipa_trust_enable_agent.py b/ipaserver/install/ipa_trust_enable_agent.py new file mode 100644 index 000000000..ea9d13ba6 --- /dev/null +++ b/ipaserver/install/ipa_trust_enable_agent.py @@ -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 diff --git a/ipaserver/plugins/trust.py b/ipaserver/plugins/trust.py index 1046c4c47..743d61fd4 100644 --- a/ipaserver/plugins/trust.py +++ b/ipaserver/plugins/trust.py @@ -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')