summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--API.txt3
-rw-r--r--VERSION2
-rw-r--r--freeipa.spec.in14
-rw-r--r--install/Makefile.am1
-rw-r--r--install/configure.ac1
-rw-r--r--install/oddjob/Makefile.am28
-rwxr-xr-xinstall/oddjob/com.redhat.idm.trust-fetch-domains198
-rw-r--r--install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf40
-rw-r--r--install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf21
-rw-r--r--ipalib/plugins/trust.py141
-rw-r--r--ipaserver/dcerpc.py44
11 files changed, 442 insertions, 51 deletions
diff --git a/API.txt b/API.txt
index f3b4df8d4..020639f02 100644
--- a/API.txt
+++ b/API.txt
@@ -4971,11 +4971,12 @@ arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=Tr
option: Str('version?', exclude='webui')
output: Output('result', None, None)
command: trust_add
-args: 1,13,3
+args: 1,14,3
arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, required=True)
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Int('base_id?', cli_name='base_id')
+option: Bool('bidirectional?', cli_name='two_way', default=False)
option: Int('range_size?', cli_name='range_size')
option: StrEnum('range_type?', cli_name='range_type', values=(u'ipa-ad-trust-posix', u'ipa-ad-trust'))
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
diff --git a/VERSION b/VERSION
index c31ddfc97..02ad7e23c 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=138
+IPA_API_VERSION_MINOR=139
# Last change: mbabinsk: Commands to manage user/host/service certificates
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 52af50dd0..46586ed71 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -204,6 +204,7 @@ Requires: samba >= %{samba_version}
Requires: samba-winbind
Requires: libsss_idmap
Requires: libsss_nss_idmap-python
+Requires: oddjob
%if (0%{?fedora} >= 22)
Requires: python-sss
%endif
@@ -581,6 +582,8 @@ fi
%post server-trust-ad
%{_sbindir}/update-alternatives --install %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \
winbind_krb5_locator.so /dev/null 90
+/bin/systemctl reload-or-try-restart dbus
+/bin/systemctl reload-or-try-restart oddjobd
%posttrans server-trust-ad
python2 -c "import sys; from ipaserver.install import installutils; sys.exit(0 if installutils.is_ipa_configured() else 1);" > /dev/null 2>&1
@@ -593,6 +596,8 @@ fi
%preun server-trust-ad
if [ $1 -eq 0 ]; then
%{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
+ /bin/systemctl reload-or-try-restart dbus
+ /bin/systemctl reload-or-try-restart oddjobd
fi
%endif # ONLY_CLIENT
@@ -830,6 +835,9 @@ fi
%attr(755,root,root) %{plugin_dir}/libipa_otp_counter.so
%attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so
%attr(755,root,root) %{plugin_dir}/libtopology.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
+%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
%dir %{_localstatedir}/lib/ipa
%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup
%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore
@@ -864,15 +872,15 @@ fi
%files server-trust-ad
%{_sbindir}/ipa-adtrust-install
-%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
%{_usr}/share/ipa/smb.conf.empty
%attr(755,root,root) %{_libdir}/samba/pdb/ipasam.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
%{_mandir}/man1/ipa-adtrust-install.1.gz
%{python_sitelib}/ipaserver/dcerpc*
%{python_sitelib}/ipaserver/install/adtrustinstance*
%ghost %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
+%{_sysconfdir}/dbus-1/system.d/oddjob-ipa-trust.conf
+%{_sysconfdir}/oddjobd.conf.d/oddjobd-ipa-trust.conf
+%%attr(755,root,root) %{_libexecdir}/ipa/com.redhat.idm.trust-fetch-domains
%endif # ONLY_CLIENT
diff --git a/install/Makefile.am b/install/Makefile.am
index c07f57155..ac52ad3bb 100644
--- a/install/Makefile.am
+++ b/install/Makefile.am
@@ -17,6 +17,7 @@ SUBDIRS = \
po \
restart_scripts \
wsgi \
+ oddjob \
$(NULL)
install-exec-local:
diff --git a/install/configure.ac b/install/configure.ac
index 57f4219b6..cf19758a1 100644
--- a/install/configure.ac
+++ b/install/configure.ac
@@ -103,6 +103,7 @@ AC_CONFIG_FILES([
po/Makefile
restart_scripts/Makefile
wsgi/Makefile
+ oddjob/Makefile
])
AC_OUTPUT
diff --git a/install/oddjob/Makefile.am b/install/oddjob/Makefile.am
new file mode 100644
index 000000000..9dde10c70
--- /dev/null
+++ b/install/oddjob/Makefile.am
@@ -0,0 +1,28 @@
+NULL =
+
+oddjobdir = $(libexecdir)/ipa
+oddjobconfdir = $(sysconfdir)/oddjobd.conf.d
+dbusconfdir = $(sysconfdir)/dbus-1/system.d
+
+oddjob_SCRIPTS = \
+ com.redhat.idm.trust-fetch-domains \
+ $(NULL)
+
+dbusconf_DATA = \
+ etc/dbus-1/system.d/oddjob-ipa-trust.conf \
+ $(NULL)
+
+oddjobconf_DATA = \
+ etc/oddjobd.conf.d/oddjobd-ipa-trust.conf \
+ $(NULL)
+
+
+#EXTRA_DIST = \
+# $(oddjob_SCRIPTS) \
+# $(dbusconf_DATA) \
+# $(oddjobconf_DATA) \
+# $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains b/install/oddjob/com.redhat.idm.trust-fetch-domains
new file mode 100755
index 000000000..2571dd09a
--- /dev/null
+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains
@@ -0,0 +1,198 @@
+#!/usr/bin/python2
+
+from ipaserver import dcerpc
+from ipaserver.install.installutils import is_ipa_configured, ScriptError
+from ipapython import config, ipautil
+from ipalib import api, errors
+from ipapython.dn import DN
+from ipalib.config import Env
+from ipalib.constants import DEFAULT_CONFIG
+from ipalib.krb_utils import KRB5_CCache
+import sys
+import os, pwd
+import krbV
+import time
+
+# This version is different from the original in ipapyton.ipautil
+# in the fact that it returns a krbV.CCache object.
+def kinit_keytab(principal, keytab, ccache_name, attempts=1):
+ errors_to_retry = {krbV.KRB5KDC_ERR_SVC_UNAVAILABLE,
+ krbV.KRB5_KDC_UNREACH}
+ for attempt in range(1, attempts + 1):
+ try:
+ krbcontext = krbV.default_context()
+ ktab = krbV.Keytab(name=keytab, context=krbcontext)
+ princ = krbV.Principal(name=principal, context=krbcontext)
+ ccache = krbV.CCache(name=ccache_name, context=krbcontext,
+ primary_principal=princ)
+ ccache.init(princ)
+ ccache.init_creds_keytab(keytab=ktab, principal=princ)
+ return ccache
+ except krbV.Krb5Error as e:
+ if e.args[0] not in errors_to_retry:
+ raise
+ if attempt == attempts:
+ raise
+ time.sleep(5)
+
+def retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal):
+ getkeytab_args = ["/usr/sbin/ipa-getkeytab",
+ "-s", api.env.host,
+ "-p", oneway_principal,
+ "-k", oneway_keytab_name,
+ "-r"]
+ (stdout, stderr, retcode) = ipautil.run(getkeytab_args,
+ env={'KRB5CCNAME': ccache_name, 'LANG': 'C'},
+ raiseonerr=False)
+ # Make sure SSSD is able to read the keytab
+ sssd = pwd.getpwnam('sssd')
+ os.chown(oneway_keytab_name, sssd[2], sssd[3])
+
+
+def parse_options():
+ usage = "%prog <trusted domain name>\n"
+ parser = config.IPAOptionParser(usage=usage,
+ formatter=config.IPAFormatter())
+
+ parser.add_option("-d", "--debug", action="store_true", dest="debug",
+ help="Display debugging information")
+
+ options, args = parser.parse_args()
+ safe_options = parser.get_safe_opts(options)
+
+ return safe_options, options, args
+
+
+if not is_ipa_configured():
+ # LSB status code 6: program is not configured
+ raise ScriptError("IPA is not configured " +
+ "(see man pages of ipa-server-install for help)", 6)
+
+if not os.getegid() == 0:
+ # LSB status code 4: user had insufficient privilege
+ raise ScriptError("You must be root to run ipactl.", 4)
+
+safe_options, options, args = parse_options()
+
+if len(args) != 1:
+ # LSB status code 2: invalid or excess argument(s)
+ raise ScriptError("You must specify trusted domain name", 2)
+
+trusted_domain = unicode(args[0].lower())
+
+env = Env()
+env._bootstrap(context='server', debug=options.debug, log=None)
+env._finalize_core(**dict(DEFAULT_CONFIG))
+
+# Initialize the API with the proper debug level
+api.bootstrap(context='server', debug=env.debug, log=None)
+api.finalize()
+
+# Only import trust plugin after api is initialized or internal imports
+# within the plugin will not work
+from ipalib.plugins import trust
+
+# We have to dance with two different credentials caches:
+# ccache_name -- for cifs/ipa.master@IPA.REALM to communicate with LDAP
+# oneway_ccache_name -- for IPA$@AD.REALM to communicate with AD DCs
+#
+# ccache_name may not exist, we'll have to initialize it from Samba's keytab
+#
+# oneway_ccache_name may not exist either but to initialize it, we need
+# to check if oneway_keytab_name keytab exists and fetch it first otherwise.
+#
+# to fetch oneway_keytab_name keytab, we need to initialize ccache_name ccache first
+# and retrieve our own NetBIOS domain name and use cifs/ipa.master@IPA.REALM to
+# retrieve the keys to oneway_keytab_name.
+
+keytab_name = '/etc/samba/samba.keytab'
+oneway_keytab_name = '/var/lib/sss/keytabs/' + trusted_domain + '.keytab'
+
+principal = str('cifs/' + api.env.host)
+
+oneway_ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts_fetch'
+ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts'
+
+# Standard sequence:
+# - check if ccache exists
+# - if not, initialize it from Samba's keytab
+# - check if ccache contains valid TGT
+# - if not, initialize it from Samba's keytab
+# - refer the correct ccache object for further use
+#
+if not os.path.isfile(ccache_name):
+ ccache = kinit_keytab(principal, keytab_name, ccache_name)
+
+ccache_check = KRB5_CCache(ccache_name)
+if not ccache_check.credential_is_valid(principal):
+ ccache = kinit_keytab(principal, keytab_name, ccache_name)
+else:
+ ccache = ccache_check.ccache
+
+old_ccache = os.environ.get('KRB5CCNAME')
+api.Backend.ldap2.connect(ccache)
+
+own_trust_dn = DN(('cn', api.env.domain),('cn','ad'), ('cn', 'etc'), api.env.basedn)
+own_trust_entry = api.Backend.ldap2.get_entry(own_trust_dn, ['ipantflatname'])
+own_trust_flatname = own_trust_entry['ipantflatname'][0].upper()
+
+oneway_principal = str('%s$@%s' % (own_trust_flatname, trusted_domain.upper()))
+
+# If keytab does not exist, retrieve it
+if not os.path.isfile(oneway_keytab_name):
+ retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+oneway_ccache = None
+try:
+ # The keytab may have stale key material (from older trust-add run)
+ if not os.path.isfile(oneway_ccache_name):
+ oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+except krbV.Krb5Error as e:
+ # If there was failure on using keytab, assume it is stale and retrieve again
+ retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+if oneway_ccache:
+ # There wasn existing ccache, validate its content
+ oneway_ccache_check = KRB5_CCache(oneway_ccache_name)
+ if not oneway_ccache_check.credential_is_valid(oneway_principal):
+ # If credentials were invalid, obtain them again
+ oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+ else:
+ oneway_ccache = oneway_ccache_check.ccache
+else:
+ oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+
+# We are done: we have ccache with TDO credentials and can fetch domains
+ipa_domain = api.env.domain
+os.environ['KRB5CCNAME'] = oneway_ccache_name
+domains = dcerpc.fetch_domains(api, ipa_domain, trusted_domain, creds=True)
+
+if domains:
+ # trust range must exist by the time fetch_domains_from_trust is called
+ range_name = unicode(trusted_domain.upper() + '_id_range')
+ old_range = api.Command.idrange_show(range_name, raw=True)['result']
+ idrange_type = old_range['iparangetype'][0]
+
+ result = []
+ for dom in domains:
+ dom['trust_type'] = u'ad'
+ try:
+ name = dom['cn']
+ del dom['cn']
+
+ res = api.Command.trustdomain_add(trusted_domain, name, **dom)
+ result.append(res['result'])
+
+ if idrange_type != u'ipa-ad-trust-posix':
+ range_name = name.upper() + '_id_range'
+ dom['range_type'] = u'ipa-ad-trust'
+ trust.add_range(range_name, dom['ipanttrusteddomainsid'],
+ trusted_domain, name, **dom)
+ except errors.DuplicateEntry:
+ # Ignore updating duplicate entries
+ pass
+
+if old_ccache:
+ os.environ['KRB5CCNAME'] = old_ccache
+
+sys.exit(0)
diff --git a/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
new file mode 100644
index 000000000..2e4c1367b
--- /dev/null
+++ b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
@@ -0,0 +1,40 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+ <!-- Only root can own (provide) the com.redhat.idm.trust service
+ on the system bus. -->
+ <policy user="root">
+ <allow own="com.redhat.idm.trust"/>
+ <allow send_destination="com.redhat.idm.trust"
+ send_path="/"
+ send_interface="com.redhat.idm.trust"
+ send_member="fetch_domains"/>
+ </policy>
+
+ <!-- Allow anyone to call the introspection methods of the "/" object
+ provided by the com.redhat.idm.trust service. -->
+ <policy context="default">
+ <allow send_destination="com.redhat.idm.trust"
+ send_path="/"
+ send_interface="org.freedesktop.DBus.Introspectable"
+ send_member="Introspect"/>
+ <allow send_destination="com.redhat.idm.trust"
+ send_path="/"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="GetAll"/>
+ <allow send_destination="com.redhat.idm.trust"
+ send_path="/"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="Get"/>
+ </policy>
+
+ <policy user="apache">
+ <allow send_destination="com.redhat.idm.trust"
+ send_path="/"
+ send_interface="com.redhat.idm.trust"
+ send_member="fetch_domains"/>
+ </policy>
+
+</busconfig>
diff --git a/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
new file mode 100644
index 000000000..17817de09
--- /dev/null
+++ b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<oddjobconfig>
+ <service name="com.redhat.idm.trust">
+ <allow user="root"/>
+ <allow user="apache"/>
+ <object name="/">
+ <interface name="org.freedesktop.DBus.Introspectable">
+ <allow min_uid="0" max_uid="0"/>
+ <!-- <method name="Introspect"/> -->
+ </interface>
+ <interface name="com.redhat.idm.trust">
+ <method name="fetch_domains">
+ <helper exec="/usr/libexec/ipa/com.redhat.idm.trust-fetch-domains"
+ arguments="1"
+ argument_passing_method="cmdline"
+ prepend_user_name="no"/>
+ </method>
+ </interface>
+ </object>
+ </service>
+</oddjobconfig>
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 13ac52ddd..9fbaf2507 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -22,6 +22,7 @@ from ipalib.plugable import Registry
from ipalib.plugins.baseldap import *
from ipalib.plugins.dns import dns_container_exists
from ipapython.ipautil import realm_to_suffix
+from ipapython.ipa_log_manager import root_logger
from ipalib import api, Str, StrEnum, Password, Bool, _, ngettext
from ipalib import Command
from ipalib import errors
@@ -43,6 +44,8 @@ except Exception, e:
if api.env.in_server and api.env.context in ['lite', 'server']:
try:
import ipaserver.dcerpc #pylint: disable=F0401
+ from ipaserver.dcerpc import TRUST_ONEWAY, TRUST_BIDIRECTIONAL
+ import dbus, dbus.mainloop.glib
_bindings_installed = True
except ImportError:
_bindings_installed = False
@@ -161,6 +164,8 @@ _trust_type_option = StrEnum('trust_type',
DEFAULT_RANGE_SIZE = 200000
+DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
+
def trust_type_string(level):
"""
Returns a string representing a type of the trust. The original field is an enum:
@@ -191,7 +196,7 @@ def make_trust_dn(env, trust_type, dn):
return DN(dn, container_dn)
return dn
-def add_range(self, range_name, dom_sid, *keys, **options):
+def add_range(myapi, range_name, dom_sid, *keys, **options):
"""
First, we try to derive the parameters of the ID range based on the
information contained in the Active Directory.
@@ -224,7 +229,7 @@ def add_range(self, range_name, dom_sid, *keys, **options):
+ basedn
# Get the domain validator
- domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
+ domain_validator = ipaserver.dcerpc.DomainValidator(myapi)
if not domain_validator.is_configured():
raise errors.NotFound(
reason=_('Cannot search in trusted domains without own '
@@ -251,10 +256,10 @@ def add_range(self, range_name, dom_sid, *keys, **options):
if not info_list:
# We were unable to gain UNIX specific info from the AD
- self.log.debug("Unable to gain POSIX info from the AD")
+ root_logger.debug("Unable to gain POSIX info from the AD")
else:
if all(attr in info for attr in required_msSFU_attrs):
- self.log.debug("Able to gain POSIX info from the AD")
+ root_logger.debug("Able to gain POSIX info from the AD")
range_type = u'ipa-ad-trust-posix'
max_uid = info.get('msSFU30MaxUidNumber')
@@ -288,16 +293,43 @@ def add_range(self, range_name, dom_sid, *keys, **options):
) * DEFAULT_RANGE_SIZE
# Finally, add new ID range
- self.api.Command['idrange_add'](range_name,
- ipabaseid=base_id,
- ipaidrangesize=range_size,
- ipabaserid=0,
- iparangetype=range_type,
- ipanttrusteddomainsid=dom_sid)
+ myapi.Command['idrange_add'](range_name,
+ ipabaseid=base_id,
+ ipaidrangesize=range_size,
+ ipabaserid=0,
+ iparangetype=range_type,
+ ipanttrusteddomainsid=dom_sid)
# Return the values that were generated inside this function
return range_type, range_size, base_id
+def fetch_trusted_domains_over_dbus(myapi, log, forest_name):
+ if not _bindings_installed:
+ return
+ # Calling oddjobd-activated service via DBus has some quirks:
+ # - Oddjobd registers multiple canonical names on the same address
+ # - python-dbus only follows name owner changes when mainloop is in use
+ # See https://fedorahosted.org/oddjob/ticket/2 for details
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ try:
+ _ret = 0
+ _stdout = ''
+ _stderr = ''
+ bus = dbus.SystemBus()
+ intf = bus.get_object(DBUS_IFACE_TRUST,"/", follow_name_owner_changes=True)
+ fetch_domains_method = intf.get_dbus_method('fetch_domains', dbus_interface=DBUS_IFACE_TRUST)
+ (_ret, _stdout, _stderr) = fetch_domains_method(forest_name)
+ except dbus.DBusException, e:
+ log.error('Failed to call %(iface)s.fetch_domains helper.'
+ 'DBus exception is %(exc)s.' % dict(iface=DBUS_IFACE_TRUST, exc=str(e)))
+ if _ret != 0:
+ log.error('Helper was called for forest %(forest)s, return code is %(ret)d' % dict(forest=forest_name, ret=_ret))
+ log.error('Standard output from the helper:\n%s---\n' % (_stdout))
+ log.error('Error output from the helper:\n%s--\n' % (_stderr))
+ raise errors.ServerCommandError(server=myapi.env.host,
+ error=_('Fetching domains from trusted forest failed. '
+ 'See details in the error_log'))
+ return
@register()
class trust(LDAPObject):
@@ -463,6 +495,12 @@ sides.
.format(vals=', '.join(range_types.keys())))),
values=tuple(range_types.keys()),
),
+ Bool('bidirectional?',
+ label=_('Two-way trust'),
+ cli_name='two_way',
+ doc=(_('Establish bi-directional trust. By default trust is inbound one-way only.')),
+ default=False,
+ ),
)
msg_summary = _('Added Active Directory trust for realm "%(value)s"')
@@ -478,7 +516,7 @@ sides.
# Store the created range type, since for POSIX trusts no
# ranges for the subdomains should be added, POSIX attributes
# provide a global mapping across all subdomains
- (created_range_type, _, _) = add_range(self, range_name, dom_sid,
+ (created_range_type, _, _) = add_range(self.api, range_name, dom_sid,
*keys, **options)
else:
created_range_type = old_range['result']['iparangetype'][0]
@@ -486,19 +524,35 @@ sides.
trust_filter = "cn=%s" % result['value']
ldap = self.obj.backend
(trusts, truncated) = ldap.find_entries(
- base_dn=DN(api.env.container_trusts, api.env.basedn),
+ base_dn=DN(self.api.env.container_trusts, self.api.env.basedn),
filter=trust_filter)
result['result'] = entry_to_dict(trusts[0], **options)
# Fetch topology of the trust forest -- we need always to do it
# for AD trusts, regardless of the type of idranges associated with it
- # Note that fetch_domains_from_trust will add needed ranges for
+ # Note that add_new_domains_from_trust will add needed ranges for
# the algorithmic ID mapping case.
if (options.get('trust_type') == u'ad' and
options.get('trust_secret') is None):
- domains = fetch_domains_from_trust(self, self.trustinstance,
+ if options.get('bidirectional') == True:
+ # Bidirectional trust allows us to use cross-realm TGT, so we can
+ # run the call under original user's credentials
+ res = fetch_domains_from_trust(self.api, self.trustinstance,
result['result'], **options)
+ domains = add_new_domains_from_trust(self.api, self.trustinstance,
+ result['result'], res, **options)
+ else:
+ # One-way trust is more complex. We don't have cross-realm TGT
+ # and cannot use IPA principals to authenticate against AD.
+ # Instead, we have to use our trusted domain object's (TDO)
+ # account in AD. Access to the credentials is limited and IPA
+ # framework cannot access it directly. Instead, we call out to
+ # oddjobd-activated higher privilege process that will use TDO
+ # object credentials to authenticate to AD with Kerberos,
+ # run DCE RPC calls to do discovery and will call
+ # add_new_domains_from_trust() on its own.
+ fetch_trusted_domains_over_dbus(self.api, self.log, result['value'])
# Format the output into human-readable values
result['result']['trusttype'] = [trust_type_string(
@@ -570,7 +624,7 @@ sides.
# If domain name and realm does not match, IPA server is not be able
# to establish trust with Active Directory.
- realm_not_matching_domain = (api.env.domain.upper() != api.env.realm)
+ realm_not_matching_domain = (self.api.env.domain.upper() != self.api.env.realm)
if options['trust_type'] == u'ad' and realm_not_matching_domain:
raise errors.ValidationError(
@@ -627,7 +681,7 @@ sides.
range_type = options.get('range_type')
try:
- old_range = api.Command['idrange_show'](range_name, raw=True)
+ old_range = self.api.Command['idrange_show'](range_name, raw=True)
except errors.NotFound:
old_range = None
@@ -699,6 +753,9 @@ sides.
except errors.NotFound:
dn = None
+ trust_type = TRUST_ONEWAY
+ if options.get('bidirectional', False):
+ trust_type = TRUST_BIDIRECTIONAL
# 1. Full access to the remote domain. Use admin credentials and
# generate random trustdom password to do work on both sides
if full_join:
@@ -707,14 +764,15 @@ sides.
keys[-1],
self.realm_server,
self.realm_admin,
- self.realm_passwd
+ self.realm_passwd,
+ trust_type
)
except errors.NotFound:
error_message=_("Unable to resolve domain controller for '%s' domain. ") % (keys[-1])
instructions=[]
if dns_container_exists(self.obj.backend):
try:
- dns_zone = api.Command.dnszone_show(keys[-1])['result']
+ dns_zone = self.api.Command.dnszone_show(keys[-1])['result']
if ('idnsforwardpolicy' in dns_zone) and dns_zone['idnsforwardpolicy'][0] == u'only':
instructions.append(_("Forward policy is defined for it in IPA DNS, "
"perhaps forwarder points to incorrect host?"))
@@ -755,7 +813,8 @@ sides.
result = self.trustinstance.join_ad_ipa_half(
keys[-1],
self.realm_server,
- options['trust_secret']
+ options['trust_secret'],
+ trust_type
)
ret = dict(
value=pkey_to_value(
@@ -940,7 +999,7 @@ class trustconfig(LDAPObject):
group,
['posixgroup'],
[''],
- DN(api.env.container_group, api.env.basedn))
+ DN(self.api.env.container_group, self.api.env.basedn))
except errors.NotFound:
self.api.Object['group'].handle_not_found(group)
else:
@@ -1066,11 +1125,11 @@ class adtrust_is_enabled(Command):
ldap = self.api.Backend.ldap2
adtrust_dn = DN(
('cn', 'ADTRUST'),
- ('cn', api.env.host),
+ ('cn', self.api.env.host),
('cn', 'masters'),
('cn', 'ipa'),
('cn', 'etc'),
- api.env.basedn
+ self.api.env.basedn
)
try:
@@ -1281,7 +1340,7 @@ class trustdomain_del(LDAPDelete):
raise errors.ValidationError(name='domain',
error=_("cannot delete root domain of the trust, use trust-del to delete the trust itself"))
try:
- res = api.Command.trustdomain_enable(keys[0], domain)
+ res = self.api.Command.trustdomain_enable(keys[0], domain)
except errors.AlreadyActive:
pass
result = super(trustdomain_del, self).execute(*keys, **options)
@@ -1291,7 +1350,7 @@ class trustdomain_del(LDAPDelete):
-def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
+def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options):
trust_name = trust_entry['cn'][0]
creds = None
password = options.get('realm_passwd', None)
@@ -1303,16 +1362,20 @@ def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
creds = u"{name}%{password}".format(name="\\".join(sp),
password=password)
server = options.get('realm_server', None)
- domains = ipaserver.dcerpc.fetch_domains(self.api,
+ domains = ipaserver.dcerpc.fetch_domains(myapi,
trustinstance.local_flatname,
trust_name, creds=creds, server=server)
+ return domains
+
+def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **options):
result = []
if not domains:
return result
- # trust range must exist by the time fetch_domains_from_trust is called
+ trust_name = trust_entry['cn'][0]
+ # trust range must exist by the time add_new_domains_from_trust is called
range_name = trust_name.upper() + '_id_range'
- old_range = api.Command.idrange_show(range_name, raw=True)['result']
+ old_range = myapi.Command.idrange_show(range_name, raw=True)['result']
idrange_type = old_range['iparangetype'][0]
for dom in domains:
@@ -1325,13 +1388,13 @@ def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
if 'raw' in options:
dom['raw'] = options['raw']
- res = self.api.Command.trustdomain_add(trust_name, name, **dom)
+ res = myapi.Command.trustdomain_add(trust_name, name, **dom)
result.append(res['result'])
if idrange_type != u'ipa-ad-trust-posix':
range_name = name.upper() + '_id_range'
dom['range_type'] = u'ipa-ad-trust'
- add_range(self, range_name, dom['ipanttrusteddomainsid'],
+ add_range(myapi, range_name, dom['ipanttrusteddomainsid'],
trust_name, name, **dom)
except errors.DuplicateEntry:
# Ignore updating duplicate entries
@@ -1362,6 +1425,17 @@ class trust_fetch_domains(LDAPRetrieve):
)
trust = self.api.Command.trust_show(keys[0], raw=True)['result']
+ result = dict()
+ result['result'] = []
+ result['count'] = 0
+ result['truncated'] = False
+
+ # For one-way trust fetch over DBus. we don't get the list in this case.
+ if trust['ipanttrustdirection'] & TRUST_BIDIRECTIONAL != TRUST_BIDIRECTIONAL:
+ fetch_trusted_domains_over_dbus(self.api, self.log, keys[0])
+ result['summary'] = unicode(_('List of trust domains successfully refreshed. Use trustdomain-find command to list them.'))
+ return result
+
trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
if not trustinstance.configured:
raise errors.NotFound(
@@ -1372,8 +1446,8 @@ class trust_fetch_domains(LDAPRetrieve):
'on the IPA server first'
)
)
- domains = fetch_domains_from_trust(self, trustinstance, trust)
- result = dict()
+ res = fetch_domains_from_trust(self.api, trustinstance, trust, **options)
+ domains = add_new_domains_from_trust(self.api, trustinstance, trust, res, **options)
if len(domains) > 0:
result['summary'] = unicode(_('List of trust domains successfully refreshed'))
@@ -1382,7 +1456,6 @@ class trust_fetch_domains(LDAPRetrieve):
result['result'] = domains
result['count'] = len(domains)
- result['truncated'] = False
return result
@@ -1413,7 +1486,7 @@ class trustdomain_enable(LDAPQuery):
trust_entry['ipantsidblacklistincoming'].remove(sid)
ldap.update_entry(trust_entry)
# Force MS-PAC cache re-initialization on KDC side
- domval = ipaserver.dcerpc.DomainValidator(api)
+ domval = ipaserver.dcerpc.DomainValidator(self.api)
(ccache_name, principal) = domval.kinit_as_http(keys[0])
else:
raise errors.AlreadyActive()
@@ -1453,7 +1526,7 @@ class trustdomain_disable(LDAPQuery):
trust_entry['ipantsidblacklistincoming'].append(sid)
ldap.update_entry(trust_entry)
# Force MS-PAC cache re-initialization on KDC side
- domval = ipaserver.dcerpc.DomainValidator(api)
+ domval = ipaserver.dcerpc.DomainValidator(self.api)
(ccache_name, principal) = domval.kinit_as_http(keys[0])
else:
raise errors.AlreadyInactive()
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 753e10e97..b11233d8d 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -66,6 +66,10 @@ The code in this module relies heavily on samba4-python package
and Samba4 python bindings.
""")
+# Both constants can be used as masks against trust direction
+# because bi-directional has two lower bits set.
+TRUST_ONEWAY = 1
+TRUST_BIDIRECTIONAL = 3
def is_sid_valid(sid):
try:
@@ -949,7 +953,7 @@ class TrustDomainInstance(object):
# We can ignore the error here -- setting up name suffix routes may fail
pass
- def establish_trust(self, another_domain, trustdom_secret):
+ def establish_trust(self, another_domain, trustdom_secret, trust_type='bidirectional'):
"""
Establishes trust between our and another domain
Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
@@ -967,7 +971,9 @@ class TrustDomainInstance(object):
info.domain_name.string = another_domain.info['dns_domain']
info.netbios_name.string = another_domain.info['name']
info.sid = security.dom_sid(another_domain.info['sid'])
- info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
+ info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND
+ if trust_type == TRUST_BIDIRECTIONAL:
+ info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
info.trust_attributes = 0
@@ -1005,7 +1011,8 @@ class TrustDomainInstance(object):
pass
try:
- info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
+ info = self._pipe.QueryTrustedDomainInfo(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
+ info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
self._pipe.SetInformationTrustedDomain(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
except RuntimeError, e:
root_logger.error('unable to set trust to transitive: %s' % (str(e)))
@@ -1014,10 +1021,10 @@ class TrustDomainInstance(object):
self.update_ftinfo(another_domain)
def verify_trust(self, another_domain):
- def retrieve_netlogon_info_2(domain, function_code, data):
+ def retrieve_netlogon_info_2(logon_server, domain, function_code, data):
try:
netr_pipe = netlogon.netlogon(domain.binding, domain.parm, domain.creds)
- result = netr_pipe.netr_LogonControl2Ex(logon_server=None,
+ result = netr_pipe.netr_LogonControl2Ex(logon_server=logon_server,
function_code=function_code,
level=2,
data=data
@@ -1026,7 +1033,7 @@ class TrustDomainInstance(object):
except RuntimeError, (num, message):
raise assess_dcerpc_exception(num=num, message=message)
- result = retrieve_netlogon_info_2(self,
+ result = retrieve_netlogon_info_2(None, self,
netlogon.NETLOGON_CONTROL_TC_VERIFY,
another_domain.info['dns_domain'])
if (result and (result.flags and netlogon.NETLOGON_VERIFY_STATUS_RETURNED)):
@@ -1098,6 +1105,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
td.info['dc'] = unicode(result.pdc_dns_name)
if creds is None:
+ # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust
domval = DomainValidator(api)
(ccache_name, principal) = domval.kinit_as_http(trustdomain)
td.creds = credentials.Credentials()
@@ -1107,7 +1115,15 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
td.creds.guess(td.parm)
td.creds.set_workstation(domain_validator.flatname)
domains = communicate(td)
+ elif type(creds) is bool:
+ # Rely on existing Kerberos credentials in the environment
+ td.creds = credentials.Credentials()
+ td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+ td.creds.guess(td.parm)
+ td.creds.set_workstation(domain_validator.flatname)
+ domains = communicate(td)
else:
+ # Assume we've got credentials as a string user%password
td.creds = credentials.Credentials()
td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS)
td.creds.guess(td.parm)
@@ -1220,7 +1236,7 @@ class TrustDomainJoins(object):
ftinfo['rec_type'] = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME
self.local_domain.ftinfo_records.append(ftinfo)
- def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd):
+ def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd, trust_type):
if not self.configured:
return None
@@ -1238,13 +1254,17 @@ class TrustDomainJoins(object):
if not self.remote_domain.read_only:
trustdom_pass = samba.generate_random_password(128, 128)
self.get_realmdomains()
- self.remote_domain.establish_trust(self.local_domain, trustdom_pass)
- self.local_domain.establish_trust(self.remote_domain, trustdom_pass)
- result = self.remote_domain.verify_trust(self.local_domain)
+ self.remote_domain.establish_trust(self.local_domain, trustdom_pass, trust_type)
+ self.local_domain.establish_trust(self.remote_domain, trustdom_pass, trust_type)
+ # if trust is inbound, we don't need to verify it because AD DC will respond
+ # with WERR_NO_SUCH_DOMAIN -- in only does verification for outbound trusts.
+ result = True
+ if trust_type == TRUST_BIDIRECTIONAL:
+ result = self.remote_domain.verify_trust(self.local_domain)
return dict(local=self.local_domain, remote=self.remote_domain, verified=result)
return None
- def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd):
+ def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd, trust_type):
if not self.configured:
return None
@@ -1254,5 +1274,5 @@ class TrustDomainJoins(object):
if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], domain=self.remote_domain.info['dns_domain'])
- self.local_domain.establish_trust(self.remote_domain, trustdom_passwd)
+ self.local_domain.establish_trust(self.remote_domain, trustdom_passwd, trust_type)
return dict(local=self.local_domain, remote=self.remote_domain, verified=False)