[Freeipa-devel] [PATCH] 0118 add support for subdomains

Alexander Bokovoy abokovoy at redhat.com
Mon Sep 30 10:07:54 UTC 2013


On Mon, 30 Sep 2013, Jan Cholasta wrote:
>On 28.9.2013 22:01, Alexander Bokovoy wrote:
>>On Fri, 27 Sep 2013, Sumit Bose wrote:
>>>On Fri, Sep 27, 2013 at 03:53:08PM +0300, Alexander Bokovoy wrote:
>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>>>On Mon, 23 Sep 2013, Martin Kosek wrote:
>>>>>>>>>>However, we don't have trust type available so it needs to
>>>>discovered
>>>>>>>>>>every time. This doesn't play well with the framework, it is
>>>>simply not
>>>>>>>>>>expecting dynamic containers.
>>>>>>>>>
>>>>>>>>>This doesn't sound like a big obstacle to me. Right now the
>>>>trust_type lookup
>>>>>>>>>is done in trust_show.execute() for some reason, which is not
>>>>the best place to
>>>>>>>>>do it IMHO. Doing it in trust.get_dn() instead should simplify
>>>>things enough to
>>>>>>>>>make parent_object work.
>>>>>>>>
>>>>>>>>Yup, get_dn() is the method where object DN lookup should be
>>>>done. See for
>>>>>>>>example host.py plugin get_dn method, we also do a dynamic lookup
>>>>for correct
>>>>>>>>host name.
>>>>>>>I'll see if that would work.
>>>>>>>
>>>>>>>>the best way to implement dynamic DN gathering is the get_dn()
>>>>method. That
>>>>>>>>way, it could be implemented in one place and all commands could
>>>>take advantage
>>>>>>>>of it instead of re-implementing it several times in pre_callback
>>>>- this is
>>>>>>>>just hackish.
>>>>>>>I'd suggest you look into the code. The commands use pre_callback
>>>>for a
>>>>>>>different purpose than implementing dynamic DN gathering.
>>>>>>>
>>>>>>>>I think it would have been very useful to have a design page
>>>>before sending a
>>>>>>>>patch. It is then easier to make design decisions without having
>>>>to dig into
>>>>>>>>the patch.
>>>>>>>The design page is there for long time:
>>>>>>>http://www.freeipa.org/page/V3/Transitive_Trusts
>>>>>>Ok, here is new version of the patch and updated version of my 0117
>>>>>>patch as Sumit noticed I've sent wrong version.
>>>>>Ok, here is updated 0118 which fixes API.txt change for trustdomain_add
>>>>>-- I renamed trustdomain_create to trustdomain_add but forgot to rerun
>>>>>makeapi.
>>>>New edition attached for all subdomain-related patches:
>>>
>>>I did some tests and all is working as expected.
>>>
>>>>
>>>>freeipa-abbra-0117-ipaserver-dcerpc.py-populate-forest-trust-informatio-3.patch
>>>>
>>>>  Use realmdomains to report name suffix routes at the time we
>>>>establish trust
>>>>
>>>>freeipa-abbra-0118-trusts-support-subdomains-in-a-forest-3.patch
>>>>  Introduce trustdomain-* commands to fetch list of domains associated
>>>>  with a forest trust and allow filtering them off
>>>
>>>We talked on irc that ipaNTSupportedEncryptionTypes in the filter
>>>for the trusted domains should be replace by a different attribute.
>>>Because of an error in ipasam the ipaNTSupportedEncryptionTypes is only
>>>set in recent versions and might not be present in the directory trees of
>>>older versions.
>>Fixed in the attached patch 0118 version 4.
>>
>>Also attached first attempt to implement transiting through trusted
>>domains, as patch 0123. In this patch we grant transition only if all
>>three realms (client, transited realm, and server realm) match any of
>>our trusted domains and our domain. This is probably a bit wider but it
>>worked for me bidirectionally, from a child domain to a service in IPA,
>>and from IPA realm to a service in a child domain of a forest trust.
>>
>
>+        if not trust_type:
>+            for ttype in self.trust_types:
>+                dn=make_trust_dn(self.env, ttype, DN(*sdn))
>+                try:
>+                    object_exists = self.backend.get_entry(dn, [''])
>+                    return object_exists.dn
>+                except errors.NotFound:
>+                    pass
>+            # if no trust object of any type exist, default to 'ad'
>+            # this is required for *_add calls.
>+            trust_type = u'ad'
>
>This could be optimized to do a single LDAP search, i.e. something like:
>
>        if trust_type is None:
>            ldap = self.backend
>            filter = ldap.make_filter({'objectclass': 
>['ipaNTTrustedDomain'], 'cn': [keys[-1]]})
>            try:
>                result = ldap.get_entries(base_dn, 
>ldap.SCOPE_SUBTREE, filter, [''])
>            except errors.NotFound:
>                trust_type = u'ad'
>            else:
>                if len(result) > 1:
>                    raise errors.OnlyOneValueAllowed(attr='trust_type')
>                return result[0].dn
Fixed.

