summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--API.txt2
-rw-r--r--ipalib/plugins/trust.py111
-rw-r--r--ipaserver/dcerpc.py164
-rw-r--r--ipaserver/install/installutils.py7
4 files changed, 231 insertions, 53 deletions
diff --git a/API.txt b/API.txt
index 663e8cb57..50834ef6b 100644
--- a/API.txt
+++ b/API.txt
@@ -3394,7 +3394,7 @@ arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=T
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Int('base_id?', cli_name='base_id')
-option: Int('range_size?', autofill=True, cli_name='range_size', default=200000)
+option: Int('range_size?', cli_name='range_size')
option: StrEnum('range_type?', cli_name='range_type', values=(u'ipa-ad-trust-posix', u'ipa-ad-trust'))
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('realm_admin?', cli_name='admin')
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 965ff76bb..b19a27eca 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -20,9 +20,13 @@
from ipalib.plugins.baseldap import *
from ipalib.plugins.dns import dns_container_exists
+from ipapython.ipautil import realm_to_suffix
from ipalib import api, Str, StrEnum, Password, _, ngettext
from ipalib import Command
from ipalib import errors
+from ldap import SCOPE_SUBTREE
+from time import sleep
+
try:
import pysss_murmur #pylint: disable=F0401
_murmur_installed = True
@@ -292,8 +296,6 @@ sides.
Int('range_size?',
cli_name='range_size',
label=_('Size of the ID range reserved for the trusted domain'),
- default=DEFAULT_RANGE_SIZE,
- autofill=True
),
StrEnum('range_type?',
label=_('Range type'),
@@ -313,7 +315,7 @@ sides.
result = self.execute_ad(full_join, *keys, **options)
if not old_range:
- self.add_range(range_name, dom_sid, **options)
+ self.add_range(range_name, dom_sid, *keys, **options)
trust_filter = "cn=%s" % result['value']
ldap = self.obj.backend
@@ -418,9 +420,7 @@ sides.
'Only the ipa-ad-trust and ipa-ad-trust-posix are '
'allowed values for --range-type when adding an AD '
'trust.'
- )
-
-)
+ ))
base_id = options.get('base_id')
range_size = options.get('range_size') != DEFAULT_RANGE_SIZE
@@ -468,9 +468,96 @@ sides.
return old_range, range_name, dom_sid
- def add_range(self, range_name, dom_sid, **options):
- base_id = options.get('base_id')
- if not base_id:
+ def add_range(self, range_name, dom_sid, *keys, **options):
+ """
+ First, we try to derive the parameters of the ID range based on the
+ information contained in the Active Directory.
+
+ If that was not successful, we go for our usual defaults (random base,
+ range size 200 000, ipa-ad-trust range type).
+
+ Any of these can be overriden by passing appropriate CLI options
+ to the trust-add command.
+ """
+
+ range_size = None
+ range_type = None
+ base_id = None
+
+ # First, get information about ID space from AD
+ # However, we skip this step if other than ipa-ad-trust-posix
+ # range type is enforced
+
+ if options.get('range_type', None) in (None, u'ipa-ad-trust-posix'):
+
+ # Get the base dn
+ domain = keys[-1]
+ basedn = realm_to_suffix(domain)
+
+ # Search for information contained in
+ # CN=ypservers,CN=ypServ30,CN=RpcServices,CN=System
+ info_filter = '(objectClass=msSFU30DomainInfo)'
+ info_dn = DN('CN=ypservers,CN=ypServ30,CN=RpcServices,CN=System')\
+ + basedn
+
+ # Get the domain validator
+ domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
+ if not domain_validator.is_configured():
+ raise errors.NotFound(
+ reason=_('Cannot search in trusted domains without own '
+ 'domain configured. Make sure you have run '
+ 'ipa-adtrust-install on the IPA server first'))
+
+ # KDC might not get refreshed data at the first time,
+ # retry several times
+ for retry in range(10):
+ info_list = domain_validator.search_in_dc(domain,
+ info_filter,
+ None,
+ SCOPE_SUBTREE,
+ basedn=info_dn,
+ use_http=True,
+ quiet=True)
+
+ if info_list:
+ info = info_list[0]
+ break
+ else:
+ sleep(2)
+
+ required_msSFU_attrs = ['msSFU30MaxUidNumber', 'msSFU30OrderNumber']
+
+ if not info_list:
+ # We were unable to gain UNIX specific info from the AD
+ self.log.debug("Unable to gain POSIX info from the AD")
+ else:
+ if all(attr in info for attr in required_msSFU_attrs):
+ self.log.debug("Able to gain POSIX info from the AD")
+ range_type = u'ipa-ad-trust-posix'
+
+ max_uid = info.get('msSFU30MaxUidNumber')
+ max_gid = info.get('msSFU30MaxGidNumber', None)
+ max_id = int(max(max_uid, max_gid)[0])
+
+ base_id = int(info.get('msSFU30OrderNumber')[0])
+ range_size = (1 + (max_id - base_id) / DEFAULT_RANGE_SIZE)\
+ * DEFAULT_RANGE_SIZE
+
+ # Second, options given via the CLI options take precedence to discovery
+ if options.get('range_type', None):
+ range_type = options.get('range_type', None)
+ elif not range_type:
+ range_type = u'ipa-ad-trust'
+
+ if options.get('range_size', None):
+ range_size = options.get('range_size', None)
+ elif not range_size:
+ range_size = DEFAULT_RANGE_SIZE
+
+ if options.get('base_id', None):
+ base_id = options.get('base_id', None)
+ elif not base_id:
+ # Generate random base_id if not discovered nor given via CLI
base_id = DEFAULT_RANGE_SIZE + (
pysss_murmur.murmurhash3(
dom_sid,
@@ -478,12 +565,12 @@ sides.
) % 10000
) * DEFAULT_RANGE_SIZE
- # Add new ID range
+ # Finally, add new ID range
api.Command['idrange_add'](range_name,
ipabaseid=base_id,
- ipaidrangesize=options['range_size'],
+ ipaidrangesize=range_size,
ipabaserid=0,
- iparangetype=options.get('range_type'),
+ iparangetype=range_type,
ipanttrusteddomainsid=dom_sid)
def execute_ad(self, full_join, *keys, **options):
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 0f98ce83c..88ad928eb 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -61,6 +61,7 @@ The code in this module relies heavily on samba4-python package
and Samba4 python bindings.
""")
+
def is_sid_valid(sid):
try:
security.dom_sid(sid)
@@ -69,6 +70,7 @@ def is_sid_valid(sid):
else:
return True
+
access_denied_error = errors.ACIError(info=_('CIFS server denied your credentials'))
dcerpc_error_codes = {
-1073741823:
@@ -113,6 +115,7 @@ class ExtendedDNControl(LDAPControl):
def encodeControlValue(self, value=None):
return '0\x03\x02\x01\x01'
+
class DomainValidator(object):
ATTR_FLATNAME = 'ipantflatname'
ATTR_SID = 'ipantsecurityidentifier'
@@ -184,6 +187,18 @@ class DomainValidator(object):
except errors.NotFound, e:
return []
+ def set_trusted_domains(self):
+ # At this point we have SID_NT_AUTHORITY family SID and really need to
+ # check it against prefixes of domain SIDs we trust to
+ if not self._domains:
+ self._domains = self.get_trusted_domains()
+ if len(self._domains) == 0:
+ # Our domain is configured but no trusted domains are configured
+ # This means we can't check the correctness of a trusted
+ # domain SIDs
+ raise errors.ValidationError(name='sid',
+ error=_('no trusted domain is configured'))
+
def get_domain_by_sid(self, sid, exact_match=False):
if not self.domain:
# our domain is not configured or self.is_configured() never run
@@ -200,14 +215,7 @@ class DomainValidator(object):
# At this point we have SID_NT_AUTHORITY family SID and really need to
# check it against prefixes of domain SIDs we trust to
- if not self._domains:
- self._domains = self.get_trusted_domains()
- if len(self._domains) == 0:
- # Our domain is configured but no trusted domains are configured
- # This means we can't check the correctness of a trusted
- # domain SIDs
- raise errors.ValidationError(name='sid',
- error=_('no trusted domain is configured'))
+ self.set_trusted_domains()
# We have non-zero list of trusted domains and have to go through
# them one by one and check their sids as prefixes / exact match
@@ -284,7 +292,7 @@ class DomainValidator(object):
raise errors.ValidationError(name=_('trusted domain object'),
error= _('domain is not trusted'))
# Now we have a name to check against our list of trusted domains
- entries = self.search_in_gc(domain, filter, attrs, scope, basedn)
+ entries = self.search_in_dc(domain, filter, attrs, scope, basedn)
elif flatname is not None:
# Flatname was specified, traverse through the list of trusted
# domains first to find the proper one
@@ -292,7 +300,7 @@ class DomainValidator(object):
for domain in self._domains:
if self._domains[domain][0] == flatname:
found_flatname = True
- entries = self.search_in_gc(domain, filter, attrs, scope, basedn)
+ entries = self.search_in_dc(domain, filter, attrs, scope, basedn)
if entries:
break
if not found_flatname:
@@ -436,48 +444,126 @@ class DomainValidator(object):
dict(domain=info['dns_domain'],message=stderr.strip()))
return (None, None)
- def search_in_gc(self, domain, filter, attrs, scope, basedn=None):
+ def kinit_as_http(self, domain):
+ """
+ Initializes ccache with http service 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 path, principal) where (None, None) signifes an
+ error on ccache initialization
+ """
+
+ domain_suffix = domain.replace('.', '-')
+
+ ccache_name = "%sTD%s" % (krbccache_prefix, domain_suffix)
+ ccache_path = os.path.join(krbccache_dir, ccache_name)
+
+ realm = api.env.realm
+ hostname = api.env.host
+ principal = 'HTTP/%s@%s' % (hostname, realm)
+ keytab = '/etc/httpd/conf/ipa.keytab'
+
+ # Destroy the contents of the ccache
+ root_logger.debug('Destroying the contents of the separate ccache')
+
+ (stdout, stderr, returncode) = ipautil.run(
+ ['/usr/bin/kdestroy', '-A', '-c', ccache_path],
+ env={'KRB5CCNAME': ccache_path},
+ raiseonerr=False)
+
+ # Destroy the contents of the ccache
+ root_logger.debug('Running kinit from ipa.keytab to obtain HTTP '
+ 'service principal with MS-PAC attached.')
+
+ (stdout, stderr, returncode) = ipautil.run(
+ ['/usr/bin/kinit', '-kt', keytab, principal],
+ env={'KRB5CCNAME': ccache_path},
+ raiseonerr=False)
+
+ if returncode == 0:
+ return (ccache_path, principal)
+ else:
+ return (None, None)
+
+ def search_in_dc(self, domain, filter, attrs, scope, basedn=None,
+ use_http=False, quiet=False):
"""
- Perform LDAP search in a trusted domain `domain' Global Catalog.
- Returns resulting entries or None
+ 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
- sid = None
+
info = self.__retrieve_trusted_domain_gc_list(domain)
+
if not info:
- raise errors.ValidationError(name=_('Trust setup'),
+ raise errors.ValidationError(
+ name=_('Trust setup'),
error=_('Cannot retrieve trusted domain GC list'))
+
for (host, port) in info['gc']:
- entries = self.__search_in_gc(info, host, port, filter, attrs, scope, basedn)
+ entries = self.__search_in_dc(info, host, port, filter, attrs,
+ scope, basedn=basedn,
+ use_http=use_http,
+ quiet=quiet)
if entries:
break
return entries
- def __search_in_gc(self, info, host, port, filter, attrs, scope, basedn=None):
+ def __search_in_dc(self, info, host, port, filter, attrs, scope,
+ basedn=None, use_http=False, quiet=False):
"""
Actual search in AD LDAP server, using SASL GSSAPI authentication
- Returns LDAP result or None
+ Returns LDAP result or None.
"""
- conn = IPAdmin(host=host, port=port, no_schema=True, decode_attrs=False)
- auth = self.__extract_trusted_auth(info)
- if attrs is None:
- attrs = []
- if auth:
- (ccache_name, principal) = self.__kinit_as_trusted_account(info, auth)
- if ccache_name:
- old_ccache = os.environ.get('KRB5CCNAME')
- os.environ["KRB5CCNAME"] = ccache_name
- # OPT_X_SASL_NOCANON is used to avoid hard requirement for PTR
- # records pointing back to the same host name
- conn.set_option(_ldap.OPT_X_SASL_NOCANON, _ldap.OPT_ON)
- conn.do_sasl_gssapi_bind()
- if basedn is None:
- # Use domain root base DN
- basedn = DN(*map(lambda p: ('dc', p), info['dns_domain'].split('.')))
- entries = conn.get_entries(basedn, scope, filter, attrs)
- os.environ["KRB5CCNAME"] = old_ccache
- return entries
+
+ 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)
+
+ if ccache_name:
+ with installutils.private_ccache(path=ccache_name):
+ entries = None
+
+ try:
+ conn = IPAdmin(host=host,
+ port=389, # query the AD DC
+ no_schema=True,
+ decode_attrs=False,
+ sasl_nocanon=True)
+ # sasl_nocanon used to avoid hard requirement for PTR
+ # records pointing back to the same host name
+
+ conn.do_sasl_gssapi_bind()
+
+ if basedn is None:
+ # Use domain root base DN
+ basedn = ipautil.realm_to_suffix(info['dns_domain'])
+
+ entries = conn.get_entries(basedn, scope, filter, attrs)
+ except Exception, e:
+ msg = "Search on AD DC {host}:{port} failed with: {err}"\
+ .format(host=host, port=str(port), err=str(e))
+ if quiet:
+ root_logger.debug(msg)
+ else:
+ root_logger.warning(msg)
+ finally:
+ return entries
def __retrieve_trusted_domain_gc_list(self, domain):
"""
@@ -508,9 +594,13 @@ class DomainValidator(object):
except RuntimeError, e:
finddc_error = e
+ if not self._domains:
+ self._domains = self.get_trusted_domains()
+
info = dict()
info['auth'] = self._domains[domain][2]
servers = []
+
if result:
info['name'] = unicode(result.domain_name)
info['dns_domain'] = unicode(result.dns_domain)
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index d23f9b57f..e6f50a52c 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -766,10 +766,11 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
@contextmanager
-def private_ccache():
+def private_ccache(path=None):
- (desc, path) = tempfile.mkstemp(prefix='krbcc')
- os.close(desc)
+ if path is None:
+ (desc, path) = tempfile.mkstemp(prefix='krbcc')
+ os.close(desc)
original_value = os.environ.get('KRB5CCNAME', None)