mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add trust management for Active Directory trusts
This commit is contained in:
committed by
Martin Kosek
parent
dd244c02dd
commit
a7420c1e83
57
API.txt
57
API.txt
@@ -3085,6 +3085,63 @@ option: Str('version?', exclude='webui')
|
|||||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||||
output: Output('value', <type 'unicode'>, None)
|
output: Output('value', <type 'unicode'>, 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', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
|
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||||
|
output: Output('value', <type 'unicode'>, 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', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
|
output: Output('result', <type 'dict'>, None)
|
||||||
|
output: Output('value', <type 'unicode'>, 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', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
|
output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
|
||||||
|
output: Output('count', <type 'int'>, None)
|
||||||
|
output: Output('truncated', <type 'bool'>, 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', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
|
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||||
|
output: Output('value', <type 'unicode'>, 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', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||||
|
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||||
|
output: Output('value', <type 'unicode'>, None)
|
||||||
command: user_add
|
command: user_add
|
||||||
args: 1,34,3
|
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)
|
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)
|
||||||
|
|||||||
@@ -189,6 +189,19 @@ user, virtual machines, groups, authentication credentials), Policy
|
|||||||
(configuration settings, access control information) and Audit (events,
|
(configuration settings, access control information) and Audit (events,
|
||||||
logs, analysis thereof). This package provides SELinux rules for the
|
logs, analysis thereof). This package provides SELinux rules for the
|
||||||
daemons included in freeipa-server
|
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
|
%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
|
logs, analysis thereof). If you are using IPA you need to install this
|
||||||
package.
|
package.
|
||||||
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -n freeipa-%{version} -q
|
%setup -n freeipa-%{version} -q
|
||||||
|
|
||||||
@@ -633,6 +645,9 @@ fi
|
|||||||
%doc COPYING README Contributors.txt
|
%doc COPYING README Contributors.txt
|
||||||
%{_usr}/share/selinux/targeted/ipa_httpd.pp
|
%{_usr}/share/selinux/targeted/ipa_httpd.pp
|
||||||
%{_usr}/share/selinux/targeted/ipa_dogtag.pp
|
%{_usr}/share/selinux/targeted/ipa_dogtag.pp
|
||||||
|
|
||||||
|
%files server-trust-ad
|
||||||
|
%{_usr}/share/ipa/smb.conf.empty
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%files client
|
%files client
|
||||||
@@ -684,6 +699,10 @@ fi
|
|||||||
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
|
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue May 29 2012 Alexander Bokovoy <abokovoy@redhat.com> - 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 <mkosek@redhat.com> - 2.99.0-29
|
* Fri May 11 2012 Martin Kosek <mkosek@redhat.com> - 2.99.0-29
|
||||||
- Replace used DNS client library (acutil) with python-dns
|
- Replace used DNS client library (acutil) with python-dns
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ app_DATA = \
|
|||||||
krbrealm.con.template \
|
krbrealm.con.template \
|
||||||
preferences.html.template \
|
preferences.html.template \
|
||||||
smb.conf.template \
|
smb.conf.template \
|
||||||
|
smb.conf.empty \
|
||||||
referint-conf.ldif \
|
referint-conf.ldif \
|
||||||
dna.ldif \
|
dna.ldif \
|
||||||
master-entry.ldif \
|
master-entry.ldif \
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ add: memberPrincipal
|
|||||||
memberPrincipal: HTTP/$FQDN@$REALM
|
memberPrincipal: HTTP/$FQDN@$REALM
|
||||||
-
|
-
|
||||||
add: ipaAllowedTarget
|
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
|
dn: cn=ipa-ldap-delegation-targets,cn=s4u2proxy,cn=etc,$SUFFIX
|
||||||
changetype: modify
|
changetype: modify
|
||||||
|
|||||||
2
install/share/smb.conf.empty
Normal file
2
install/share/smb.conf.empty
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[global]
|
||||||
|
|
||||||
@@ -131,6 +131,7 @@ def main():
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Check we have a public IP that is associated with the hostname
|
# Check we have a public IP that is associated with the hostname
|
||||||
|
ip = None
|
||||||
try:
|
try:
|
||||||
if options.ip_address:
|
if options.ip_address:
|
||||||
ip = ipautil.CheckedIPAddress(options.ip_address, match_local=True)
|
ip = ipautil.CheckedIPAddress(options.ip_address, match_local=True)
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ DEFAULT_CONFIG = (
|
|||||||
('container_automember', 'cn=automember,cn=etc'),
|
('container_automember', 'cn=automember,cn=etc'),
|
||||||
('container_selinux', 'cn=usermap,cn=selinux'),
|
('container_selinux', 'cn=usermap,cn=selinux'),
|
||||||
('container_s4u2proxy', 'cn=s4u2proxy,cn=etc'),
|
('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:
|
# Ports, hosts, and URIs:
|
||||||
# FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
|
# FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
|
||||||
('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
|
('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
|
||||||
|
|||||||
254
ipalib/plugins/trust.py
Normal file
254
ipalib/plugins/trust.py
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# Authors:
|
||||||
|
# Alexander Bokovoy <abokovoy@redhat.com>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
324
ipaserver/dcerpc.py
Normal file
324
ipaserver/dcerpc.py
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
# Authors:
|
||||||
|
# Alexander Bokovoy <abokovoy@redhat.com>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# 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('.*<SID=(S-.*)>.*')
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ class ADTRUSTInstance(service.Service):
|
|||||||
print "The user for Samba is %s" % self.smb_dn
|
print "The user for Samba is %s" % self.smb_dn
|
||||||
try:
|
try:
|
||||||
self.admin_conn.getEntry(self.smb_dn, ldap.SCOPE_BASE)
|
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, \
|
self.admin_conn.modify_s(self.smb_dn, \
|
||||||
[(ldap.MOD_REPLACE, "userPassword", self.smb_dn_pwd)])
|
[(ldap.MOD_REPLACE, "userPassword", self.smb_dn_pwd)])
|
||||||
@@ -215,7 +215,7 @@ class ADTRUSTInstance(service.Service):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.admin_conn.getEntry(self.smb_dom_dn, ldap.SCOPE_BASE)
|
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
|
return
|
||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
pass
|
pass
|
||||||
@@ -283,7 +283,12 @@ class ADTRUSTInstance(service.Service):
|
|||||||
def __setup_principal(self):
|
def __setup_principal(self):
|
||||||
cifs_principal = "cifs/" + self.fqdn + "@" + self.realm_name
|
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"
|
samba_keytab = "/etc/samba/samba.keytab"
|
||||||
if os.path.exists(samba_keytab):
|
if os.path.exists(samba_keytab):
|
||||||
@@ -291,7 +296,6 @@ class ADTRUSTInstance(service.Service):
|
|||||||
ipautil.run(["ipa-rmkeytab", "--principal", cifs_principal,
|
ipautil.run(["ipa-rmkeytab", "--principal", cifs_principal,
|
||||||
"-k", samba_keytab])
|
"-k", samba_keytab])
|
||||||
except ipautil.CalledProcessError, e:
|
except ipautil.CalledProcessError, e:
|
||||||
root_logger.critical("Result of removing old key: %d" % e.returncode)
|
|
||||||
if e.returncode != 5:
|
if e.returncode != 5:
|
||||||
root_logger.critical("Failed to remove old key for %s" % cifs_principal)
|
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.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \
|
||||||
self.suffix)
|
self.suffix)
|
||||||
except (ldap.ALREADY_EXISTS, errors.DuplicateEntry), e:
|
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
|
pass
|
||||||
|
|
||||||
def __setup_sub_dict(self):
|
def __setup_sub_dict(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user