From a7420c1e83e22a2d441804a7cc5bcf3815a89e47 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Tue, 28 Feb 2012 13:24:41 +0200 Subject: Add trust management for Active Directory trusts --- API.txt | 57 ++++++ freeipa.spec.in | 21 ++- install/share/Makefile.am | 1 + install/share/replica-s4u2proxy.ldif | 2 +- install/share/smb.conf.empty | 2 + install/tools/ipa-adtrust-install | 1 + ipalib/constants.py | 5 +- ipalib/plugins/trust.py | 254 +++++++++++++++++++++++++++ ipaserver/dcerpc.py | 324 +++++++++++++++++++++++++++++++++++ ipaserver/install/adtrustinstance.py | 14 +- 10 files changed, 673 insertions(+), 8 deletions(-) create mode 100644 install/share/smb.conf.empty create mode 100644 ipalib/plugins/trust.py create mode 100644 ipaserver/dcerpc.py diff --git a/API.txt b/API.txt index 365c8bc8c..6c1ea45c9 100644 --- a/API.txt +++ b/API.txt @@ -3085,6 +3085,63 @@ option: Str('version?', exclude='webui') output: Output('summary', (, ), None) output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('value', , None) +command: trust_add_ad +args: 1,7,3 +arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, required=True) +option: Str('realm_admin?', cli_name='admin') +option: Password('realm_passwd?', cli_name='password', confirm=False) +option: Str('realm_server?', cli_name='server') +option: Password('trust_secret?', cli_name='trust_secret', confirm=False) +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: Str('version?', exclude='webui') +output: Output('summary', (, ), None) +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('value', , None) +command: trust_del +args: 1,1,3 +arg: Str('cn', attribute=True, cli_name='realm', multivalue=True, primary_key=True, query=True, required=True) +option: Flag('continue', autofill=True, cli_name='continue', default=False) +output: Output('summary', (, ), None) +output: Output('result', , None) +output: Output('value', , None) +command: trust_find +args: 1,7,4 +arg: Str('criteria?', noextrawhitespace=False) +option: Str('cn', attribute=True, autofill=False, cli_name='realm', multivalue=False, primary_key=True, query=True, required=False) +option: Int('timelimit?', autofill=False, minvalue=0) +option: Int('sizelimit?', autofill=False, minvalue=0) +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: Str('version?', exclude='webui') +option: Flag('pkey_only?', autofill=True, default=False) +output: Output('summary', (, ), None) +output: ListOfEntries('result', (, ), Gettext('A list of LDAP entries', domain='ipa', localedir=None)) +output: Output('count', , None) +output: Output('truncated', , None) +command: trust_mod +args: 1,7,3 +arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, query=True, required=True) +option: Str('setattr*', cli_name='setattr', exclude='webui') +option: Str('addattr*', cli_name='addattr', exclude='webui') +option: Str('delattr*', cli_name='delattr', exclude='webui') +option: Flag('rights', autofill=True, default=False) +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: Str('version?', exclude='webui') +output: Output('summary', (, ), None) +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('value', , None) +command: trust_show +args: 1,4,3 +arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, query=True, required=True) +option: Flag('rights', autofill=True, default=False) +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: Str('version?', exclude='webui') +output: Output('summary', (, ), None) +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('value', , None) command: user_add args: 1,34,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/freeipa.spec.in b/freeipa.spec.in index de93aecb6..369c6833e 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -189,6 +189,19 @@ user, virtual machines, groups, authentication credentials), Policy (configuration settings, access control information) and Audit (events, logs, analysis thereof). This package provides SELinux rules for the daemons included in freeipa-server + +%package server-trust-ad +Summary: Virtual package to install packages required for Active Directory trusts +Group: System Environment/Base +Requires: %{name}-server = %version-%release +Requires: python-crypto +Requires: samba4-python +Requires: samba4 + +%description server-trust-ad +Cross-realm trusts with Active Directory in IPA require working Samba 4 installation. +This package is provided for convenience to install all required dependencies at once. + %endif @@ -279,7 +292,6 @@ user, virtual machines, groups, authentication credentials), Policy logs, analysis thereof). If you are using IPA you need to install this package. - %prep %setup -n freeipa-%{version} -q @@ -633,6 +645,9 @@ fi %doc COPYING README Contributors.txt %{_usr}/share/selinux/targeted/ipa_httpd.pp %{_usr}/share/selinux/targeted/ipa_dogtag.pp + +%files server-trust-ad +%{_usr}/share/ipa/smb.conf.empty %endif %files client @@ -684,6 +699,10 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt %changelog +* Tue May 29 2012 Alexander Bokovoy - 2.99.0-30 +- Add freeipa-server-trust-ad virtual package to capture all required dependencies + for Active Directory trust management + * Fri May 11 2012 Martin Kosek - 2.99.0-29 - Replace used DNS client library (acutil) with python-dns diff --git a/install/share/Makefile.am b/install/share/Makefile.am index 243fc2a11..81fd0dc15 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -33,6 +33,7 @@ app_DATA = \ krbrealm.con.template \ preferences.html.template \ smb.conf.template \ + smb.conf.empty \ referint-conf.ldif \ dna.ldif \ master-entry.ldif \ diff --git a/install/share/replica-s4u2proxy.ldif b/install/share/replica-s4u2proxy.ldif index 410eceb1c..ce58365c5 100644 --- a/install/share/replica-s4u2proxy.ldif +++ b/install/share/replica-s4u2proxy.ldif @@ -4,7 +4,7 @@ add: memberPrincipal memberPrincipal: HTTP/$FQDN@$REALM - add: ipaAllowedTarget -ipaAllowedTarget: cn=ipa-cifs-delegation-targets,cn=s4u2proxy,cn=etc,$SUFFIX +ipaAllowedTarget: 'cn=ipa-cifs-delegation-targets,cn=s4u2proxy,cn=etc,$SUFFIX' dn: cn=ipa-ldap-delegation-targets,cn=s4u2proxy,cn=etc,$SUFFIX changetype: modify diff --git a/install/share/smb.conf.empty b/install/share/smb.conf.empty new file mode 100644 index 000000000..6d1e6265d --- /dev/null +++ b/install/share/smb.conf.empty @@ -0,0 +1,2 @@ +[global] + diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install index 0dfc6eba6..5b7dc9fdf 100755 --- a/install/tools/ipa-adtrust-install +++ b/install/tools/ipa-adtrust-install @@ -131,6 +131,7 @@ def main(): break # Check we have a public IP that is associated with the hostname + ip = None try: if options.ip_address: ip = ipautil.CheckedIPAddress(options.ip_address, match_local=True) diff --git a/ipalib/constants.py b/ipalib/constants.py index dc32533ee..3376d30a0 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -101,7 +101,10 @@ DEFAULT_CONFIG = ( ('container_automember', 'cn=automember,cn=etc'), ('container_selinux', 'cn=usermap,cn=selinux'), ('container_s4u2proxy', 'cn=s4u2proxy,cn=etc'), - + ('container_cifsdomains', 'cn=ad,cn=etc'), + ('container_trusts', 'cn=trusts'), + ('container_adtrusts', 'cn=ad,cn=trusts'), + # Ports, hosts, and URIs: # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py new file mode 100644 index 000000000..2fd949cd2 --- /dev/null +++ b/ipalib/plugins/trust.py @@ -0,0 +1,254 @@ +# Authors: +# Alexander Bokovoy +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ipalib.plugins.baseldap import * +from ipalib import api, Str, Password, DefaultFrom, _, ngettext, Object +from ipalib.parameters import Enum +from ipalib import Command +from ipalib import errors +from ipapython import ipautil +from ipalib import util +if api.env.in_server and api.env.context in ['lite', 'server']: + try: + import ipaserver.dcerpc + _bindings_installed = True + except Exception, e: + _bindings_installed = False + +__doc__ = _(""" +Manage trust relationship between realms +""") + +trust_output_params = ( + Str('ipantflatname', + label=_('Domain NetBIOS name')), + Str('ipantsecurityidentifier', + label=_('Domain Security Identifier')), + Str('trustdirection', + label=_('Trust direction')), + Str('trusttype', + label=_('Trust type')), + Str('truststatus', + label=_('Trust status')), +) + +_trust_type_dict = {1 : _('Non-Active Directory domain'), + 2 : _('Active Directory domain'), + 3 : _('RFC4120-compliant Kerberos realm')} +_trust_direction_dict = {1 : _('Trusting forest'), + 2 : _('Trusted forest'), + 3 : _('Two-way trust')} +_trust_status = {1 : _('Established and verified'), + 2 : _('Waiting for confirmation by remote side')} +_trust_type_dict_unknown = _('Unknown') + +def trust_type_string(level): + """ + Returns a string representing a type of the trust. The original field is an enum: + LSA_TRUST_TYPE_DOWNLEVEL = 0x00000001, + LSA_TRUST_TYPE_UPLEVEL = 0x00000002, + LSA_TRUST_TYPE_MIT = 0x00000003 + """ + string = _trust_type_dict.get(int(level), _trust_type_dict_unknown) + return unicode(string) + +def trust_direction_string(level): + """ + Returns a string representing a direction of the trust. The original field is a bitmask taking two bits in use + LSA_TRUST_DIRECTION_INBOUND = 0x00000001, + LSA_TRUST_DIRECTION_OUTBOUND = 0x00000002 + """ + string = _trust_direction_dict.get(int(level), _trust_type_dict_unknown) + return unicode(string) + +def trust_status_string(level): + string = _trust_direction_dict.get(int(level), _trust_type_dict_unknown) + return unicode(string) + +class trust(LDAPObject): + """ + Trust object. + """ + trust_types = ('ad', 'ipa') + container_dn = api.env.container_trusts + object_name = _('trust') + object_name_plural = _('trusts') + object_class = ['ipaNTTrustedDomain'] + default_attributes = ['cn', 'ipantflatname', 'ipantsecurityidentifier', + 'ipanttrusttype', 'ipanttrustattributes', 'ipanttrustdirection', 'ipanttrustpartner', + 'ipantauthtrustoutgoing', 'ipanttrustauthincoming', 'ipanttrustforesttrustinfo', + 'ipanttrustposixoffset', 'ipantsupportedencryptiontypes' ] + + label = _('Trusts') + label_singular = _('Trust') + + takes_params = ( + Str('cn', + cli_name='realm', + label=_('Realm name'), + primary_key=True, + ), + ) + +def make_trust_dn(env, trust_type, dn): + if trust_type in trust.trust_types: + container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn) + return unicode(DN(DN(dn)[0], container_dn)) + return dn + +class trust_add_ad(LDAPCreate): + __doc__ = _('Add new trust to use against Active Directory domain.') + + takes_options = ( + Str('realm_admin?', + cli_name='admin', + label=_("Active Directory domain administrator"), + ), + Password('realm_passwd?', + cli_name='password', + label=_("Active directory domain adminstrator's password"), + confirm=False, + ), + Str('realm_server?', + cli_name='server', + label=_('Domain controller for the Active Directory domain (optional)'), + ), + Password('trust_secret?', + cli_name='trust_secret', + label=_('Shared secret for the trust'), + confirm=False, + ), + ) + + + msg_summary = _('Added Active Directory trust for realm "%(value)s"') + + def execute(self, *keys, **options): + # Join domain using full credentials and with random trustdom + # secret (will be generated by the join method) + trustinstance = None + 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 'realm_server' not in options: + realm_server = None + else: + realm_server = options['realm_server'] + + trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api) + + # 1. Full access to the remote domain. Use admin credentials and + # generate random trustdom password to do work on both sides + if 'realm_admin' in options: + realm_admin = options['realm_admin'] + + if 'realm_passwd' not in options: + raise errors.ValidationError(name=_('AD Trust setup'), reason=_('Realm administrator password should be specified')) + realm_passwd = options['realm_passwd'] + + result = trustinstance.join_ad_full_credentials(keys[-1], realm_server, realm_admin, realm_passwd) + + if result is None: + raise errors.ValidationError(name=_('AD Trust setup'), reason=_('Unable to verify write permissions to the AD')) + + return dict(result=dict(), value=trustinstance.remote_domain.info['dns_domain']) + + # 2. We don't have access to the remote domain and trustdom password + # is provided. Do the work on our side and inform what to do on remote + # side. + if 'trust_secret' in options: + result = trustinstance.join_ad_ipa_half(keys[-1], realm_server, options['trust_secret']) + return dict(result=dict(), value=trustinstance.remote_domain.info['dns_domain']) + +class trust_del(LDAPDelete): + __doc__ = _('Delete a trust.') + + msg_summary = _('Deleted trust "%(value)s"') + + def pre_callback(self, ldap, dn, *keys, **options): + 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.') + + msg_summary = _('Modified trust "%(value)s"') + + def pre_callback(self, ldap, dn, *keys, **options): + result = None + try: + result = self.api.Command.trust_show(keys[-1]) + except errors.NotFound, e: + self.obj.handle_not_found(*keys) + + # TODO: we found the trust object, now modify it + return result['result']['dn'] + +class trust_find(LDAPSearch): + __doc__ = _('Search for trusts.') + + msg_summary = ngettext( + '%(count)d trust matched', '%(count)d trusts matched', 0 + ) + + # 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): + return (filters, base_dn, ldap.SCOPE_SUBTREE) + +class trust_show(LDAPRetrieve): + __doc__ = _('Display information about a trust.') + has_output_params = LDAPRetrieve.has_output_params + trust_output_params + + 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: + result['result']['trusttype'] = [trust_type_string(result['result']['ipanttrusttype'][0])] + result['result']['trustdirection'] = [trust_direction_string(result['result']['ipanttrustdirection'][0])] + break + if error or not result: + self.obj.handle_not_found(*keys) + + return result + + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + if 'trust_show_type' in options: + return make_trust_dn(self.env, options['trust_show_type'], dn) + return dn + +api.register(trust) +api.register(trust_add_ad) +api.register(trust_mod) +api.register(trust_del) +api.register(trust_find) +api.register(trust_show) + diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py new file mode 100644 index 000000000..7aac1caea --- /dev/null +++ b/ipaserver/dcerpc.py @@ -0,0 +1,324 @@ +# Authors: +# Alexander Bokovoy +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# Portions (C) Andrew Tridgell, Andrew Bartlett +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Make sure we only run this module at the server where samba4-python +# package is installed to avoid issues with unavailable modules + +from ipalib.plugins.baseldap import * +from ipalib import api, Str, Password, DefaultFrom, _, ngettext, Object +from ipalib.parameters import Enum +from ipalib import Command +from ipalib import errors +from ipapython import ipautil +from ipalib import util + +import os, string, struct, copy +import uuid +from samba import param +from samba import credentials +from samba.dcerpc import security, lsa, drsblobs, nbt +from samba.ndr import ndr_pack +from samba import net +import samba +import random +import ldap as _ldap +from Crypto.Cipher import ARC4 + +__doc__ = _(""" +Classes to manage trust joins using DCE-RPC calls + +The code in this module relies heavily on samba4-python package +and Samba4 python bindings. +""") + +class ExtendedDNControl(_ldap.controls.RequestControl): + def __init__(self): + self.controlType = "1.2.840.113556.1.4.529" + self.criticality = False + self.integerValue = 1 + + def encodeControlValue(self): + return '0\x03\x02\x01\x01' + +class TrustDomainInstance(object): + + def __init__(self, hostname, creds=None): + self.parm = param.LoadParm() + self.parm.load(os.path.join(ipautil.SHARE_DIR,"smb.conf.empty")) + if len(hostname) > 0: + self.parm.set('netbios name', hostname) + self.creds = creds + self.hostname = hostname + self.info = {} + self._pipe = None + self._policy_handle = None + self.read_only = False + + def __gen_lsa_connection(self, binding): + if self.creds is None: + raise errors.RequirementError(name='CIFS credentials object') + try: + result = lsa.lsarpc(binding, self.parm, self.creds) + return result + except: + return None + + def __init_lsa_pipe(self, remote_host): + """ + Try to initialize connection to the LSA pipe at remote host. + This method tries consequently all possible transport options + and selects one that works. See __gen_lsa_bindings() for details. + + The actual result may depend on details of existing credentials. + For example, using signing causes NO_SESSION_KEY with Win2K8 and + using kerberos against Samba with signing does not work. + """ + # short-cut: if LSA pipe is initialized, skip completely + if self._pipe: + return + + bindings = self.__gen_lsa_bindings(remote_host) + for binding in bindings: + self._pipe = self.__gen_lsa_connection(binding) + if self._pipe: + break + if self._pipe is None: + raise errors.RequirementError(name='Working LSA pipe') + + def __gen_lsa_bindings(self, remote_host): + """ + There are multiple transports to issue LSA calls. However, depending on a + system in use they may be blocked by local operating system policies. + Generate all we can use. __init_lsa_pipe() will try them one by one until + there is one working. + + We try NCACN_NP before NCACN_IP_TCP and signed sessions before unsigned. + """ + transports = (u'ncacn_np', u'ncacn_ip_tcp') + options = ( u',', u'') + binding_template=lambda x,y,z: u'%s:%s[%s]' % (x, y, z) + return [binding_template(t, remote_host, o) for t in transports for o in options] + + def retrieve_anonymously(self, remote_host, discover_srv=False): + """ + When retrieving DC information anonymously, we can't get SID of the domain + """ + netrc = net.Net(creds=self.creds, lp=self.parm) + if discover_srv: + result = netrc.finddc(domain=remote_host, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS) + else: + result = netrc.finddc(address=remote_host, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS) + if not result: + return False + self.info['name'] = unicode(result.domain_name) + self.info['dns_domain'] = unicode(result.dns_domain) + self.info['dns_forest'] = unicode(result.forest) + self.info['guid'] = unicode(result.domain_uuid) + + # Netlogon response doesn't contain SID of the domain. + # We need to do rootDSE search with LDAP_SERVER_EXTENDED_DN_OID control to reveal the SID + ldap_uri = 'ldap://%s' % (result.pdc_name) + conn = _ldap.initialize(ldap_uri) + conn.set_option(_ldap.OPT_SERVER_CONTROLS, [ExtendedDNControl()]) + result = None + try: + (objtype, res) = conn.search_s('', _ldap.SCOPE_BASE)[0] + result = res['defaultNamingContext'][0] + self.info['dns_hostname'] = res['dnsHostName'][0] + except _ldap.LDAPError, e: + print "LDAP error when connecting to %s: %s" % (unicode(result.pdc_name), str(e)) + + if result: + self.info['sid'] = self.parse_naming_context(result) + return True + + def parse_naming_context(self, context): + naming_ref = re.compile('.*.*') + return naming_ref.match(context).group(1) + + def retrieve(self, remote_host): + self.__init_lsa_pipe(remote_host) + + objectAttribute = lsa.ObjectAttribute() + objectAttribute.sec_qos = lsa.QosInfo() + self._policy_handle = self._pipe.OpenPolicy2(u"", objectAttribute, security.SEC_FLAG_MAXIMUM_ALLOWED) + result = self._pipe.QueryInfoPolicy2(self._policy_handle, lsa.LSA_POLICY_INFO_DNS) + self.info['name'] = unicode(result.name.string) + self.info['dns_domain'] = unicode(result.dns_domain.string) + self.info['dns_forest'] = unicode(result.dns_forest.string) + self.info['guid'] = unicode(result.domain_guid) + self.info['sid'] = unicode(result.sid) + + def generate_auth(self, trustdom_secret): + def arcfour_encrypt(key, data): + c = ARC4.new(key) + return c.encrypt(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')) + + clear_value = drsblobs.AuthInfoClear() + clear_value.size = len(password_blob) + clear_value.password = password_blob + + clear_authentication_information = drsblobs.AuthenticationInformation() + clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time())) + clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR + clear_authentication_information.AuthInfo = clear_value + + authentication_information_array = drsblobs.AuthenticationInformationArray() + authentication_information_array.count = 1 + authentication_information_array.array = [clear_authentication_information] + + outgoing = drsblobs.trustAuthInOutBlob() + outgoing.count = 1 + outgoing.current = authentication_information_array + + confounder = [3]*512 + for i in range(512): + confounder[i] = random.randint(0, 255) + + trustpass = drsblobs.trustDomainPasswords() + trustpass.confounder = confounder + + trustpass.outgoing = outgoing + trustpass.incoming = outgoing + + trustpass_blob = ndr_pack(trustpass) + + encrypted_trustpass = arcfour_encrypt(self._pipe.session_key, trustpass_blob) + + auth_blob = lsa.DATA_BUF2() + auth_blob.size = len(encrypted_trustpass) + auth_blob.data = string_to_array(encrypted_trustpass) + + auth_info = lsa.TrustDomainInfoAuthInfoInternal() + auth_info.auth_blob = auth_blob + self.auth_info = auth_info + + + + def establish_trust(self, another_domain, trustdom_secret): + """ + Establishes trust between our and another domain + Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call + trustdom_secret -- shared secred used for the trust + """ + self.generate_auth(trustdom_secret) + + info = lsa.TrustDomainInfoInfoEx() + info.domain_name.string = another_domain.info['dns_domain'] + info.netbios_name.string = another_domain.info['name'] + info.sid = security.dom_sid(another_domain.info['sid']) + info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND + info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL + info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE | lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION + + try: + dname = lsa.String() + dname.string = another_domain.info['dns_domain'] + res = self._pipe.QueryTrustedDomainInfoByName(self._policy_handle, dname, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + self._pipe.DeleteTrustedDomain(self._policy_handle, res.info_ex.sid) + except: + pass + self._pipe.CreateTrustedDomainEx2(self._policy_handle, info, self.auth_info, security.SEC_STD_DELETE) + +class TrustDomainJoins(object): + ATTR_FLATNAME = 'ipantflatname' + + def __init__(self, api): + self.api = api + self.local_domain = None + self.remote_domain = None + + self.ldap = self.api.Backend.ldap2 + cn_trust_local = DN(('cn', self.api.env.domain), self.api.env.container_cifsdomains, self.api.env.basedn) + (dn, entry_attrs) = self.ldap.get_entry(unicode(cn_trust_local), [self.ATTR_FLATNAME]) + self.local_flatname = entry_attrs[self.ATTR_FLATNAME][0] + self.local_dn = dn + + self.__populate_local_domain() + + def __populate_local_domain(self): + # Initialize local domain info using kerberos only + ld = TrustDomainInstance(self.local_flatname) + ld.creds = credentials.Credentials() + ld.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS) + ld.creds.guess(ld.parm) + ld.creds.set_workstation(ld.hostname) + ld.retrieve(util.get_fqdn()) + self.local_domain = ld + + def __populate_remote_domain(self, realm, realm_server=None, realm_admin=None, realm_passwd=None): + def get_instance(self): + # Fetch data from foreign domain using password only + rd = TrustDomainInstance('') + rd.parm.set('workgroup', self.local_domain.info['name']) + rd.creds = credentials.Credentials() + rd.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS) + rd.creds.guess(rd.parm) + return rd + + rd = get_instance(self) + rd.creds.set_anonymous() + rd.creds.set_workstation(self.local_domain.hostname) + if realm_server is None: + rd.retrieve_anonymously(realm, discover_srv=True) + else: + rd.retrieve_anonymously(realm_server, discover_srv=False) + rd.read_only = True + if realm_admin and realm_passwd: + if 'name' in rd.info: + auth_string = u"%s\%s%%%s" % (rd.info['name'], realm_admin, realm_passwd) + td = get_instance(self) + td.creds.parse_string(auth_string) + td.creds.set_workstation(self.local_domain.hostname) + if realm_server is None: + # we must have rd.info['dns_hostname'] then, part of anonymous discovery + td.retrieve(rd.info['dns_hostname']) + else: + td.retrieve(realm_server) + td.read_only = False + self.remote_domain = td + return + # Otherwise, use anonymously obtained data + self.remote_domain = rd + + def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd): + self.__populate_remote_domain(realm, realm_server, realm_admin, realm_passwd) + if not self.remote_domain.read_only: + trustdom_pass = samba.generate_random_password(128, 128) + self.remote_domain.establish_trust(self.local_domain, trustdom_pass) + self.local_domain.establish_trust(self.remote_domain, trustdom_pass) + return dict(local=self.local_domain, remote=self.remote_domain) + return None + + def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd): + self.__populate_remote_domain(realm, realm_server, realm_passwd=None) + self.local_domain.establish_trust(self.remote_domain, trustdom_passwd) + return dict(local=self.local_domain, remote=self.remote_domain) + + diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py index b978146c3..38f9eaecc 100644 --- a/ipaserver/install/adtrustinstance.py +++ b/ipaserver/install/adtrustinstance.py @@ -114,7 +114,7 @@ class ADTRUSTInstance(service.Service): print "The user for Samba is %s" % self.smb_dn try: self.admin_conn.getEntry(self.smb_dn, ldap.SCOPE_BASE) - print "Samba user entry exists, resetting password" + root_logger.info("Samba user entry exists, resetting password") self.admin_conn.modify_s(self.smb_dn, \ [(ldap.MOD_REPLACE, "userPassword", self.smb_dn_pwd)]) @@ -215,7 +215,7 @@ class ADTRUSTInstance(service.Service): try: self.admin_conn.getEntry(self.smb_dom_dn, ldap.SCOPE_BASE) - print "Samba domain object already exists" + root_logger.info("Samba domain object already exists") return except errors.NotFound: pass @@ -283,7 +283,12 @@ class ADTRUSTInstance(service.Service): def __setup_principal(self): cifs_principal = "cifs/" + self.fqdn + "@" + self.realm_name - api.Command.service_add(unicode(cifs_principal)) + try: + api.Command.service_add(unicode(cifs_principal)) + except errors.DuplicateEntry, e: + # CIFS principal already exists, it is not the first time adtrustinstance is managed + # That's fine, we we'll re-extract the key again. + pass samba_keytab = "/etc/samba/samba.keytab" if os.path.exists(samba_keytab): @@ -291,7 +296,6 @@ class ADTRUSTInstance(service.Service): ipautil.run(["ipa-rmkeytab", "--principal", cifs_principal, "-k", samba_keytab]) except ipautil.CalledProcessError, e: - root_logger.critical("Result of removing old key: %d" % e.returncode) if e.returncode != 5: root_logger.critical("Failed to remove old key for %s" % cifs_principal) @@ -374,7 +378,7 @@ class ADTRUSTInstance(service.Service): self.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \ self.suffix) except (ldap.ALREADY_EXISTS, errors.DuplicateEntry), e: - root_logger.critical("ADTRUST Service startup entry already exists.") + root_logger.info("ADTRUST Service startup entry already exists.") pass def __setup_sub_dict(self): -- cgit