mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-24 16:10:02 -06:00
trusts: Add support for an external trust to Active Directory domain
External trust is a trust that can be created between Active Directory domains that are in different forests or between an Active Directory domain. Since FreeIPA does not support non-Kerberos means of communication, external trust to Windows NT 4.0 or earlier domains is not supported. The external trust is not transitive and can be established to any domain in another forest. This means no access beyond the external domain is possible via the trust link. Resolves: https://fedorahosted.org/freeipa/ticket/5743 Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
parent
971b4bf009
commit
8ca7a4c947
3
API.txt
3
API.txt
@ -5208,12 +5208,13 @@ arg: Str('cn', cli_name='name')
|
|||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('result')
|
output: Output('result')
|
||||||
command: trust_add
|
command: trust_add
|
||||||
args: 1,14,3
|
args: 1,15,3
|
||||||
arg: Str('cn', cli_name='realm')
|
arg: Str('cn', cli_name='realm')
|
||||||
option: Str('addattr*', cli_name='addattr')
|
option: Str('addattr*', cli_name='addattr')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Int('base_id?', cli_name='base_id')
|
option: Int('base_id?', cli_name='base_id')
|
||||||
option: Bool('bidirectional?', cli_name='two_way', default=False)
|
option: Bool('bidirectional?', cli_name='two_way', default=False)
|
||||||
|
option: Bool('external?', cli_name='external', default=False)
|
||||||
option: Int('range_size?', cli_name='range_size')
|
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: 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)
|
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||||
|
@ -77,6 +77,11 @@ and Samba4 python bindings.
|
|||||||
TRUST_ONEWAY = 1
|
TRUST_ONEWAY = 1
|
||||||
TRUST_BIDIRECTIONAL = 3
|
TRUST_BIDIRECTIONAL = 3
|
||||||
|
|
||||||
|
# Trust join behavior
|
||||||
|
# External trust -- allow creating trust to a non-root domain in the forest
|
||||||
|
TRUST_JOIN_EXTERNAL = 1
|
||||||
|
|
||||||
|
|
||||||
def is_sid_valid(sid):
|
def is_sid_valid(sid):
|
||||||
try:
|
try:
|
||||||
security.dom_sid(sid)
|
security.dom_sid(sid)
|
||||||
@ -1037,7 +1042,7 @@ class TrustDomainInstance(object):
|
|||||||
# We can ignore the error here -- setting up name suffix routes may fail
|
# We can ignore the error here -- setting up name suffix routes may fail
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def establish_trust(self, another_domain, trustdom_secret, trust_type='bidirectional'):
|
def establish_trust(self, another_domain, trustdom_secret, trust_type='bidirectional', trust_external=False):
|
||||||
"""
|
"""
|
||||||
Establishes trust between our and another domain
|
Establishes trust between our and another domain
|
||||||
Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
|
Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
|
||||||
@ -1060,6 +1065,8 @@ class TrustDomainInstance(object):
|
|||||||
info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
|
info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
|
||||||
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
|
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
|
||||||
info.trust_attributes = 0
|
info.trust_attributes = 0
|
||||||
|
if trust_external:
|
||||||
|
info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dname = lsa.String()
|
dname = lsa.String()
|
||||||
@ -1096,14 +1103,17 @@ class TrustDomainInstance(object):
|
|||||||
# server as that one doesn't support AES encryption types
|
# server as that one doesn't support AES encryption types
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
if not trust_external:
|
||||||
info = self._pipe.QueryTrustedDomainInfo(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
|
try:
|
||||||
info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
|
info = self._pipe.QueryTrustedDomainInfo(trustdom_handle,
|
||||||
self._pipe.SetInformationTrustedDomain(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
|
lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
|
||||||
except RuntimeError as e:
|
info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
|
||||||
root_logger.error('unable to set trust to transitive: %s' % (str(e)))
|
self._pipe.SetInformationTrustedDomain(trustdom_handle,
|
||||||
|
lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
|
||||||
|
except RuntimeError as e:
|
||||||
|
root_logger.error('unable to set trust transitivity status: %s' % (str(e)))
|
||||||
|
|
||||||
if self.info['is_pdc']:
|
if self.info['is_pdc'] or trust_external:
|
||||||
self.update_ftinfo(another_domain)
|
self.update_ftinfo(another_domain)
|
||||||
|
|
||||||
def verify_trust(self, another_domain):
|
def verify_trust(self, another_domain):
|
||||||
@ -1262,6 +1272,7 @@ class TrustDomainJoins(object):
|
|||||||
self.api = api
|
self.api = api
|
||||||
self.local_domain = None
|
self.local_domain = None
|
||||||
self.remote_domain = None
|
self.remote_domain = None
|
||||||
|
self.__allow_behavior = 0
|
||||||
|
|
||||||
domain_validator = DomainValidator(api)
|
domain_validator = DomainValidator(api)
|
||||||
self.configured = domain_validator.is_configured()
|
self.configured = domain_validator.is_configured()
|
||||||
@ -1271,6 +1282,10 @@ class TrustDomainJoins(object):
|
|||||||
self.local_dn = domain_validator.dn
|
self.local_dn = domain_validator.dn
|
||||||
self.__populate_local_domain()
|
self.__populate_local_domain()
|
||||||
|
|
||||||
|
def allow_behavior(self, *flags):
|
||||||
|
for f in flags:
|
||||||
|
self.__allow_behavior |= int(f)
|
||||||
|
|
||||||
def __populate_local_domain(self):
|
def __populate_local_domain(self):
|
||||||
# Initialize local domain info using kerberos only
|
# Initialize local domain info using kerberos only
|
||||||
ld = TrustDomainInstance(self.local_flatname)
|
ld = TrustDomainInstance(self.local_flatname)
|
||||||
@ -1358,14 +1373,19 @@ class TrustDomainJoins(object):
|
|||||||
realm_passwd
|
realm_passwd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trust_external = bool(self.__allow_behavior & TRUST_JOIN_EXTERNAL)
|
||||||
if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
|
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'])
|
if not trust_external:
|
||||||
|
raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'],
|
||||||
|
domain=self.remote_domain.info['dns_domain'])
|
||||||
|
|
||||||
if not self.remote_domain.read_only:
|
if not self.remote_domain.read_only:
|
||||||
trustdom_pass = samba.generate_random_password(128, 128)
|
trustdom_pass = samba.generate_random_password(128, 128)
|
||||||
self.get_realmdomains()
|
self.get_realmdomains()
|
||||||
self.remote_domain.establish_trust(self.local_domain, trustdom_pass, trust_type)
|
self.remote_domain.establish_trust(self.local_domain,
|
||||||
self.local_domain.establish_trust(self.remote_domain, trustdom_pass, trust_type)
|
trustdom_pass, trust_type, trust_external)
|
||||||
|
self.local_domain.establish_trust(self.remote_domain,
|
||||||
|
trustdom_pass, trust_type, trust_external)
|
||||||
# if trust is inbound, we don't need to verify it because AD DC will respond
|
# 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.
|
# with WERR_NO_SUCH_DOMAIN -- in only does verification for outbound trusts.
|
||||||
result = True
|
result = True
|
||||||
@ -1381,8 +1401,12 @@ class TrustDomainJoins(object):
|
|||||||
if not(isinstance(self.remote_domain, TrustDomainInstance)):
|
if not(isinstance(self.remote_domain, TrustDomainInstance)):
|
||||||
self.populate_remote_domain(realm, realm_server, realm_passwd=None)
|
self.populate_remote_domain(realm, realm_server, realm_passwd=None)
|
||||||
|
|
||||||
|
trust_external = bool(self.__allow_behavior & TRUST_JOIN_EXTERNAL)
|
||||||
if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
|
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'])
|
if not trust_external:
|
||||||
|
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, trust_type)
|
self.local_domain.establish_trust(self.remote_domain,
|
||||||
|
trustdom_passwd, trust_type, trust_external)
|
||||||
return dict(local=self.local_domain, remote=self.remote_domain, verified=False)
|
return dict(local=self.local_domain, remote=self.remote_domain, verified=False)
|
||||||
|
@ -62,8 +62,10 @@ except Exception as e:
|
|||||||
|
|
||||||
if api.env.in_server and api.env.context in ['lite', 'server']:
|
if api.env.in_server and api.env.context in ['lite', 'server']:
|
||||||
try:
|
try:
|
||||||
import ipaserver.dcerpc #pylint: disable=F0401
|
import ipaserver.dcerpc # pylint: disable=F0401
|
||||||
from ipaserver.dcerpc import TRUST_ONEWAY, TRUST_BIDIRECTIONAL
|
from ipaserver.dcerpc import (TRUST_ONEWAY,
|
||||||
|
TRUST_BIDIRECTIONAL,
|
||||||
|
TRUST_JOIN_EXTERNAL)
|
||||||
import dbus
|
import dbus
|
||||||
import dbus.mainloop.glib
|
import dbus.mainloop.glib
|
||||||
_bindings_installed = True
|
_bindings_installed = True
|
||||||
@ -162,11 +164,18 @@ trust_output_params = (
|
|||||||
label=_('Trust type')),
|
label=_('Trust type')),
|
||||||
Str('truststatus',
|
Str('truststatus',
|
||||||
label=_('Trust status')),
|
label=_('Trust status')),
|
||||||
|
Str('ipantadditionalsuffixes*',
|
||||||
|
label=_('UPN suffixes')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Trust type is a combination of ipanttrusttype and ipanttrustattributes
|
||||||
|
# We shift trust attributes by 3 bits to left so bit 0 becomes bit 3 and
|
||||||
|
# 2+(1 << 3) becomes 10.
|
||||||
_trust_type_dict = {1 : _('Non-Active Directory domain'),
|
_trust_type_dict = {1 : _('Non-Active Directory domain'),
|
||||||
2 : _('Active Directory domain'),
|
2 : _('Active Directory domain'),
|
||||||
3 : _('RFC4120-compliant Kerberos realm')}
|
3 : _('RFC4120-compliant Kerberos realm'),
|
||||||
|
10: _('Non-transitive external trust to a domain in another Active Directory forest')}
|
||||||
|
|
||||||
_trust_direction_dict = {1 : _('Trusting forest'),
|
_trust_direction_dict = {1 : _('Trusting forest'),
|
||||||
2 : _('Trusted forest'),
|
2 : _('Trusted forest'),
|
||||||
3 : _('Two-way trust')}
|
3 : _('Two-way trust')}
|
||||||
@ -189,14 +198,17 @@ DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
|
|||||||
CRED_STYLE_SAMBA = 1
|
CRED_STYLE_SAMBA = 1
|
||||||
CRED_STYLE_KERBEROS = 2
|
CRED_STYLE_KERBEROS = 2
|
||||||
|
|
||||||
def trust_type_string(level):
|
LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE = 0x00000001
|
||||||
|
|
||||||
|
def trust_type_string(level, attrs):
|
||||||
"""
|
"""
|
||||||
Returns a string representing a type of the trust. The original field is an enum:
|
Returns a string representing a type of the trust. The original field is an enum:
|
||||||
LSA_TRUST_TYPE_DOWNLEVEL = 0x00000001,
|
LSA_TRUST_TYPE_DOWNLEVEL = 0x00000001,
|
||||||
LSA_TRUST_TYPE_UPLEVEL = 0x00000002,
|
LSA_TRUST_TYPE_UPLEVEL = 0x00000002,
|
||||||
LSA_TRUST_TYPE_MIT = 0x00000003
|
LSA_TRUST_TYPE_MIT = 0x00000003
|
||||||
"""
|
"""
|
||||||
string = _trust_type_dict.get(int(level), _trust_type_dict_unknown)
|
transitive = int(attrs) & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
|
||||||
|
string = _trust_type_dict.get(int(level) | (transitive << 3), _trust_type_dict_unknown)
|
||||||
return unicode(string)
|
return unicode(string)
|
||||||
|
|
||||||
def trust_direction_string(level):
|
def trust_direction_string(level):
|
||||||
@ -677,6 +689,12 @@ sides.
|
|||||||
doc=(_('Establish bi-directional trust. By default trust is inbound one-way only.')),
|
doc=(_('Establish bi-directional trust. By default trust is inbound one-way only.')),
|
||||||
default=False,
|
default=False,
|
||||||
),
|
),
|
||||||
|
Bool('external?',
|
||||||
|
label=_('External trust'),
|
||||||
|
cli_name='external',
|
||||||
|
doc=(_('Establish external trust to a domain in another forest. The trust is not transitive beyond the domain.')),
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_summary = _('Added Active Directory trust for realm "%(value)s"')
|
msg_summary = _('Added Active Directory trust for realm "%(value)s"')
|
||||||
@ -735,12 +753,15 @@ sides.
|
|||||||
fetch_trusted_domains_over_dbus(self.api, self.log, result['value'])
|
fetch_trusted_domains_over_dbus(self.api, self.log, result['value'])
|
||||||
|
|
||||||
# Format the output into human-readable values
|
# Format the output into human-readable values
|
||||||
|
attributes = int(result['result'].get('ipanttrustattributes', [0])[0])
|
||||||
result['result']['trusttype'] = [trust_type_string(
|
result['result']['trusttype'] = [trust_type_string(
|
||||||
result['result']['ipanttrusttype'][0])]
|
result['result']['ipanttrusttype'][0], attributes)]
|
||||||
result['result']['trustdirection'] = [trust_direction_string(
|
result['result']['trustdirection'] = [trust_direction_string(
|
||||||
result['result']['ipanttrustdirection'][0])]
|
result['result']['ipanttrustdirection'][0])]
|
||||||
result['result']['truststatus'] = [trust_status_string(
|
result['result']['truststatus'] = [trust_status_string(
|
||||||
result['verified'])]
|
result['verified'])]
|
||||||
|
if attributes:
|
||||||
|
result['result'].pop('ipanttrustattributes', None)
|
||||||
|
|
||||||
del result['verified']
|
del result['verified']
|
||||||
result['result'].pop('ipanttrustauthoutgoing', None)
|
result['result'].pop('ipanttrustauthoutgoing', None)
|
||||||
@ -929,6 +950,11 @@ sides.
|
|||||||
trust_type = TRUST_ONEWAY
|
trust_type = TRUST_ONEWAY
|
||||||
if options.get('bidirectional', False):
|
if options.get('bidirectional', False):
|
||||||
trust_type = TRUST_BIDIRECTIONAL
|
trust_type = TRUST_BIDIRECTIONAL
|
||||||
|
|
||||||
|
# If we are forced to establish external trust, allow it
|
||||||
|
if options.get('external', False):
|
||||||
|
self.trustinstance.allow_behavior(TRUST_JOIN_EXTERNAL)
|
||||||
|
|
||||||
# 1. Full access to the remote domain. Use admin credentials and
|
# 1. Full access to the remote domain. Use admin credentials and
|
||||||
# generate random trustdom password to do work on both sides
|
# generate random trustdom password to do work on both sides
|
||||||
if full_join:
|
if full_join:
|
||||||
@ -1033,7 +1059,7 @@ class trust_mod(LDAPUpdate):
|
|||||||
class trust_find(LDAPSearch):
|
class trust_find(LDAPSearch):
|
||||||
__doc__ = _('Search for trusts.')
|
__doc__ = _('Search for trusts.')
|
||||||
has_output_params = LDAPSearch.has_output_params + trust_output_params +\
|
has_output_params = LDAPSearch.has_output_params + trust_output_params +\
|
||||||
(Str('ipanttrusttype'),)
|
(Str('ipanttrusttype'), Str('ipanttrustattributes'))
|
||||||
|
|
||||||
msg_summary = ngettext(
|
msg_summary = ngettext(
|
||||||
'%(count)d trust matched', '%(count)d trusts matched', 0
|
'%(count)d trust matched', '%(count)d trusts matched', 0
|
||||||
@ -1043,7 +1069,7 @@ class trust_find(LDAPSearch):
|
|||||||
# search needs to be done on a sub-tree scope
|
# search needs to be done on a sub-tree scope
|
||||||
def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
|
def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
|
||||||
# list only trust, not trust domains
|
# list only trust, not trust domains
|
||||||
trust_filter = '(ipaNTTrustPartner=*)'
|
trust_filter = '(&(ipaNTTrustPartner=*)(&(objectclass=ipaIDObject)(objectclass=ipaNTTrustedDomain)))'
|
||||||
filter = ldap.combine_filters((filters, trust_filter), rules=ldap.MATCH_ALL)
|
filter = ldap.combine_filters((filters, trust_filter), rules=ldap.MATCH_ALL)
|
||||||
return (filter, base_dn, ldap.SCOPE_SUBTREE)
|
return (filter, base_dn, ldap.SCOPE_SUBTREE)
|
||||||
|
|
||||||
@ -1060,10 +1086,13 @@ class trust_find(LDAPSearch):
|
|||||||
|
|
||||||
for attrs in entries:
|
for attrs in entries:
|
||||||
# Translate ipanttrusttype to trusttype if --raw not used
|
# Translate ipanttrusttype to trusttype if --raw not used
|
||||||
trust_type = attrs.get('ipanttrusttype', [None])[0]
|
trust_type = attrs.single_value.get('ipanttrusttype', None)
|
||||||
|
attributes = attrs.single_value.get('ipanttrustattributes', 0)
|
||||||
if not options.get('raw', False) and trust_type is not None:
|
if not options.get('raw', False) and trust_type is not None:
|
||||||
attrs['trusttype'] = trust_type_string(attrs['ipanttrusttype'][0])
|
attrs['trusttype'] = [trust_type_string(trust_type, attributes)]
|
||||||
del attrs['ipanttrusttype']
|
del attrs['ipanttrusttype']
|
||||||
|
if attributes:
|
||||||
|
del attrs['ipanttrustattributes']
|
||||||
|
|
||||||
return truncated
|
return truncated
|
||||||
|
|
||||||
@ -1071,7 +1100,7 @@ class trust_find(LDAPSearch):
|
|||||||
class trust_show(LDAPRetrieve):
|
class trust_show(LDAPRetrieve):
|
||||||
__doc__ = _('Display information about a trust.')
|
__doc__ = _('Display information about a trust.')
|
||||||
has_output_params = LDAPRetrieve.has_output_params + trust_output_params +\
|
has_output_params = LDAPRetrieve.has_output_params + trust_output_params +\
|
||||||
(Str('ipanttrusttype'), Str('ipanttrustdirection'))
|
(Str('ipanttrusttype'), Str('ipanttrustdirection'), Str('ipanttrustattributes'))
|
||||||
|
|
||||||
def execute(self, *keys, **options):
|
def execute(self, *keys, **options):
|
||||||
result = super(trust_show, self).execute(*keys, **options)
|
result = super(trust_show, self).execute(*keys, **options)
|
||||||
@ -1088,16 +1117,20 @@ class trust_show(LDAPRetrieve):
|
|||||||
# if --raw not used
|
# if --raw not used
|
||||||
|
|
||||||
if not options.get('raw', False):
|
if not options.get('raw', False):
|
||||||
trust_type = entry_attrs.get('ipanttrusttype', [None])[0]
|
trust_type = entry_attrs.single_value.get('ipanttrusttype', None)
|
||||||
|
attributes = entry_attrs.single_value.get('ipanttrustattributes', 0)
|
||||||
if trust_type is not None:
|
if trust_type is not None:
|
||||||
entry_attrs['trusttype'] = trust_type_string(trust_type)
|
entry_attrs['trusttype'] = [trust_type_string(trust_type, attributes)]
|
||||||
del entry_attrs['ipanttrusttype']
|
del entry_attrs['ipanttrusttype']
|
||||||
|
|
||||||
dir_str = entry_attrs.get('ipanttrustdirection', [None])[0]
|
dir_str = entry_attrs.single_value.get('ipanttrustdirection', None)
|
||||||
if dir_str is not None:
|
if dir_str is not None:
|
||||||
entry_attrs['trustdirection'] = [trust_direction_string(dir_str)]
|
entry_attrs['trustdirection'] = [trust_direction_string(dir_str)]
|
||||||
del entry_attrs['ipanttrustdirection']
|
del entry_attrs['ipanttrustdirection']
|
||||||
|
|
||||||
|
if attributes:
|
||||||
|
del entry_attrs['ipanttrustattributes']
|
||||||
|
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user