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')