From 55d44dab48abaf03ad138be89a7e4cd1bde85173 Mon Sep 17 00:00:00 2001 From: Vojtech Juranek Date: Mon, 17 Dec 2012 22:16:19 +0100 Subject: [PATCH] Added kerberos authentication --- jenkinsapi/jenkins.py | 13 +++++++---- jenkinsapi/jenkinsbase.py | 3 ++- jenkinsapi/utils/urlopener.py | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/jenkinsapi/jenkins.py b/jenkinsapi/jenkins.py index b1a11a2..f306023 100644 --- a/jenkinsapi/jenkins.py +++ b/jenkinsapi/jenkins.py @@ -4,7 +4,7 @@ from jenkinsapi.job import Job from jenkinsapi.view import View from jenkinsapi.node import Node from jenkinsapi.exceptions import UnknownJob, NotAuthorized -from utils.urlopener import mkurlopener, mkopener, NoAuto302Handler +from utils.urlopener import mkurlopener, mkkrbopener, mkopener, NoAuto302Handler import logging import time import urllib2 @@ -22,7 +22,7 @@ class Jenkins(JenkinsBase): """ Represents a jenkins environment. """ - def __init__(self, baseurl, username=None, password=None, proxyhost=None, proxyport=None, proxyuser=None, proxypass=None, formauth=False): + def __init__(self, baseurl, username=None, password=None, proxyhost=None, proxyport=None, proxyuser=None, proxypass=None, formauth=False, krbauth=False): """ :param baseurl: baseurl for jenkins instance including port, str @@ -40,13 +40,13 @@ class Jenkins(JenkinsBase): self.proxyport = proxyport self.proxyuser = proxyuser self.proxypass = proxypass - JenkinsBase.__init__(self, baseurl, formauth=formauth) + JenkinsBase.__init__(self, baseurl, formauth=formauth, krbauth=krbauth) def _clone(self): return Jenkins(self.baseurl, username=self.username, password=self.password, proxyhost=self.proxyhost, proxyport=self.proxyport, proxyuser=self.proxyuser, - proxypass=self.proxypass, formauth=self.formauth) + proxypass=self.proxypass, formauth=self.formauth, krbauth=self.krbauth) def get_proxy_auth(self): return self.proxyhost, self.proxyport, self.proxyuser, self.proxypass @@ -64,6 +64,8 @@ class Jenkins(JenkinsBase): def get_opener(self): if self.formauth: return self.get_login_opener() + if self.krbauth: + return self.get_krb_opener() return mkurlopener(*self.get_auth()) def get_login_opener(self): @@ -75,6 +77,9 @@ class Jenkins(JenkinsBase): hdrs.append(urllib2.HTTPCookieProcessor(mcj)) return mkopener(*hdrs) + def get_krb_opener(self): + return mkkrbopener(self.baseurl) + def login(self): formdata = dict(j_username=self.username, j_password=self.password, remember_me=True, form='/') diff --git a/jenkinsapi/jenkinsbase.py b/jenkinsapi/jenkinsbase.py index ca5c182..b17aab2 100644 --- a/jenkinsapi/jenkinsbase.py +++ b/jenkinsapi/jenkinsbase.py @@ -23,12 +23,13 @@ class JenkinsBase(object): def __str__(self): raise NotImplemented - def __init__(self, baseurl, poll=True, formauth=False): + def __init__(self, baseurl, poll=True, formauth=False, krbauth=False): """ Initialize a jenkins connection """ self.baseurl = baseurl self.formauth = formauth + self.krbauth = krbauth if poll and not self.formauth: try: self.poll() diff --git a/jenkinsapi/utils/urlopener.py b/jenkinsapi/utils/urlopener.py index 9dd5a2f..d13fd5d 100644 --- a/jenkinsapi/utils/urlopener.py +++ b/jenkinsapi/utils/urlopener.py @@ -1,5 +1,7 @@ import urllib2 import base64 +import kerberos as krb +from urlparse import urlparse import logging @@ -30,6 +32,21 @@ class PreemptiveBasicAuthHandler(urllib2.BaseHandler): def https_request(self,req): return self.http_request(req) +class KerberosAuthHandler(urllib2.BaseHandler): + """ + A BaseHandler class that will add Kerberos Auth headers to a request + """ + def __init__(self,tgt): + self.tgt = tgt + + def http_request(self,req): + req.add_unredirected_header('Authorization', 'Negotiate %s' % self.tgt) + return req + + def https_request(self,req): + return self.http_request(req) + + def mkurlopener( jenkinsuser, jenkinspass, jenkinsurl, proxyhost, proxyport, proxyuser, proxypass ): """ Creates an url opener that works with both jenkins auth and proxy auth @@ -51,6 +68,20 @@ def mkurlopener( jenkinsuser, jenkinspass, jenkinsurl, proxyhost, proxyport, pro opener = urllib2.build_opener(*handlers) return opener.open +def mkkrbopener( jenkinsurl ): + """ + Creates an url opener that works with kerberos auth + + :param jenkinsurl: jenkins url, str + :return: urllib2.opener configured for kerberos auth + """ + handlers = [] + for handler in get_kerberos_auth_handler(jenkinsurl=jenkinsurl): + handlers.append(handler) + opener = urllib2.build_opener(*handlers) + return opener.open + + def mkopener(*handlers): opener = urllib2.build_opener(*handlers) return opener.open @@ -100,6 +131,27 @@ def get_proxy_handler(proxyhost, proxyport, proxyuser, proxypass): return [proxy_handler, proxy_auth_handler] +def get_kerberos_auth_handler(jenkinsurl): + """ + Get a handler which enabled authentication over GSSAPI + + :param jenkinsurl: jenkins base url, str + :return: a list of handlers + """ + jenkinsnetloc = urlparse(jenkinsurl).netloc + assert type( jenkinsnetloc ) == str, "Jenkins network location should be a string, got %s" % repr( jenkinsnetloc ) + + _ignore, ctx = krb.authGSSClientInit('HTTP@%s' % jenkinsnetloc, gssflags=krb.GSS_C_DELEG_FLAG|krb.GSS_C_MUTUAL_FLAG|krb.GSS_C_SEQUENCE_FLAG) + rc = krb.authGSSClientStep(ctx,'') + if rc != krb.AUTH_GSS_CONTINUE: + return [] + tgt = krb.authGSSClientResponse(ctx) + if not tgt: + return [] + + krb_handler = KerberosAuthHandler(tgt) + return [ krb_handler ] + class NoAuto302Handler(urllib2.HTTPRedirectHandler): def http_error_302(self, req, fp, code, msg, hdrs): return fp -- 2.7.4