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

Alexander Bokovoy abokovoy at redhat.com
Thu Sep 19 19:08:37 UTC 2013


Hi!

Attached patch adds IPA CLI to manage trust subdomains.

ipa trust-domain-fetch <trust>   -- fetch list of subdomains from AD side and add new ones to IPA
ipa trust-domain-find <trust>    -- show all available subdomains 
ipa trust-domain-del <trust> <domain> -- remove subdomain from IPA view about <trust>
ipa trust-domain-mod <trust> <domain> -- modify subdomain parameters (work in progress)

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

The patch implements some dark magic to get around IPA framework
limitations:

  -- CLI commands belong to 'trust' family but operate on 'subdomain' object
  -- 'subdomain' objects are stored under trust container, thus making
     container_dn dependent on a particular trust:
      cn=<subdomain>,cn=<trust>,cn=ad,cn=trusts,$SUFFIX

The latter is a design decision since our KDC driver loads all objects
with objectclass=ipaNTTrustedDomain from cn=ad,cn=trusts,$SUFFIX using
subtree scope. With this design no changes were needed in ipa-kdb at all
to support subdomains.

-- 
/ Alexander Bokovoy
-------------- next part --------------
>From bf6145368cd517557f9839586cae32160291964e 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 5/5] trusts: support subdomains in a forest

Add IPA CLI to manage trust subdomains.

ipa trust-domain-fetch <trust>   -- fetch list of subdomains from AD side and add new ones to IPA
ipa trust-domain-find <trust>    -- show all available subdomains
ipa trust-domain-del <trust> <domain> -- remove subdomain from IPA view about <trust>
ipa trust-domain-mod <trust> <domain> -- modify subdomain parameters (work in progress)

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                 |  67 ++++++++++++++++++
 ipalib/plugins/trust.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++-
 ipaserver/dcerpc.py     |  54 +++++++++++++++
 3 files changed, 295 insertions(+), 2 deletions(-)

diff --git a/API.txt b/API.txt
index 761d1d1..a7c97e0 100644
--- a/API.txt
+++ b/API.txt
@@ -3423,6 +3423,73 @@ 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_domain_create
+args: 2,9,3
+arg: Str('trust?')
+arg: Str('cn?', cli_name='domain', primary_key=True)
+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?')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+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: trust_domain_del
+args: 2,2,3
+arg: Str('trust?')
+arg: Str('cn?', cli_name='domain', primary_key=True)
+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: trust_domain_fetch
+args: 1,4,2
+arg: Str('trust?')
+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_domain_find
+args: 2,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+arg: Str('trust?')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn?', cli_name='domain', primary_key=True)
+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: 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: trust_domain_mod
+args: 2,10,3
+arg: Str('trust?')
+arg: Str('cn?', cli_name='domain', primary_key=True)
+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: 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: trust_find
 args: 1,11,4
 arg: Str('criteria?', noextrawhitespace=False)
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 3c117b4..6ffe119 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -245,7 +245,7 @@ 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(dn, container_dn)
     return dn
 
 class trust_add(LDAPCreate):
@@ -738,7 +738,7 @@ class trust_show(LDAPRetrieve):
     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 make_trust_dn(self.env, options['trust_show_type'], DN(dn[0]))
 
         return dn
 
@@ -1078,3 +1078,175 @@ class sidgen_was_run(Command):
         return dict(result=True)
 
 api.register(sidgen_was_run)