>
>
>+        # First see if the trust is already in place
>+        # Force retrieval of the trust object by not passing trust_type
>+        dn = self.obj.get_dn(keys[-1], {})
>
>The "{}" does not seem right to me (you are passing it as a second 
>key to get_dn, which is probably not what you want).
Simply removed {}.


>
>             (dn, attrs) = entry
>-
>+
>             # Translate ipanttrusttype to trusttype if --raw not used
>
>No trailing whitespace please.
Yep, removed.


>+    takes_options = LDAPCreate.takes_options + (_trust_type_option,
>+        Str('ipanttrustpartner?',
>+            label=_('Trusted domain partner'),
>+        ),
>+    )
>+
>+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, 
>**options):
>+        if 'ipanttrustpartner' in options:
>+            entry_attrs['ipanttrustpartner'] = 
>[options['ipanttrustpartner']]
>+        return dn
>
>Why is ipanttrustpartner a trustdomain_add option rather than 
>trustdomain virtual attribute?
Moved to takes_param in trustdomain object.

>+class trustdomain_fetch(LDAPRetrieve):
>
>I know I caused some confusion here, but I *do* think that this 
>single command should be in the trust namespace, as it is actually 
>method of the trust object (it takes a trust object name argument and 
>creates trustdomain objects for all the subdomains in the trust, 
>right?) I would suggest "trust_domain_fetch" or 
>"trust_fetch_domains".
Renamed to trust_fetch_domains.

>+class trustdomain_filter(LDAPRetrieve):
>
>It seems this command toggles the blacklist state of a trustdomain 
>object. AFAIK we don't have any toggle command in IPA and I am 
>opposed to the idea of introducing them - it's better to be explicit 
>than implicit. I think two commands would be better for this 
>("trustdomain_enable" and "trustdomain_disable" perhaps?)
Made two methods, though they are mostly duplicating each other's code.

-- 
/ Alexander Bokovoy
-------------- next part --------------
>From 776ade8b6827ce4825ac1c328d9442975e000c25 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 18 Sep 2013 17:04:19 +0200
Subject: [PATCH 2/8] trusts: support subdomains in a forest

Add IPA CLI to manage trust domains.

ipa trust-fetch-domains <trust>      -- fetch list of subdomains from AD side and add new ones to IPA
ipa trustdomain-find <trust>         -- show all available domains
ipa trustdomain-del <trust> <domain> -- remove domain from IPA view about <trust>
ipa trustdomain-enable <trust> <domain> -- allow users from trusted domain to access resources in IPA
ipa trustdomain-disable <trust> <domain> -- disable access to resources in IPA from trusted domain

By default all discovered trust domains are allowed to access IPA resources

