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

Sumit Bose sbose at redhat.com
Fri Sep 20 15:40:05 UTC 2013


On Thu, Sep 19, 2013 at 10:08:37PM +0300, Alexander Bokovoy wrote:
> 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.

I just want to let you know that I successfully tested you patch with
sssd in a setup with the hierarchical case, i.e. the DNS name of the
subdomain is a child of the DNS name of the forest root. I also didn't
need to change the realm-domain mapping
(https://fedorahosted.org/sssd/ticket/2080) in this case. I will test
again with a non-hierarchical setup.

bye,
Sumit

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

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

> _______________________________________________
> Freeipa-devel mailing list
> Freeipa-devel at redhat.com
> https://www.redhat.com/mailman/listinfo/freeipa-devel




More information about the Freeipa-devel mailing list