From 480cdfb540c0468f87337cee2ba625095c447ffd Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Nov 22 2016 21:45:31 +0000 Subject: Implement GSSAPI login Signed-off-by: Patrick Uiterwijk --- diff --git a/cli/koji b/cli/koji index f093c50..ee331ec 100755 --- a/cli/koji +++ b/cli/koji @@ -6968,6 +6968,8 @@ def handle_moshimoshi(options, session, args): authtype = u.get('authtype', getattr(session, 'authtype', None)) if authtype == koji.AUTHTYPE_NORMAL: print "Authenticated via password" + elif authtype == koji.AUTHTYPE_GSSAPI: + print "Authenticated via GSSAPI" elif authtype == koji.AUTHTYPE_KERB: print "Authenticated via Kerberos principal %s" % u["krb_principal"] elif authtype == koji.AUTHTYPE_SSL: diff --git a/koji.spec b/koji.spec index 3beff7e..0022eab 100644 --- a/koji.spec +++ b/koji.spec @@ -29,6 +29,7 @@ Requires: python-krbV >= 1.0.13 Requires: rpm-python Requires: pyOpenSSL Requires: python-requests +Requires: python-requests-kerberos Requires: python-urlgrabber Requires: python-dateutil BuildRequires: python diff --git a/koji/__init__.py b/koji/__init__.py index 90b770c..4723844 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -57,6 +57,10 @@ try: import requests except ImportError: #pragma: no cover requests = None +try: + from requests_kerberos import HTTPKerberosAuth +except ImportError: #pragma: no cover + HTTPKerberosAuth = None import rpm import shutil import signal @@ -192,6 +196,7 @@ USER_STATUS = Enum(( AUTHTYPE_NORMAL = 0 AUTHTYPE_KERB = 1 AUTHTYPE_SSL = 2 +AUTHTYPE_GSSAPI = 3 #dependency types DEP_REQUIRE = 0 @@ -2017,6 +2022,14 @@ class ClientSession(object): principal. The principal must be in the "ProxyPrincipals" list on the server side.""" + if principal is None and keytab is None and ccache is None: + try: + # Silently try GSSAPI first + if self.gssapi_login(proxyuser=proxyuser): + return True + except: + pass + if not krbV: raise exceptions.ImportError( "Please install python-krbV to use kerberos." @@ -2094,6 +2107,37 @@ class ClientSession(object): return '%s/%s@%s' % (service, servername, realm) + def gssapi_login(self, proxyuser=None): + if not HTTPKerberosAuth: + raise exceptions.ImportError( + "Please install python-requests-kerberos to use GSSAPI." + ) + + # force https + uri = urlparse.urlsplit(self.baseurl) + if uri[0] != 'https': + self.baseurl = 'https://%s%s' % (uri[1], uri[2]) + + # Force a new session + self.new_session() + + # 60 second timeout during login + old_opts = self.opts + self.opts = old_opts.copy() + self.opts['timeout'] = 60 + self.opts['auth'] = HTTPKerberosAuth() + try: + sinfo = self.callMethod('sslLogin', proxyuser) + finally: + self.opts = old_opts + if not sinfo: + raise AuthError, 'unable to obtain a session' + + self.setSession(sinfo) + + self.authtype = AUTHTYPE_GSSAPI + return True + def ssl_login(self, cert=None, ca=None, serverca=None, proxyuser=None): cert = cert or self.opts.get('cert') serverca = serverca or self.opts.get('serverca') @@ -2222,6 +2266,9 @@ class ClientSession(object): if cert: # TODO: we really only need to do this for ssllogin calls callopts['cert'] = cert + auth = self.opts.get('auth') + if auth: + callopts['auth'] = auth timeout = self.opts.get('timeout') if timeout: callopts['timeout'] = timeout diff --git a/koji/auth.py b/koji/auth.py index 3dbe1cc..ef2f338 100644 --- a/koji/auth.py +++ b/koji/auth.py @@ -374,27 +374,28 @@ class Session(object): if self.logged_in: raise koji.AuthError, "Already logged in" - if context.environ['wsgi.url_scheme'] != 'https': - raise koji.AuthError, 'cannot call sslLogin() via a non-https connection: %s' % context.environ['wsgi.url_scheme'] - - if context.environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS': - raise koji.AuthError, 'could not verify client: %s' % context.environ.get('SSL_CLIENT_VERIFY') + if context.environ.get('REMOTE_USER'): + username = context.environ.get('REMOTE_USER') + client_dn = username + authtype = koji.AUTHTYPE_GSSAPI + else: + if context.environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS': + raise koji.AuthError, 'could not verify client: %s' % context.environ.get('SSL_CLIENT_VERIFY') - name_dn_component = context.opts.get('DNUsernameComponent', 'CN') - client_name = context.environ.get('SSL_CLIENT_S_DN_%s' % name_dn_component) - if not client_name: - raise koji.AuthError, 'unable to get user information (%s) from client certificate' % name_dn_component + name_dn_component = context.opts.get('DNUsernameComponent', 'CN') + username = context.environ.get('SSL_CLIENT_S_DN_%s' % name_dn_component) + if not username: + raise koji.AuthError, 'unable to get user information (%s) from client certificate' % name_dn_component + client_dn = context.environ.get('SSL_CLIENT_S_DN') + authtype = koji.AUTHTYPE_SSL if proxyuser: - client_dn = context.environ.get('SSL_CLIENT_S_DN') proxy_dns = [dn.strip() for dn in context.opts.get('ProxyDNs', '').split('|')] if client_dn in proxy_dns: # the SSL-authenticated user authorized to login other users username = proxyuser else: raise koji.AuthError, '%s is not authorized to login other users' % client_dn - else: - username = client_name cursor = context.cnx.cursor() query = """SELECT id FROM users @@ -416,7 +417,7 @@ class Session(object): if hostip == '127.0.0.1': hostip = socket.gethostbyname(socket.gethostname()) - sinfo = self.createSession(user_id, hostip, koji.AUTHTYPE_SSL) + sinfo = self.createSession(user_id, hostip, authtype) return sinfo def makeExclusive(self, force=False):