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 <[email protected]> > 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 <[email protected]> > 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 <[email protected]> > 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 <[email protected]> > 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 <[email protected]> > 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 <[email protected]> > 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 > [email protected] > https://www.redhat.com/mailman/listinfo/freeipa-devel _______________________________________________ Freeipa-devel mailing list [email protected] https://www.redhat.com/mailman/listinfo/freeipa-devel
