summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/trust.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/plugins/trust.py')
-rw-r--r--ipalib/plugins/trust.py141
1 files changed, 107 insertions, 34 deletions
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()