summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorAlexander Bokovoy <abokovoy@redhat.com>2015-06-05 12:57:02 +0000
committerAlexander Bokovoy <abokovoy@redhat.com>2015-07-07 11:09:03 +0300
commit3d975c104be2bd68df53617bc82883aa1a001833 (patch)
tree66023bd0ad5aa4c413764688cc392dabe9afad6b /ipalib
parent65422777e6721bcf7708805f001eb39016495830 (diff)
downloadfreeipa-3d975c104be2bd68df53617bc82883aa1a001833.tar.gz
freeipa-3d975c104be2bd68df53617bc82883aa1a001833.tar.xz
freeipa-3d975c104be2bd68df53617bc82883aa1a001833.zip
trusts: add support for one-way trust and switch to it by default
One-way trust is the default now, use 'trust add --two-way ' to force bidirectional trust https://fedorahosted.org/freeipa/ticket/4959 In case of one-way trust we cannot authenticate using cross-realm TGT against an AD DC. We have to use trusted domain object from within AD domain and access to this object is limited to avoid compromising the whole trust configuration. Instead, IPA framework can call out to oddjob daemon and ask it to run the script which can have access to the TDO object. This script (com.redhat.idm.trust-fetch-domains) is using cifs/ipa.master principal to retrieve TDO object credentials from IPA LDAP if needed and then authenticate against AD DCs using the TDO object credentials. The script pulls the trust topology out of AD DCs and updates IPA LDAP store. Then IPA framework can pick the updated data from the IPA LDAP under normal access conditions. Part of https://fedorahosted.org/freeipa/ticket/4546
Diffstat (limited to 'ipalib')
-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()