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

Reviewed-By: Tomas Babej <tbabej@redhat.com>
This commit is contained in:
Alexander Bokovoy 2015-06-05 12:57:02 +00:00 committed by Tomas Babej
parent 14992a07fc
commit d5aa1ee04e
11 changed files with 443 additions and 52 deletions

View File

@ -4973,11 +4973,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')

View File

@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=143
# Last change: ab - trusts: pass AD DC hostname if specified explicitly
IPA_API_VERSION_MINOR=144
# Last change: ab - trusts: add support for one-way trust and switch to it by default

View File

@ -205,6 +205,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
@ -583,6 +584,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
@ -595,6 +598,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
@ -832,6 +837,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
@ -866,15 +874,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

View File

@ -17,6 +17,7 @@ SUBDIRS = \
po \
restart_scripts \
wsgi \
oddjob \
$(NULL)
install-exec-local:

View File

@ -103,6 +103,7 @@ AC_CONFIG_FILES([
po/Makefile
restart_scripts/Makefile
wsgi/Makefile
oddjob/Makefile
])
AC_OUTPUT

View File

@ -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

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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()

View File

@ -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)):
@ -1100,6 +1107,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()
@ -1109,7 +1117,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)
@ -1222,7 +1238,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
@ -1240,13 +1256,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
@ -1256,5 +1276,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)