+
+class subdomain(LDAPObject):
+    """
+    Object representing a domain of the AD trust.
+    """
+    trust_type_idx = {'2':u'ad'}
+    object_name = _('trust domain')
+    object_name_plural = _('trust domains')
+    object_class = ['ipaNTTrustedDomain']
+    default_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', 'ipanttrustdirection', 'ipanttrustpartner']
+    search_display_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', ]
+
+    label = _('Trusted domains')
+    label_singular = _('Trusted domain')
+
+    subdomain_args = (
+        Str('trust?',
+            label=_('AD trust'),
+            flags=('virtual_attribute',),
+        ),
+        Str('cn?',
+            label=_('Domain name'),
+            cli_name='domain',
+            primary_key=True,
+            flags=('req_update','req_create',),
+        ),
+    )
+
+    takes_params = (
+        Str('ipantflatname?',
+            cli_name='flat_name',
+            label=_('Domain NetBIOS name'),
+        ),
+        Str('ipanttrusteddomainsid?',
+            cli_name='sid',
+            label=_('Domain Security Identifier'),
+        ),
+    )
+
+    container_dn = api.env.container_trusts
+    def get_dn(self, *keys, **kwargs):
+        if len(keys) == 1:
+           return self.api.Object.trust.get_dn(*keys, **kwargs)
+        dn=make_trust_dn(self.env, getattr(kwargs, 'trust_type', u'ad'), DN(('cn', keys[1]), ('cn', keys[0])))
+        return dn
+
+api.register(subdomain)
+
+class trust_domain_find(LDAPSearch):
+    __doc__ = _('Search subdomains of the trust')
+
+    obj_name = 'subdomain'
+
+    takes_args = subdomain.subdomain_args[0]
+    takes_options = LDAPSearch.takes_options + (subdomain.subdomain_args[1],)
+    def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+        assert isinstance(base_dn, DN)
+        if not args[0]:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Trust name is required to search subdomains'))
+
+        base_dn = DN(self.api.Command.trust_show(args[0])['result']['dn'])
+        return (filters, base_dn, ldap.SCOPE_SUBTREE)
+api.register(trust_domain_find)
+
+class trust_domain_mod(LDAPUpdate):
+    __doc__ = _('Modify subdomain of the trust')
+
+    obj_name = 'subdomain'
+
+    takes_args = subdomain.subdomain_args
+    takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        if len(keys) != 2:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Trust name and subdomain name are required to modify the subdomain'))
+        return dn
+api.register(trust_domain_mod)
+
+class trust_domain_create(LDAPCreate):
+    __doc__ = _('Create subdomain of the trust')
+    NO_CLI = True
+    obj_name = 'subdomain'
+
+    takes_args = subdomain.subdomain_args
+    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 len(keys) != 2:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Trust name and subdomain name are required to create the subdomain'))
+        assert isinstance(dn, DN)
+        if 'ipanttrustpartner' in options:
+            entry_attrs['ipanttrustpartner'] = [options['ipanttrustpartner']]
+        self.log.error('trust_domain_create(%s,%s), entry attrs %s' % (keys,options,entry_attrs))
+        return dn
+api.register(trust_domain_create)
+
+class trust_domain_del(LDAPDelete):
+    __doc__ = _('Delete a subdomain of the trust.')
+
+    obj_name = 'subdomain'
+    msg_summary = _('Deleted subdomain "%(value)s"')
+
+    takes_args = subdomain.subdomain_args
+    def pre_callback(self, ldap, dn, *keys, **options):
+        if len(keys) != 2:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Trust name and subdomain name are required to delete the subdomain'))
+        try:
+            result = self.api.Command.trust_show(keys[0], raw=True)
+        except errors.NotFound, e:
+            self.obj.handle_not_found(*keys)
+        options['trust_type'] = self.obj.trust_type_idx[result['result']['ipanttrusttype'][0]]
+        return make_trust_dn(self.env, options['trust_type'],  DN(('cn', keys[1]), ('cn', keys[0])))
+api.register(trust_domain_del)
+
+class trust_domain_fetch(LDAPRetrieve):
+    __doc__ = _('Refresh list of subdomains associated with the trust')
+    obj_name = 'subdomain'
+
+    takes_args = (subdomain.subdomain_args[0])
+
+    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'
+                )
+            )
+        if len(keys) != 1:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Trust name is required to search subdomains'))
+
+        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 = ipaserver.dcerpc.fetch_domains(self.api, trustinstance.local_flatname, trust['cn'][0])
+        result = dict(result=[])
+        if not domains:
+            result['summary'] = unicode(_('No sudomains were detected during refresh'))
+            return result
+
+        for dom in domains:
+            dom['trust_type'] = self.obj.trust_type_idx[trust['ipanttrusttype'][0]]
+            try:
+                res = self.api.Command.trust_domain_create(trust['cn'][0], **dom)
+                result['result'].append(res['result'])
+            except errors.DuplicateEntry:
+                # Ignore updating duplicate entries
+                pass
+        if len(result['result']) > 0:
+            result['summary'] = unicode(_('Subdomains successfully refreshed'))
+        else:
+            result['summary'] = unicode(_('No new subdomains were found'))
+        return result
+api.register(trust_domain_fetch)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 33e7e07..fcfedc4 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -992,6 +992,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