[Freeipa-devel] [PATCH] 0118 add support for subdomains
Sumit Bose
sbose at redhat.com
Fri Sep 27 16:31:21 UTC 2013
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.
bye,
Sumit
>
> freeipa-abbra-0119-frontend-report-arguments-errors-with-better-detail.patch
> Small fix to error reporting in parameters validation to show what
> command produced an error and what was the context for it
>
> freeipa-abbra-0120-ipaserver-dcerpc-remove-use-of-trust-account-authent.patch
> Removal of trust account password use. We don't need it anymore as
> KDC is now capable to produce MS-PAC for HTTP/ipa.master principal
>
> freeipa-abbra-0121-trust-integrate-subdomains-support-into-trust-add.patch
> Add automation for trust subdomains -- discover them at the time of
> establishing trust and add ranges for non-POSIX case. In POSIX
> attributes case no ranges are needed as trust idrange is used
> instead.
>
> freeipa-abbra-0122-ipasam-for-subdomains-pick-up-defaults-for-missing-v.patch
> Make sure to fill in missing details for subdomains. Subdomains are
> stored in the LDAP tree without information about trust type, direction,
> and attributes as they are the same for subdomains and the trust root
> domain.
>
>
> --
> / Alexander Bokovoy
> >From 5d0c5e8c4373fcfc92b4fa5113a554debb435073 Mon Sep 17 00:00:00 2001
> From: Alexander Bokovoy <abokovoy at redhat.com>
> Date: Wed, 11 Sep 2013 21:34:55 +0300
> Subject: [PATCH 1/7] ipaserver/dcerpc.py: populate forest trust information
> using realmdomains
>
> Use realmdomains information to prepopulate forest trust info. As result,
> all additional domains should now be enabled from the beginning, unless they
> really conflict with existing DNS domains on AD side.
>
> https://fedorahosted.org/freeipa/ticket/3919
> ---
> ipaserver/dcerpc.py | 113 +++++++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 95 insertions(+), 18 deletions(-)
>
> diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
> index bd8f5aa..c24230b 100644
> --- a/ipaserver/dcerpc.py
> +++ b/ipaserver/dcerpc.py
> @@ -39,7 +39,7 @@ import uuid
> from samba import param
> from samba import credentials
> from samba.dcerpc import security, lsa, drsblobs, nbt, netlogon
> -from samba.ndr import ndr_pack
> +from samba.ndr import ndr_pack, ndr_print
> from samba import net
> import samba
> import random
> @@ -684,6 +684,12 @@ class DomainValidator(object):
> self._info[domain] = info
> return info
>
> +def string_to_array(what):
> + blob = [0] * len(what)
> +
> + for i in range(len(what)):
> + blob[i] = ord(what[i])
> + return blob
>
> class TrustDomainInstance(object):
>
> @@ -698,6 +704,7 @@ class TrustDomainInstance(object):
> self._pipe = None
> self._policy_handle = None
> self.read_only = False
> + self.ftinfo_records = None
>
> def __gen_lsa_connection(self, binding):
> if self.creds is None:
> @@ -827,12 +834,6 @@ class TrustDomainInstance(object):
> def arcfour_encrypt(key, data):
> c = RC4.RC4(key)
> return c.update(data)
> - def string_to_array(what):
> - blob = [0] * len(what)
> -
> - for i in range(len(what)):
> - blob[i] = ord(what[i])
> - return blob
>
> password_blob = string_to_array(trustdom_secret.encode('utf-16-le'))
>
> @@ -876,6 +877,53 @@ class TrustDomainInstance(object):
> self.auth_info = auth_info
>
>
> + def generate_ftinfo(self, another_domain):
> + """
> + Generates TrustDomainInfoFullInfo2Internal structure
> + This structure allows to pass information about all domains associated
> + with the another domain's realm.
> +
> + Only top level name and top level name exclusions are handled here.
> + """
> + if not another_domain.ftinfo_records:
> + return
> +
> + ftinfo_records = []
> + info = lsa.ForestTrustInformation()
> +
> + for rec in another_domain.ftinfo_records:
> + record = lsa.ForestTrustRecord()
> + record.flags = 0
> + record.time = rec['rec_time']
> + record.type = rec['rec_type']
> + record.forest_trust_data.string = rec['rec_name']
> + ftinfo_records.append(record)
> +
> + info.count = len(ftinfo_records)
> + info.entries = ftinfo_records
> + return info
> +
> + def update_ftinfo(self, another_domain):
> + """
> + Updates forest trust information in this forest corresponding
> + to the another domain's information.
> + """
> + try:
> + if another_domain.ftinfo_records:
> + ftinfo = self.generate_ftinfo(another_domain)
> + # Set forest trust information -- we do it only against AD DC as
> + # smbd already has the information about itself
> + ldname = lsa.StringLarge()
> + ldname.string = another_domain.info['dns_domain']
> + collision_info = self._pipe.lsaRSetForestTrustInformation(self._policy_handle,
> + ldname,
> + lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
> + ftinfo, 0)
> + if collision_info:
> + root_logger.error("When setting forest trust information, got collision info back:\n%s" % (ndr_print(collision_info)))
> + except RuntimeError, e:
> + # We can ignore the error here -- setting up name suffix routes may fail
> + pass
>
> def establish_trust(self, another_domain, trustdom_secret):
> """
> @@ -883,6 +931,12 @@ class TrustDomainInstance(object):
> Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
> trustdom_secret -- shared secred used for the trust
> """
> + if self.info['name'] == another_domain.info['name']:
> + # Check that NetBIOS names do not clash
> + raise errors.ValidationError(name=u'AD Trust Setup',
> + error=_('the IPA server and the remote domain cannot share the same '
> + 'NetBIOS name: %s') % self.info['name'])
> +
> self.generate_auth(trustdom_secret)
>
> info = lsa.TrustDomainInfoInfoEx()
> @@ -893,12 +947,6 @@ class TrustDomainInstance(object):
> info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
> info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
>
> - if self.info['name'] == info.netbios_name.string:
> - # Check that NetBIOS names do not clash
> - raise errors.ValidationError(name=u'AD Trust Setup',
> - error=_('the IPA server and the remote domain cannot share the same '
> - 'NetBIOS name: %s') % self.info['name'])
> -
> try:
> dname = lsa.String()
> dname.string = another_domain.info['dns_domain']
> @@ -911,12 +959,14 @@ class TrustDomainInstance(object):
> except RuntimeError, (num, message):
> raise assess_dcerpc_exception(num=num, message=message)
>
> + self.update_ftinfo(another_domain)
> +
> + # We should use proper trustdom handle in order to modify the
> + # trust settings. Samba insists this has to be done with LSA
> + # OpenTrustedDomain* calls, it is not enough to have a handle
> + # returned by the CreateTrustedDomainEx2 call.
> + trustdom_handle = self._pipe.OpenTrustedDomainByName(self._policy_handle, dname, security.SEC_FLAG_MAXIMUM_ALLOWED)
> try:
> - # We should use proper trustdom handle in order to modify the
> - # trust settings. Samba insists this has to be done with LSA
> - # OpenTrustedDomain* calls, it is not enough to have a handle
> - # returned by the CreateTrustedDomainEx2 call.
> - trustdom_handle = self._pipe.OpenTrustedDomainByName(self._policy_handle, dname, security.SEC_FLAG_MAXIMUM_ALLOWED)
> infoclass = lsa.TrustDomainInfoSupportedEncTypes()
> infoclass.enc_types = security.KERB_ENCTYPE_RC4_HMAC_MD5
> infoclass.enc_types |= security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
> @@ -1016,6 +1066,32 @@ class TrustDomainJoins(object):
> # Otherwise, use anonymously obtained data
> self.remote_domain = rd
>
> + def get_realmdomains(self):
> + """
> + Generate list of records for forest trust information about
> + our realm domains. Note that the list generated currently
> + includes only top level domains, no exclusion domains, and no TDO objects
> + as we handle the latter in a separte way
> + """
> + if self.local_domain.read_only:
> + return
> +
> + self.local_domain.ftinfo_records = []
> +
> + realm_domains = self.api.Command.realmdomains_show()['result']
> + trustconfig = self.api.Command.trustconfig_show()['result']
> + # Use realmdomains' modification timestamp to judge records last update time
> + (dn, entry_attrs) = self.api.Backend.ldap2.get_entry(realm_domains['dn'], ['modifyTimestamp'])
> + # Convert the timestamp to Windows 64-bit timestamp format
> + trust_timestamp = long(time.mktime(time.strptime(entry_attrs['modifytimestamp'][0][:14], "%Y%m%d%H%M%S"))*1e7+116444736000000000)
> +
> + for dom in realm_domains['associateddomain']:
> + ftinfo = dict()
> + ftinfo['rec_name'] = dom
> + ftinfo['rec_time'] = trust_timestamp
> + 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):
> if not self.configured:
> return None
> @@ -1030,6 +1106,7 @@ 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)
> --
> 1.8.3.1
>
> >From b077e4a0613fff8830388efab60ff0966877a7f6 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/7] trusts: support subdomains in a forest
>
> Add IPA CLI to manage trust domains.
>
> ipa trustdomain-fetch <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 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 | 73 ++++++++++++
> ipalib/plugins/trust.py | 295 +++++++++++++++++++++++++++++++++++++++---------
> ipaserver/dcerpc.py | 54 +++++++++
> 3 files changed, 370 insertions(+), 52 deletions(-)
>
> diff --git a/API.txt b/API.txt
> index 761d1d1..674ff6c 100644
> --- a/API.txt
> +++ b/API.txt
> @@ -3497,6 +3497,79 @@ 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,9,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?')
> +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: 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_fetch
> +args: 1,4,2
> +arg: Str('trustcn', cli_name='trust', 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: trustdomain_filter
> +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,7,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: 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,10,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: 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..bb3a023 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,24 @@ 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 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'
> +
> + dn=make_trust_dn(self.env, trust_type, DN(*sdn))
> + return dn
>
> class trust_add(LDAPCreate):
> __doc__ = _('''
> @@ -576,10 +596,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 +675,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 +688,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 +705,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 = '(ipaNTSupportedEncryptionTypes=*)'
> + 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):
> @@ -705,7 +716,7 @@ class trust_find(LDAPSearch):
>
> for entry in entries:
> (dn, attrs) = entry
> -
> +
> # Translate ipanttrusttype to trusttype if --raw not used
> if not options.get('raw', False):
> attrs['trusttype'] = trust_type_string(attrs['ipanttrusttype'][0])
> @@ -718,30 +729,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 +1065,207 @@ 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'),
> + ),
> + )
> +
> + # 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')
> +
> + 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
> + 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
> +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 trustdomain_fetch(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(trustdomain_fetch)
> +
> +class trustdomain_filter(LDAPRetrieve):
> + __doc__ = _('Disable/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'] = _('allowed')
> + else:
> + trust_entry['ipantsidblacklistincoming'].append(sid)
> + result['action'] = _('not allowed')
> + ldap.update_entry(trust_entry)
> + result['summary'] = _('Domain %(domain)s of trust %(trust)s is %(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_filter)
> 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
>
> >From 9155190367c0543533746e6f5bc01c2609b4ec01 Mon Sep 17 00:00:00 2001
> From: Alexander Bokovoy <abokovoy at redhat.com>
> Date: Thu, 26 Sep 2013 16:44:37 +0200
> Subject: [PATCH 3/7] frontend: report arguments errors with better detail
>
> When reporting argument errors, show also a context -- what is processed,
> what is the name of the command.
> ---
> ipalib/frontend.py | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/ipalib/frontend.py b/ipalib/frontend.py
> index cac3e3b..f478ef0 100644
> --- a/ipalib/frontend.py
> +++ b/ipalib/frontend.py
> @@ -869,7 +869,8 @@ class Command(HasParam):
> for arg in args():
> if optional and arg.required:
> raise ValueError(
> - '%s: required argument after optional' % arg.name
> + '%s: required argument after optional in %s arguments %s' % (arg.name,
> + self.name, map(lambda x: x.param_spec, args()))
> )
> if multivalue:
> raise ValueError(
> --
> 1.8.3.1
>
> >From 0f91393b2c02a864eb4cc59f9a45ef466f24be56 Mon Sep 17 00:00:00 2001
> From: Alexander Bokovoy <abokovoy at redhat.com>
> Date: Fri, 27 Sep 2013 12:36:59 +0200
> Subject: [PATCH 4/7] ipaserver/dcerpc: remove use of trust account
> authentication
>
> Since FreeIPA KDC supports adding MS-PAC to HTTP/ipa.server principal,
> it is possible to use it when talking to the trusted AD DC.
>
> Remove support for authenticating as trust account because it should not
> really be used other than within Samba.
> ---
> ipalib/plugins/trust.py | 1 -
> ipaserver/dcerpc.py | 71 ++++---------------------------------------------
> 2 files changed, 5 insertions(+), 67 deletions(-)
>
> diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
> index bb3a023..06ebd92 100644
> --- a/ipalib/plugins/trust.py
> +++ b/ipalib/plugins/trust.py
> @@ -536,7 +536,6 @@ sides.
> None,
> SCOPE_SUBTREE,
> basedn=info_dn,
> - use_http=True,
> quiet=True)
>
> if info_list:
> diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
> index af0e77e..fa5c449 100644
> --- a/ipaserver/dcerpc.py
> +++ b/ipaserver/dcerpc.py
> @@ -165,8 +165,7 @@ class DomainValidator(object):
> base_dn=cn_trust,
> attrs_list=[self.ATTR_TRUSTED_SID,
> self.ATTR_FLATNAME,
> - self.ATTR_TRUST_PARTNER,
> - self.ATTR_TRUST_AUTHOUT]
> + self.ATTR_TRUST_PARTNER]
> )
>
> # We need to use case-insensitive dictionary since we use
> @@ -185,18 +184,8 @@ class DomainValidator(object):
> "attribute: %s", dn, e)
> continue
>
> - trust_authout = entry.get(self.ATTR_TRUST_AUTHOUT, [None])[0]
> -
> - # We were able to read all Trusted domain attributes but the
> - # secret User is not member of trust admins group
> - if trust_authout is None:
> - raise errors.ACIError(
> - info=_('communication with trusted domains is allowed '
> - 'for Trusts administrator group members only'))
> -
> result[trust_partner] = (flatname_normalized,
> - security.dom_sid(trusted_sid),
> - trust_authout)
> + security.dom_sid(trusted_sid))
> return result
> except errors.NotFound, e:
> return []
> @@ -462,43 +451,6 @@ class DomainValidator(object):
> ]
> return u'S-%d-%d-%s' % ( sid_rev_num, ia, '-'.join([str(s) for s in subs]),)
>
> - def __extract_trusted_auth(self, info):
> - """
> - Returns in clear trusted domain account credentials
> - """
> - clear = None
> - auth = drsblobs.trustAuthInOutBlob()
> - auth.__ndr_unpack__(info['auth'])
> - auth_array = auth.current.array[0]
> - if auth_array.AuthType == lsa.TRUST_AUTH_TYPE_CLEAR:
> - clear = ''.join(map(chr, auth_array.AuthInfo.password)).decode('utf-16-le')
> - return clear
> -
> - def __kinit_as_trusted_account(self, info, password):
> - """
> - Initializes ccache with trusted domain account credentials.
> -
> - Applies session code defaults for ccache directory and naming prefix.
> - Session code uses krbccache_prefix+<pid>, we use
> - krbccache_prefix+<TD>+<domain netbios name> so there is no clash
> -
> - Returns tuple (ccache name, principal) where (None, None) signifes an error
> - on ccache initialization
> - """
> - ccache_name = os.path.join(krbccache_dir, "%sTD%s" % (krbccache_prefix, info['name'][0]))
> - principal = '%s$@%s' % (self.flatname, info['dns_domain'].upper())
> - (stdout, stderr, returncode) = ipautil.run(['/usr/bin/kinit', principal],
> - env={'KRB5CCNAME':ccache_name},
> - stdin=password, raiseonerr=False)
> - if returncode == 0:
> - return (ccache_name, principal)
> - else:
> - if returncode == 1:
> - raise errors.ACIError(
> - info=_("KDC for %(domain)s denied trust account for IPA domain with a message '%(message)s'") %
> - dict(domain=info['dns_domain'],message=stderr.strip()))
> - return (None, None)
> -
> def kinit_as_http(self, domain):
> """
> Initializes ccache with http service credentials.
> @@ -544,13 +496,10 @@ class DomainValidator(object):
> return (None, None)
>
> def search_in_dc(self, domain, filter, attrs, scope, basedn=None,
> - use_http=False, quiet=False):
> + quiet=False):
> """
> Perform LDAP search in a trusted domain `domain' Domain Controller.
> Returns resulting entries or None.
> -
> - If use_http is set to True, the search is conducted using
> - HTTP service credentials.
> """
>
> entries = None
> @@ -565,7 +514,6 @@ class DomainValidator(object):
> for (host, port) in info['gc']:
> entries = self.__search_in_dc(info, host, port, filter, attrs,
> scope, basedn=basedn,
> - use_http=use_http,
> quiet=quiet)
> if entries:
> break
> @@ -573,22 +521,13 @@ class DomainValidator(object):
> return entries
>
> def __search_in_dc(self, info, host, port, filter, attrs, scope,
> - basedn=None, use_http=False, quiet=False):
> + basedn=None, quiet=False):
> """
> Actual search in AD LDAP server, using SASL GSSAPI authentication
> Returns LDAP result or None.
> """
>
> - if use_http:
> - (ccache_name, principal) = self.kinit_as_http(info['dns_domain'])
> - else:
> - auth = self.__extract_trusted_auth(info)
> -
> - if not auth:
> - return None
> -
> - (ccache_name, principal) = self.__kinit_as_trusted_account(info,
> - auth)
> + (ccache_name, principal) = self.kinit_as_http(info['dns_domain'])
>
> if ccache_name:
> with installutils.private_ccache(path=ccache_name):
> --
> 1.8.3.1
>
> >From cb695d12b638f23a2743ac97c792625a065fc8f0 Mon Sep 17 00:00:00 2001
> From: Alexander Bokovoy <abokovoy at redhat.com>
> Date: Fri, 27 Sep 2013 12:39:57 +0200
> Subject: [PATCH 5/7] trust: integrate subdomains support into trust-add
>
> ---
> ipalib/plugins/trust.py | 12 +++++++++++-
> 1 file changed, 11 insertions(+), 1 deletion(-)
>
> diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
> index 06ebd92..771a92b 100644
> --- a/ipalib/plugins/trust.py
> +++ b/ipalib/plugins/trust.py
> @@ -343,7 +343,17 @@ sides.
> base_dn = DN(api.env.container_trusts, api.env.basedn),
> filter = trust_filter)
>
> +
> result['result'] = entry_to_dict(trusts[0][1], **options)
> + if options.get('trust_type') == u'ad':
> + domains = fetch_domains_from_trust(self, self.trustinstance, result['result'])
> + if domains and len(domains) > 0:
> + for dom in domains:
> + range_name = dom['cn'][0].upper() + '_id_range'
> + range_type=options.get('range_type', u'ipa-ad-trust')
> + dom_sid = dom['ipanttrusteddomainsid'][0]
> + self.add_range(range_name, dom_sid, range_type=range_type)
> +
> result['result']['trusttype'] = [trust_type_string(result['result']['ipanttrusttype'][0])]
> result['result']['trustdirection'] = [trust_direction_string(result['result']['ipanttrustdirection'][0])]
> result['result']['truststatus'] = [trust_status_string(result['verified'])]
> @@ -431,7 +441,7 @@ sides.
> except errors.NotFound:
> old_range = None
>
> - if options.get('type') == u'ad':
> + if options.get('trust_type') == u'ad':
> if range_type and range_type not in (u'ipa-ad-trust',
> u'ipa-ad-trust-posix'):
> raise errors.ValidationError(
> --
> 1.8.3.1
>
> >From cccde6f77aaf95593400c2804f1bcca26564fdca Mon Sep 17 00:00:00 2001
> From: Alexander Bokovoy <abokovoy at redhat.com>
> Date: Fri, 27 Sep 2013 14:00:22 +0200
> Subject: [PATCH 7/7] ipasam: for subdomains pick up defaults for missing
> values
>
> We don't store trust type, attributes, and direction for subdomains
> of the existing trust. Since trust is always forest level, these parameters
> can be added as defaults when they are missing.
> ---
> daemons/ipa-sam/ipa_sam.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
> index a535c0f..59ddcef 100644
> --- a/daemons/ipa-sam/ipa_sam.c
> +++ b/daemons/ipa-sam/ipa_sam.c
> @@ -2026,6 +2026,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
> if (!res) {
> return false;
> }
> + if (td->trust_direction == 0) {
> + /* attribute wasn't present, set default value */
> + td->trust_direction = LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND;
> + }
>
> res = get_uint32_t_from_ldap_msg(ldap_state, entry,
> LDAP_ATTRIBUTE_TRUST_ATTRIBUTES,
> @@ -2033,6 +2037,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
> if (!res) {
> return false;
> }
> + if (td->trust_attributes == 0) {
> + /* attribute wasn't present, set default value */
> + td->trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
> + }
>
> res = get_uint32_t_from_ldap_msg(ldap_state, entry,
> LDAP_ATTRIBUTE_TRUST_TYPE,
> @@ -2040,6 +2048,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
> if (!res) {
> return false;
> }
> + if (td->trust_type == 0) {
> + /* attribute wasn't present, set default value */
> + td->trust_type = LSA_TRUST_TYPE_UPLEVEL;
> + }
>
> td->trust_posix_offset = talloc_zero(td, uint32_t);
> if (td->trust_posix_offset == NULL) {
> --
> 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