diff options
Diffstat (limited to 'ipalib/krb_utils.py')
-rw-r--r-- | ipalib/krb_utils.py | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/ipalib/krb_utils.py b/ipalib/krb_utils.py new file mode 100644 index 000000000..e04c70ae7 --- /dev/null +++ b/ipalib/krb_utils.py @@ -0,0 +1,329 @@ +# Authors: John Dennis <jdennis@redhat.com> +# +# Copyright (C) 2012 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/>. + +import krbV +import time +import re +from ipapython.ipa_log_manager import * + +#------------------------------------------------------------------------------- + +# Kerberos constants, should be defined in krbV, but aren't +KRB5_GC_CACHED = 0x2 + +# 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 + + +krb_ticket_expiration_threshold = 60*5 # number of seconds to accmodate clock skew +krb5_time_fmt = '%m/%d/%y %H:%M:%S' +ccache_name_re = re.compile(r'^((\w+):)?(.+)') + +#------------------------------------------------------------------------------- + +def krb5_parse_ccache(name): + ''' + Given a Kerberos ccache name parse it into it's scheme and + location components. Currently valid values for the scheme + are: + + * FILE + * MEMORY + + The scheme is always returned as upper case. If the scheme + does not exist it defaults to FILE. + + :parameters: + name + The name of the Kerberos ccache. + :returns: + A two-tuple of (scheme, ccache) + ''' + match = ccache_name_re.search(name) + if match: + scheme = match.group(2) + location = match.group(3) + if scheme is None: + scheme = 'FILE' + else: + scheme = scheme.upper() + + return scheme, location + else: + raise ValueError('Invalid ccache name = "%s"' % name) + +def krb5_format_principal_name(user, realm): + ''' + Given a Kerberos user principal name and a Kerberos realm + return the Kerberos V5 user principal name. + + :parameters: + user + User principal name. + realm + The Kerberos realm the user exists in. + :returns: + Kerberos V5 user principal name. + ''' + return '%s@%s' % (user, realm) + +def krb5_format_service_principal_name(service, host, realm): + ''' + + Given a Kerberos service principal name, the host where the + service is running and a Kerberos realm return the Kerberos V5 + service principal name. + + :parameters: + service + Service principal name. + host + The DNS name of the host where the service is located. + realm + The Kerberos realm the service exists in. + :returns: + Kerberos V5 service principal name. + ''' + return '%s/%s@%s' % (service, host, realm) + +def krb5_format_tgt_principal_name(realm): + ''' + Given a Kerberos realm return the Kerberos V5 TGT name. + + :parameters: + realm + The Kerberos realm the TGT exists in. + :returns: + Kerberos V5 TGT name. + ''' + return krb5_format_service_principal_name('krbtgt', realm, realm) + +def krb5_format_time(timestamp): + ''' + Given a UNIX timestamp format it into a string in the same + manner the MIT Kerberos library does. Kerberos timestamps are + always in local time. + + :parameters: + timestamp + Unix timestamp + :returns: + formated string + ''' + 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 __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 + + self.debug('opening ccache file "%s"', ccache) + 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, 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, 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, e: + error_code = e.args[0] + if error_code == KRB5_CC_NOTFOUND: + self.debug('"%s" credential not found in "%s" ccache', + krbV_principal.name, self.ccache_str()) #pylint: disable=E1103 + raise KeyError('"%s" credential not found in "%s" ccache' % \ + (krbV_principal.name, self.ccache_str())) #pylint: disable=E1103 + raise e + except Exception, 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, 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('principal=%s, authtime=%s, starttime=%s, endtime=%s, renew_till=%s', + krbV_principal.name, #pylint: disable=E1103 + 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, e: + raise e + except Exception, e: + self.error('get_credential_times failed, principal="%s" error="%s"', krbV_principal.name, e) #pylint: disable=E1103 + 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, e: + return False + except Exception, 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 |