diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/krb_utils.py | 333 | ||||
-rw-r--r-- | ipalib/plugins/kerberos.py | 125 | ||||
-rw-r--r-- | ipalib/plugins/passwd.py | 6 | ||||
-rw-r--r-- | ipalib/plugins/vault.py | 7 | ||||
-rw-r--r-- | ipalib/rpc.py | 9 | ||||
-rw-r--r-- | ipalib/util.py | 12 |
6 files changed, 77 insertions, 415 deletions
diff --git a/ipalib/krb_utils.py b/ipalib/krb_utils.py index 19cd0ad79..db1cffc1e 100644 --- a/ipalib/krb_utils.py +++ b/ipalib/krb_utils.py @@ -16,25 +16,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import krbV import time import re -from ipapython.ipa_log_manager import * +import gssapi -#------------------------------------------------------------------------------- - -# Kerberos constants, should be defined in krbV, but aren't -KRB5_GC_CACHED = 0x2 +from ipalib import errors -# Kerberos error codes, should be defined in krbV, but aren't -KRB5_CC_NOTFOUND = -1765328243 # Matching credential not found -KRB5_FCC_NOFILE = -1765328189 # No credentials cache found -KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN = -1765328377 # Server not found in Kerberos database -KRB5KRB_AP_ERR_TKT_EXPIRED = -1765328352 # Ticket expired -KRB5_FCC_PERM = -1765328190 # Credentials cache permissions incorrect -KRB5_CC_FORMAT = -1765328185 # Bad format in credentials cache -KRB5_REALM_CANT_RESOLVE = -1765328164 # Cannot resolve network address for KDC in requested realm +#------------------------------------------------------------------------------- +# Kerberos error codes +KRB5_CC_NOTFOUND = 2529639053 # Matching credential not found +KRB5_FCC_NOFILE = 2529639107 # No credentials cache found +KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN = 2529638919 # Server not found in Kerberos database +KRB5KRB_AP_ERR_TKT_EXPIRED = 2529638944 # Ticket expired +KRB5_FCC_PERM = 2529639106 # Credentials cache permissions incorrect +KRB5_CC_FORMAT = 2529639111 # Bad format in credentials cache +KRB5_REALM_CANT_RESOLVE = 2529639132 # Cannot resolve network address for KDC in requested realm krb_ticket_expiration_threshold = 60*5 # number of seconds to accmodate clock skew krb5_time_fmt = '%m/%d/%y %H:%M:%S' @@ -136,260 +133,66 @@ def krb5_format_time(timestamp): ''' return time.strftime(krb5_time_fmt, time.localtime(timestamp)) -class KRB5_CCache(object): - ''' - Kerberos stores a TGT (Ticket Granting Ticket) and the service - tickets bound to it in a ccache (credentials cache). ccaches are - bound to a Kerberos user principal. This class opens a Kerberos - ccache and allows one to manipulate it. Most useful is the - extraction of ticket entries (cred's) in the ccache and the - ability to examine their attributes. +def get_credentials(name=None, ccache_name=None): ''' + Obtains GSSAPI credentials with given principal name from ccache. When no + principal name specified, it retrieves the default one for given + credentials cache. - def __init__(self, ccache): - ''' - :parameters: - ccache - The name of a Kerberos ccache used to hold Kerberos tickets. - :returns: - `KRB5_CCache` object encapsulting the ccache. - ''' - log_mgr.get_logger(self, True) - self.context = None - self.scheme = None - self.name = None - self.ccache = None - self.principal = None - - try: - self.context = krbV.default_context() - self.scheme, self.name = krb5_parse_ccache(ccache) - self.ccache = krbV.CCache(name=str(ccache), context=self.context) - self.principal = self.ccache.principal() - except krbV.Krb5Error as e: - error_code = e.args[0] - message = e.args[1] - if error_code == KRB5_FCC_NOFILE: - raise ValueError('"%s", ccache="%s"' % (message, ccache)) - else: - raise e - - def ccache_str(self): - ''' - A Kerberos ccache is identified by a name comprised of a - scheme and location component. This function returns that - canonical name. See `krb5_parse_ccache()` - - :returns: - The name of ccache with it's scheme and location components. - ''' - - return '%s:%s' % (self.scheme, self.name) - - def __str__(self): - return 'cache="%s" principal="%s"' % (self.ccache_str(), self.principal.name) - - def get_credentials(self, principal): - ''' - Given a Kerberos principal return the krbV credentials - tuple describing the credential. If the principal does - not exist in the ccache a KeyError is raised. - - :parameters: - principal - The Kerberos principal whose ticket is being retrieved. - The principal may be either a string formatted as a - Kerberos V5 principal or it may be a `krbV.Principal` - object. - :returns: - A krbV credentials tuple. If the principal is not in the - ccache a KeyError is raised. - - ''' - - if isinstance(principal, krbV.Principal): - krbV_principal = principal - else: - try: - krbV_principal = krbV.Principal(str(principal), self.context) - except Exception as e: - self.error('could not create krbV principal from "%s", %s', principal, e) - raise e - - creds_tuple = (self.principal, - krbV_principal, - (0, None), # keyblock: (enctype, contents) - (0, 0, 0, 0), # times: (authtime, starttime, endtime, renew_till) - 0,0, # is_skey, ticket_flags - None, # addrlist - None, # ticket_data - None, # second_ticket_data - None) # adlist - try: - cred = self.ccache.get_credentials(creds_tuple, KRB5_GC_CACHED) - except krbV.Krb5Error as e: - error_code = e.args[0] - if error_code == KRB5_CC_NOTFOUND: - raise KeyError('"%s" credential not found in "%s" ccache' % \ - (krbV_principal.name, self.ccache_str())) - raise e - except Exception as e: - raise e - - return cred - - def get_credential_times(self, principal): - ''' - Given a Kerberos principal return the ticket timestamps if the - principal's ticket in the ccache is valid. If the principal - does not exist in the ccache a KeyError is raised. - - The return credential time values are Unix timestamps in - localtime. - - The returned timestamps are: - - authtime - The time when the ticket was issued. - starttime - The time when the ticket becomes valid. - endtime - The time when the ticket expires. - renew_till - The time when the ticket becomes no longer renewable (if renewable). - - :parameters: - principal - The Kerberos principal whose ticket is being validated. - The principal may be either a string formatted as a - Kerberos V5 principal or it may be a `krbV.Principal` - object. - :returns: - return authtime, starttime, endtime, renew_till - ''' - - if isinstance(principal, krbV.Principal): - krbV_principal = principal - else: - try: - krbV_principal = krbV.Principal(str(principal), self.context) - except Exception as e: - self.error('could not create krbV principal from "%s", %s', principal, e) - raise e - - try: - cred = self.get_credentials(krbV_principal) - authtime, starttime, endtime, renew_till = cred[3] - - self.debug('get_credential_times: principal=%s, authtime=%s, starttime=%s, endtime=%s, renew_till=%s', - krbV_principal.name, - krb5_format_time(authtime), krb5_format_time(starttime), - krb5_format_time(endtime), krb5_format_time(renew_till)) - - return authtime, starttime, endtime, renew_till - - except KeyError as e: - raise e - except Exception as e: - self.error('get_credential_times failed, principal="%s" error="%s"', krbV_principal.name, e) - raise e - - def credential_is_valid(self, principal): - ''' - Given a Kerberos principal return a boolean indicating if the - principal's ticket in the ccache is valid. If the ticket is - not in the ccache False is returned. If the ticket - exists in the ccache it's validity is checked and returned. - - :parameters: - principal - The Kerberos principal whose ticket is being validated. - The principal may be either a string formatted as a - Kerberos V5 principal or it may be a `krbV.Principal` - object. - :returns: - True if the principal's ticket exists and is valid. False if - the ticket does not exist or if the ticket is not valid. - ''' - - try: - authtime, starttime, endtime, renew_till = self.get_credential_times(principal) - except KeyError as e: - return False - except Exception as e: - self.error('credential_is_valid failed, principal="%s" error="%s"', principal, e) - raise e - - - now = time.time() - if starttime > now: - return False - if endtime < now: - return False - return True - - def valid(self, host, realm): - ''' - Test to see if ldap service ticket or the TGT is valid. - - :parameters: - host - ldap server - realm - kerberos realm - :returns: - True if either the ldap service ticket or the TGT is valid, - False otherwise. - ''' - - try: - principal = krb5_format_service_principal_name('HTTP', host, realm) - valid = self.credential_is_valid(principal) - if valid: - return True - except KeyError: - pass - - try: - principal = krb5_format_tgt_principal_name(realm) - valid = self.credential_is_valid(principal) - return valid - except KeyError: - return False - - def endtime(self, host, realm): - ''' - Returns the minimum endtime for tickets of interest (ldap service or TGT). + :parameters: + name + gssapi.Name object specifying principal or None for the default + ccache_name + string specifying Kerberos credentials cache name or None for the + default + :returns: + gssapi.Credentials object + ''' + store = None + if ccache_name: + store = {'ccache': ccache_name} + try: + return gssapi.Credentials(usage='initiate', name=name, store=store) + except gssapi.exceptions.GSSError as e: + if e.min_code == KRB5_FCC_NOFILE: + raise ValueError('"%s", ccache="%s"' % (e.message, ccache_name)) + raise + +def get_principal(ccache_name=None): + ''' + Gets default principal name from given credentials cache. - :parameters: - host - ldap server - realm - kerberos realm - :returns: - UNIX timestamp value. - ''' + :parameters: + ccache_name + string specifying Kerberos credentials cache name or None for the + default + :returns: + Default principal name as string + ''' + creds = get_credentials(ccache_name=ccache_name) + return unicode(creds.name) - result = 0 - try: - principal = krb5_format_service_principal_name('HTTP', host, realm) - authtime, starttime, endtime, renew_till = self.get_credential_times(principal) - if result: - result = min(result, endtime) - else: - result = endtime - except KeyError: - pass +def get_credentials_if_valid(name=None, ccache_name=None): + ''' + Obtains GSSAPI credentials with principal name from ccache. When no + principal name specified, it retrieves the default one for given + credentials cache. When the credentials cannot be retrieved or aren't valid + it returns None. - try: - principal = krb5_format_tgt_principal_name(realm) - authtime, starttime, endtime, renew_till = self.get_credential_times(principal) - if result: - result = min(result, endtime) - else: - result = endtime - except KeyError: - pass + :parameters: + name + gssapi.Name object specifying principal or None for the default + ccache_name + string specifying Kerberos credentials cache name or None for the + default + :returns: + gssapi.Credentials object or None if valid credentials weren't found + ''' - self.debug('KRB5_CCache %s endtime=%s (%s)', self.ccache_str(), result, krb5_format_time(result)) - return result + try: + creds = get_credentials(name=name, ccache_name=ccache_name) + if creds.lifetime > 0: + return creds + return None + except gssapi.exceptions.ExpiredCredentialsError: + return None diff --git a/ipalib/plugins/kerberos.py b/ipalib/plugins/kerberos.py deleted file mode 100644 index 3ed6d7671..000000000 --- a/ipalib/plugins/kerberos.py +++ /dev/null @@ -1,125 +0,0 @@ -# Authors: -# Jason Gerard DeRose <jderose@redhat.com> -# -# Copyright (C) 2008 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/>. - -""" -Backend plugin for Kerberos. - -This wraps the python-kerberos and python-krbV bindings. -""" - -import sys -from ipalib import api -from ipalib.backend import Backend -from ipalib.plugable import Registry -import krbV - -register = Registry() - -ENCODING = 'UTF-8' - - -@register() -class krb(Backend): - """ - Kerberos backend plugin. - - This wraps the `krbV` bindings (and will eventually wrap the `kerberos` - bindings also). Importantly, this plugin does correct Unicode - encoding/decoding of values going-to/coming-from the bindings. - """ - - def __default_ccache(self): - """ - Return the ``krbV.CCache`` for the default credential cache. - """ - return krbV.default_context().default_ccache() - - def __default_principal(self): - """ - Return the ``krb5.Principal`` for the default credential cache. - """ - return self.__default_ccache().principal() - - def __get_ccache(self, ccname): - """ - Return the ``krbV.CCache`` for the ``ccname`` credential ccache. - """ - return krbV.CCache(ccname) - - def __get_principal(self, ccname): - """ - Return the ``krb5.Principal`` for the ``ccname`` credential ccache. - """ - return self.__get_ccache(ccname).principal() - - def default_ccname(self): - """ - Return the default ccache file name (schema+name). - - This will return something like 'FILE:/tmp/krb5cc_500'. - - This cannot return anything meaningful if used in the server as a - request is processed. - """ - default_ccache = self.__default_ccache() - ccname = "%(type)s:%(name)s" % dict(type=default_ccache.type, - name=default_ccache.name) - return ccname - - def default_principal(self): - """ - Return the principal name in default credential cache. - - This will return something like 'admin@EXAMPLE.COM'. If no credential - cache exists for the invoking user, None is returned. - - This cannot return anything meaningful if used in the server as a - request is processed. - """ - return self.__default_principal().name.decode(ENCODING) - - def default_realm(self): - """ - Return the realm from the default credential cache. - - This will return something like 'EXAMPLE.COM'. If no credential cache - exists for the invoking user, None is returned. - - This cannot return anything meaningful if used in the server as a - request is processed. - """ - return krbV.default_context().default_realm.decode(ENCODING) - - def get_principal(self, ccname): - """ - Return the principal from credential cache file at ``ccname``. - - This will return something like 'admin@EXAMPLE.COM'. - """ - return self.__get_principal(ccname).name.decode(ENCODING) - - def get_realm(self, ccname): - """ - Return the realm from credential cache file at ``ccname``. - - This will return something like 'EXAMPLE.COM'. - """ - return self.__get_principal(ccname).realm.decode(ENCODING) - - diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py index f5fc14d51..a4f791c1b 100644 --- a/ipalib/plugins/passwd.py +++ b/ipalib/plugins/passwd.py @@ -17,7 +17,7 @@ # 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 import api, errors, util +from ipalib import api, errors, krb_utils from ipalib import Command from ipalib import Str, Password from ipalib import _ @@ -58,7 +58,7 @@ def get_current_password(principal): current password is prompted for, otherwise return a fixed value to be ignored later. """ - current_principal = util.get_current_principal() + current_principal = krb_utils.get_principal() if current_principal == normalize_principal(principal): return None else: @@ -74,7 +74,7 @@ class passwd(Command): label=_('User name'), primary_key=True, autofill=True, - default_from=lambda: util.get_current_principal(), + default_from=lambda: krb_utils.get_principal(), normalizer=lambda value: normalize_principal(value), ), Password('password', diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py index 6a07a76b5..18436e399 100644 --- a/ipalib/plugins/vault.py +++ b/ipalib/plugins/vault.py @@ -34,7 +34,6 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key,\ load_pem_private_key import nss.nss as nss -import krbV from ipalib.frontend import Command, Object, Local from ipalib import api, errors @@ -640,7 +639,7 @@ class vault_add(PKQuery, Local): else: backend = self.api.Backend.rpcclient if not backend.isconnected(): - backend.connect(ccache=krbV.default_context().default_ccache()) + backend.connect() if vault_type == u'standard': @@ -1239,7 +1238,7 @@ class vault_archive(PKQuery, Local): else: backend = self.api.Backend.rpcclient if not backend.isconnected(): - backend.connect(ccache=krbV.default_context().default_ccache()) + backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] @@ -1508,7 +1507,7 @@ class vault_retrieve(PKQuery, Local): else: backend = self.api.Backend.rpcclient if not backend.isconnected(): - backend.connect(ccache=krbV.default_context().default_ccache()) + backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 04b8d01d9..9d0fc8f7b 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -55,7 +55,6 @@ from ipalib.errors import (public_errors, UnknownError, NetworkError, KerberosError, XMLRPCMarshallError, JSONError, ConversionError) from ipalib import errors, capabilities from ipalib.request import context, Connection -from ipalib.util import get_current_principal from ipapython.ipa_log_manager import root_logger from ipapython import ipautil from ipapython import kernel_keyring @@ -66,7 +65,8 @@ from ipalib.text import _ import ipapython.nsslib from ipapython.nsslib import NSSHTTPS, NSSConnection from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT_EXPIRED, \ - KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE + KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, \ + KRB5_REALM_CANT_RESOLVE, get_principal from ipapython.dn import DN from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES from ipalib import api @@ -518,10 +518,7 @@ class KerbTransport(SSLTransport): self._sec_context = None def _handle_exception(self, e, service=None): - # kerberos library coerced error codes to signed, gssapi uses unsigned minor = e.min_code - if minor & (1 << 31): - minor -= 1 << 32 if minor == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: raise errors.ServiceError(service=service) elif minor == KRB5_FCC_NOFILE: @@ -835,7 +832,7 @@ class RPCClient(Connectible): delegate=False, nss_dir=None): try: rpc_uri = self.env[self.env_rpc_uri_key] - principal = get_current_principal() + principal = get_principal() setattr(context, 'principal', principal) # We have a session cookie, try using the session URI to see if it # is still valid diff --git a/ipalib/util.py b/ipalib/util.py index 0d5f85040..4a75b820a 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -61,18 +61,6 @@ def json_serialize(obj): return '' return json_serialize(obj.__json__()) -def get_current_principal(): - try: - import gssapi - cred = gssapi.Credentials(usage='initiate') - return unicode(cred.name) - except ImportError: - raise RuntimeError('python-gssapi is not available.') - except gssapi.exceptions.GSSError: - #TODO: do a kinit? - raise errors.CCacheError() - - def validate_host_dns(log, fqdn): """ See if the hostname has a DNS A/AAAA record. |