IPA KDC needs also information for authentication paths to subdomains in case they
are not hierarchical under AD forest trust root. This information is managed via capaths
section in krb5.conf. SSSD should be able to generate it once
ticket https://fedorahosted.org/sssd/ticket/2093 is resolved.

part of https://fedorahosted.org/freeipa/ticket/3909
---
 API.txt                 |  81 ++++++++++++
 ipalib/plugins/trust.py | 336 ++++++++++++++++++++++++++++++++++++++++--------
 ipaserver/dcerpc.py     |  54 ++++++++
 3 files changed, 420 insertions(+), 51 deletions(-)

diff --git a/API.txt b/API.txt
index 761d1d1..b06da30 100644
--- a/API.txt
+++ b/API.txt
@@ -3423,6 +3423,15 @@ option: Str('version?', exclude='webui')
 output: Output('result', <type 'dict'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, None)
+command: trust_fetch_domains
+args: 1,4,2
+arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 command: trust_find
 args: 1,11,4
 arg: Str('criteria?', noextrawhitespace=False)
@@ -3497,6 +3506,78 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, None)
+command: trustdomain_add
+args: 2,8,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('ipantflatname', attribute=True, cli_name='flat_name', multivalue=False, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, cli_name='sid', multivalue=False, required=False)
+option: Str('ipanttrustpartner', attribute=True, cli_name='ipanttrustpartner', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
+command: trustdomain_del
+args: 2,2,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
+command: trustdomain_disable
+args: 2,2,1
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+command: trustdomain_enable
+args: 2,2,1
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+command: trustdomain_find
+args: 2,8,4
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('ipantflatname', attribute=True, autofill=False, cli_name='flat_name', multivalue=False, query=True, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='sid', multivalue=False, query=True, required=False)
+option: Str('ipanttrustpartner', attribute=True, autofill=False, cli_name='ipanttrustpartner', multivalue=False, query=True, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: trustdomain_mod
+args: 2,11,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('ipantflatname', attribute=True, autofill=False, cli_name='flat_name', multivalue=False, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='sid', multivalue=False, required=False)
+option: Str('ipanttrustpartner', attribute=True, autofill=False, cli_name='ipanttrustpartner', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: StrEnum('trust_type', autofill=True, cli_name='type', default=u'ad', values=(u'ad',))
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
 command: user_add
 args: 1,35,3
 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, required=True)
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 3c117b4..dd2c02a 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -181,6 +181,13 @@ def trust_status_string(level):
     string = _trust_status_dict.get(level, _trust_type_dict_unknown)
     return unicode(string)
 
+def make_trust_dn(env, trust_type, dn):
+    assert isinstance(dn, DN)
+    if trust_type:
+        container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn)
+        return DN(dn, container_dn)
+    return dn
+
 class trust(LDAPObject):
     """
     Trust object.
@@ -195,7 +202,8 @@ class trust(LDAPObject):
         'ipantauthtrustoutgoing', 'ipanttrustauthincoming', 'ipanttrustforesttrustinfo',
         'ipanttrustposixoffset', 'ipantsupportedencryptiontypes' ]
     search_display_attributes = ['cn', 'ipantflatname',
-                                 'ipanttrusteddomainsid', 'ipanttrusttype' ]
+                                 'ipanttrusteddomainsid', 'ipanttrusttype',
+                                 'ipantsidblacklistincoming', 'ipantsidblacklistoutgoing' ]
 
     label = _('Trusts')
     label_singular = _('Trust')
@@ -241,12 +249,26 @@ class trust(LDAPObject):
                     raise errors.ValidationError(name=attr,
                             error=_("invalid SID: %(value)s") % dict(value=value))
 
-def make_trust_dn(env, trust_type, dn):
-    assert isinstance(dn, DN)
-    if trust_type in trust.trust_types:
-        container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn)
-        return DN(dn[0], container_dn)
-    return dn
+    def get_dn(self, *keys, **kwargs):
+        sdn = map(lambda x: ('cn', x), keys)
+        sdn.reverse()
+        trust_type = kwargs.get('trust_type')
+        if trust_type is None:
+            ldap = self.backend
+            filter = ldap.make_filter({'objectclass': ['ipaNTTrustedDomain'], 'cn': [keys[-1]]})
+            filter = ldap.combine_filters((filter, "ipaNTSIDBlacklistIncoming=*"), rules=ldap.MATCH_ALL)
+            try:
+                result = ldap.get_entries(DN(self.container_dn, self.env.basedn),
+                                          ldap.SCOPE_SUBTREE, filter, [''])
+            except errors.NotFound:
+                trust_type = u'ad'
+            else:
+                if len(result) > 1:
+                    raise errors.OnlyOneValueAllowed(attr='trust domain')
+                return result[0].dn
+
+        dn=make_trust_dn(self.env, trust_type, DN(*sdn))
+        return dn
 
 class trust_add(LDAPCreate):
     __doc__ = _('''
@@ -576,10 +598,13 @@ sides.
     def execute_ad(self, full_join, *keys, **options):
         # Join domain using full credentials and with random trustdom
         # secret (will be generated by the join method)
-        try:
-            api.Command['trust_show'](keys[-1])
+
+        # First see if the trust is already in place
+        # Force retrieval of the trust object by not passing trust_type
+        dn = self.obj.get_dn(keys[-1])
+        if dn:
             summary = _('Re-established trust to domain "%(value)s"')
-        except errors.NotFound:
+        else:
             summary = self.msg_summary
 
         # 1. Full access to the remote domain. Use admin credentials and
@@ -652,14 +677,6 @@ class trust_del(LDAPDelete):
 
     msg_summary = _('Deleted trust "%(value)s"')
 
-    def pre_callback(self, ldap, dn, *keys, **options):
-        assert isinstance(dn, DN)
-        try:
-            result = self.api.Command.trust_show(keys[-1])
-        except errors.NotFound, e:
-            self.obj.handle_not_found(*keys)
-        return result['result']['dn']
-
 class trust_mod(LDAPUpdate):
     __doc__ = _("""
     Modify a trust (for future use).
@@ -673,16 +690,10 @@ class trust_mod(LDAPUpdate):
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
-        result = None
-        try:
-            result = self.api.Command.trust_show(keys[-1])
-        except errors.NotFound, e:
-            self.obj.handle_not_found(*keys)
 
         self.obj.validate_sid_blacklists(entry_attrs)
 
-        # TODO: we found the trust object, now modify it
-        return result['result']['dn']
+        return dn
 
 class trust_find(LDAPSearch):
     __doc__ = _('Search for trusts.')
@@ -696,8 +707,10 @@ class trust_find(LDAPSearch):
     # Since all trusts types are stored within separate containers under 'cn=trusts',
     # search needs to be done on a sub-tree scope
     def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
-        assert isinstance(base_dn, DN)
-        return (filters, base_dn, ldap.SCOPE_SUBTREE)
+        # list only trust, not trust domains
+        trust_filter = '(ipaNTSIDBlacklistIncoming=*)'
+        filter = ldap.combine_filters((filters, trust_filter), rules=ldap.MATCH_ALL)
+        return (filter, base_dn, ldap.SCOPE_SUBTREE)
 
     def post_callback(self, ldap, entries, truncated, *args, **options):
         if options.get('pkey_only', False):
@@ -718,30 +731,6 @@ class trust_show(LDAPRetrieve):
     has_output_params = LDAPRetrieve.has_output_params + trust_output_params +\
                         (Str('ipanttrusttype'), Str('ipanttrustdirection'))
 
-    def execute(self, *keys, **options):
-        error = None
-        result = None
-        for trust_type in trust.trust_types:
-            options['trust_show_type'] = trust_type
-            try:
-                result = super(trust_show, self).execute(*keys, **options)
-            except errors.NotFound, e:
-                result = None
-                error = e
-            if result:
-                break
-        if error or not result:
-            self.obj.handle_not_found(*keys)
-
-        return result
-
-    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        assert isinstance(dn, DN)
-        if 'trust_show_type' in options:
-            return make_trust_dn(self.env, options['trust_show_type'], dn)
-
-        return dn
-
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
 
         # Translate ipanttrusttype to trusttype
@@ -1078,3 +1067,248 @@ class sidgen_was_run(Command):
         return dict(result=True)
 
 api.register(sidgen_was_run)
+
+class trustdomain(LDAPObject):
+    """
+    Object representing a domain of the AD trust.
+    """
+    parent_object = 'trust'
+    trust_type_idx = {'2':u'ad'}
+    object_name = _('trust domain')
+    object_name_plural = _('trust domains')
+    object_class = ['ipaNTTrustedDomain']
+    default_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', 'ipanttrustpartner']
+    search_display_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', ]
+
+    label = _('Trusted domains')
+    label_singular = _('Trusted domain')
+
+    # We do not add this argument implicitly via takes_args in the object.
+    # Rather, it is added explicitly in the commands that require second arg
+    # because first argument will be inherited from the 'trust' parent object
+    trustdomain_args = (
+        Str('cn',
+            label=_('Domain name'),
+            cli_name='domain',
+        ),
+    )
+
+    takes_params = (
+        Str('ipantflatname?',
+            cli_name='flat_name',
+            label=_('Domain NetBIOS name'),
+        ),
+        Str('ipanttrusteddomainsid?',
+            cli_name='sid',
+            label=_('Domain Security Identifier'),
+        ),
+        Str('ipanttrustpartner?',
+            label=_('Trusted domain partner'),
+            flags=['no_display', 'no_option'],
+        ),
+    )
+
+    # LDAPObject.get_dn() only passes all but last element of keys and no kwargs
+    # to the parent object's get_dn() no matter what you pass to it. Make own get_dn()
+    # as we really need all elements to construct proper dn.
+    def get_dn(self, *keys, **kwargs):
+        sdn = map(lambda x: ('cn', x), keys)
+        sdn.reverse()
+        trust_type = kwargs.get('trust_type')
+        if not trust_type:
+            trust_type=u'ad'
+
+        dn=make_trust_dn(self.env, trust_type, DN(*sdn))
+        return dn
+api.register(trustdomain)
+
+class trustdomain_find(LDAPSearch):
+    __doc__ = _('Search domains of the trust')
+
+    has_output_params = LDAPSearch.has_output_params + trustdomain.trustdomain_args
+    def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+        return (filters, base_dn, ldap.SCOPE_SUBTREE)
+api.register(trustdomain_find)
+
+class trustdomain_mod(LDAPUpdate):
+    __doc__ = _('Modify trustdomain of the trust')
+
+    NO_CLI = True
+    takes_args = trustdomain.trustdomain_args
+    takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
+api.register(trustdomain_mod)
+
+class trustdomain_add(LDAPCreate):
+    __doc__ = _('Allow access from the trusted domain')
+    NO_CLI = True
+
+    takes_args = trustdomain.trustdomain_args
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        if 'ipanttrustpartner' in options:
+            entry_attrs['ipanttrustpartner'] = [options['ipanttrustpartner']]
+        return dn
+api.register(trustdomain_add)
+
+class trustdomain_del(LDAPDelete):
+    __doc__ = _('Remove infromation about the domain associated with the trust.')
+
+    msg_summary = _('Removed information about the trusted domain "%(value)s"')
+
+    takes_args = trustdomain.trustdomain_args
+    def execute(self, *keys, **options):
+        result = super(trustdomain_del, self).execute(*keys, **options)
+        result['value'] = keys[1]
+        return result
+
+
+api.register(trustdomain_del)
+
+
+def fetch_domains_from_trust(self, trustinstance, trust_entry):
+    trust_name = trust_entry['cn'][0]
+    domains = ipaserver.dcerpc.fetch_domains(self.api, trustinstance.local_flatname, trust_name)
+    result = []
+    if not domains:
+        return None
+
+    for dom in domains:
+        dom['trust_type'] = u'ad'
+        try:
+            name = dom['cn']
+            del dom['cn']
+            res = self.api.Command.trustdomain_add(trust_name, name, **dom)
+            result.append(res['result'])
+        except errors.DuplicateEntry:
+            # Ignore updating duplicate entries
+            pass
+    return result
+
+class trust_fetch_domains(LDAPRetrieve):
+    __doc__ = _('Refresh list of the domains associated with the trust')
+
+    has_output = (
+        output.ListOfEntries('result'),
+        output.summary
+    )
+
+    def execute(self, *keys, **options):
+        if not _bindings_installed:
+            raise errors.NotFound(
+                name=_('AD Trust setup'),
+                reason=_(
+                    'Cannot perform join operation without Samba 4 support '
+                    'installed. Make sure you have installed server-trust-ad '
+                    'sub-package of IPA'
+                )
+            )
+        trust = self.api.Command.trust_show(keys[0], raw=True)['result']
+
+        trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
+        if not trustinstance.configured:
+            raise errors.NotFound(
+                name=_('AD Trust setup'),
+                reason=_(
+                    'Cannot perform join operation without own domain '
+                    'configured. Make sure you have run ipa-adtrust-install '
+                    'on the IPA server first'
+                )
+            )
+        domains = fetch_domains_from_trust(self, trustinstance, trust)
+        result = dict(result=[])
+        if not domains:
+            result['summary'] = unicode(_('No trust domains were detected during refresh'))
+            return result
+
+        if len(domains) > 0:
+            result['summary'] = unicode(_('List of trust domains successfully refreshed'))
+        else:
+            result['summary'] = unicode(_('No new trust domains were found'))
+
+        result['result'] = domains
+        return result
+api.register(trust_fetch_domains)
+
+class trustdomain_enable(LDAPRetrieve):
+    __doc__ = _('Allow use of IPA resources by the domain of the trust')
+
+    has_output = (
+        output.summary,
+    )
+
+    takes_args = trustdomain.trustdomain_args
+
+    def execute(self, *keys, **options):
+        ldap = self.api.Backend.ldap2
+
+        try:
+            trust = super(trustdomain_filter, self).execute(keys[0], **options)
+            trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
+            trust_entry = ldap.get_entry(trust_dn)
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust name is required'))
+
+        result = dict()
+        result['trust'] = unicode(keys[0])
+        result['domain'] = unicode(keys[1])
+        dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
+        try:
+            entry = ldap.get_entry(dn)
+            sid = entry['ipanttrusteddomainsid'][0]
+            if sid in trust_entry['ipantsidblacklistincoming']:
+                trust_entry['ipantsidblacklistincoming'].remove(sid)
+                result['action'] = _('is allowed')
+                ldap.update_entry(trust_entry)
+            else:
+                result['action'] = _('is already allowed')
+            result['summary'] = _('Domain %(domain)s of trust %(trust)s %(action)s to access IPA resources')
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust domain name is required'))
+        except errors.EmptyModlist:
+            result['summary'] = _('No changes were done')
+
+        return dict(summary=result['summary'] % result)
+
+api.register(trustdomain_enable)
+
+class trustdomain_disable(LDAPRetrieve):
+    __doc__ = _('Disable use of IPA resources by the domain of the trust')
+
+    has_output = (
+        output.summary,
+    )
+
+    takes_args = trustdomain.trustdomain_args
+
+    def execute(self, *keys, **options):
+        ldap = self.api.Backend.ldap2
+
+        try:
+            trust = super(trustdomain_filter, self).execute(keys[0], **options)
+            trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
+            trust_entry = ldap.get_entry(trust_dn)
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust name is required'))
+
+        result = dict()
+        result['trust'] = unicode(keys[0])
+        result['domain'] = unicode(keys[1])
+        dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
+        try:
+            entry = ldap.get_entry(dn)
+            sid = entry['ipanttrusteddomainsid'][0]
+            if not (sid in trust_entry['ipantsidblacklistincoming']):
+                trust_entry['ipantsidblacklistincoming'].append(sid)
+                result['action'] = _('is not allowed')
+                ldap.update_entry(trust_entry)
+            else:
+                result['action'] = _('is already not allowed')
+            result['summary'] = _('Domain %(domain)s of trust %(trust)s %(action)s to access IPA resources')
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust domain name is required'))
+        except errors.EmptyModlist:
+            result['summary'] = _('No changes were done')
+
+        return dict(summary=result['summary'] % result)
+
+api.register(trustdomain_disable)
+
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index c24230b..af0e77e 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1002,6 +1002,60 @@ class TrustDomainInstance(object):
             return True
         return False
 
+def fetch_domains(api, mydomain, trustdomain):
+    trust_flags = dict(
+                NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
+                NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
+                NETR_TRUST_FLAG_TREEROOT  = 0x00000004,
+                NETR_TRUST_FLAG_PRIMARY   = 0x00000008,
+                NETR_TRUST_FLAG_NATIVE    = 0x00000010,
+                NETR_TRUST_FLAG_INBOUND   = 0x00000020,
+                NETR_TRUST_FLAG_MIT_KRB5  = 0x00000080,
+                NETR_TRUST_FLAG_AES       = 0x00000100)
+
+    trust_attributes = dict(
+                NETR_TRUST_ATTRIBUTE_NON_TRANSITIVE     = 0x00000001,
+                NETR_TRUST_ATTRIBUTE_UPLEVEL_ONLY       = 0x00000002,
+                NETR_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN = 0x00000004,
+                NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE  = 0x00000008,
+                NETR_TRUST_ATTRIBUTE_CROSS_ORGANIZATION = 0x00000010,
+                NETR_TRUST_ATTRIBUTE_WITHIN_FOREST      = 0x00000020,
+                NETR_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL  = 0x00000040)
+
+    domval = DomainValidator(api)
+    (ccache_name, principal) = domval.kinit_as_http(trustdomain)
+    if ccache_name:
+        with installutils.private_ccache(path=ccache_name):
+            td = TrustDomainInstance('')
+            td.parm.set('workgroup', mydomain)
+            td.creds = credentials.Credentials()
+            td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+            td.creds.guess(td.parm)
+            netrc = net.Net(creds=td.creds, lp=td.parm)
+            try:
+                result = netrc.finddc(domain=trustdomain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+            except RuntimeError, e:
+                raise assess_dcerpc_exception(message=str(e))
+            if not result:
+                return None
+            td.retrieve(unicode(result.pdc_dns_name))
+
+            netr_pipe = netlogon.netlogon(td.binding, td.parm, td.creds)
+            domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
+
+            result = []
+            for t in domains.array:
+                if ((t.trust_attributes & trust_attributes['NETR_TRUST_ATTRIBUTE_WITHIN_FOREST']) and
+                    (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
+                    res = dict()
+                    res['cn'] = unicode(t.dns_name)
+                    res['ipantflatname'] = unicode(t.netbios_name)
+                    res['ipanttrusteddomainsid'] = unicode(t.sid)
+                    res['ipanttrustpartner'] = res['cn']
+                    result.append(res)
+            return result
+
+
 class TrustDomainJoins(object):
     def __init__(self, api):
         self.api = api
-- 
1.8.3.1



More information about the Freeipa-devel mailing list