Tuesday, September 17, 2019

How to implement SPNEGO authentication in kerberos with python

Let me explain my best  how spnego authentication work and how can we implement SPNEGO authentication in a few lines in python.

In this test, I used MIT kerberos server (Active directory installation and configuration is more painful for me) refer my blog on how to setup and configure MIT kerberos in centos. Linux Ldap authentication with kerberos backend and openldap SASL Passthrough  and kerberos  (python module to implement the spengo authentication (pip install kerberos))


We need to create service principle in Kerberos server for the domain we are going to use www.sathish.com. since webserver use HTTP protocol, kerberos service principle would be HTTP.

So we need to create HTTP service principle with below name for www.sathish.com. ( TANU.COM is my kerberos domain.)

HTTP/www.sathish.com@TANU.COM

Run following commands in kerberos server to create the keytab.

kadmin.local -q "addprinc -randkey HTTP/www.sathish.com@TANU.COM"
kadmin.local -q "ktadd -k /www/http/secure/www.sathish.com.keytab HTTP/www.sathish.com

set below environment variable to point the keytab

export KRB5_KTNAME=/www/http/secure/www.sathish.com.keytab

(if we dont set above variable python code with throw below exception

    rc, state = kerberos.authGSSServerInit('HTTP')
GSSError: (('Unspecified GSS failure.  Minor code may provide more information', 851968), ('', 100002))

)

Now we are good to run our own code to implement the spnego kerberos authentication.


Webserver (simple python http server with custom code) running on CENTOS  with url http:/www.sathish.com



Client machine(one Linux  and  one windows 10 desktop)

How SPNEGO work:

1) client request url using Internet explore   ----> http://www.sathish.com

2) webserver get request and check  "Authorization" header. if header not present in the request , webserver send the response with 401 and WWW-Authenticate','Negotiate' header  back to client

                       header=s.headers.get('Authorization')
                        if not header:
                        s.send_response(401)
                        s.send_header('WWW-Authenticate','Negotiate')

3) Internet explore get the response from webserver and request URL with Authorization header with valid token.

4)  Then webserver read the token from Authorization header and decrypt  token using service key stored in the keytab for this HTTP Service principle HTTP/www.sathish.com@TANU.COM.

If the decryption is successful then user token is valid and we can mark user authentication is completed.

               rc, state = kerberos.authGSSServerInit('HTTP')
                if rc != kerberos.AUTH_GSS_COMPLETE:
                        return None
                rc = kerberos.authGSSServerStep(state, token)
                if rc == kerberos.AUTH_GSS_COMPLETE:
                        user = kerberos.authGSSServerUserName(state)

  if token is valid, webserver can  retrieve the username.

5) Post Authentication webserver response back to client.

Here is the full code for testing

       
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
import sys
import kerberos
import re

class AuthHandler(SimpleHTTPRequestHandler):
    ''' Main class to present webpages and authentication. '''
    def do_HEAD(self):
        print "send header"
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_AUTHHEAD(self):
        print "send header"
        self.send_response(401)
        #self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"')
        self.send_header('WWW-Authenticate', 'Negotiate')
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        ''' Present frontpage with user authentication. '''
        if self.headers.getheader('Authorization') == None:
            self.do_AUTHHEAD()
            self.wfile.write('no auth header received')
            pass
        #elif self.headers.getheader('Authorization') == 'Negotiate':
        elif re.match(r'^Negotiate', self.headers.getheader('Authorization')):
            #print self.headers.getheader('Authorization')
            header=self.headers.getheader('Authorization')
            token = ''.join(header.split()[1:])
            print token
            rc, state = kerberos.authGSSServerInit('HTTP')
            if rc != kerberos.AUTH_GSS_COMPLETE:
                return None
            rc = kerberos.authGSSServerStep(state, token)
            if rc == kerberos.AUTH_GSS_COMPLETE:
                user = kerberos.authGSSServerUserName(state)
                print user
                SimpleHTTPRequestHandler.do_GET(self)
                pass
        else:
            self.do_AUTHHEAD()
            self.wfile.write(self.headers.getheader('Authorization'))
            self.wfile.write('not authenticated')
            pass

def test(HandlerClass = AuthHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    if len(sys.argv)<2: code="" port="" print="" simpleauthserver.py="" sys.exit="" test="" usage="">
 




Here is the curl with negotiate request output



[root@krbserver ~]# curl --negotiate -u : -b ~/cookiejar.txt -c ~/cookiejar.txt  http://www.sathish.com/ -v
* About to connect() to www.sathish.com port 80 (#0)
*   Trying 192.168.100.31...
* Connected to www.sathish.com (192.168.100.31) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.sathish.com
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 401 Unauthorized
< Server: BaseHTTP/0.3 Python/2.7.5
< Date: Sun, 13 May 2018 17:09:17 GMT
< WWW-Authenticate: Negotiate
* Closing connection 0
* Issue another request to this URL: 'http://www.sathish.com/'
* About to connect() to www.sathish.com port 80 (#1)
*   Trying 192.168.100.31...
* Connected to www.sathish.com (192.168.100.31) port 80 (#1)
* Server auth using GSS-Negotiate with user ''
> GET / HTTP/1.0
> Authorization: Negotiate YIICWwYJKoZIhvcSAQICAQBuggJKMIICRqADAgEFoQMCAQ6iBwMFACAAAACjggFiYYIBXjCCAVqgAwIBBaEKGwhUQU5VLkNPTaIZMBegAwIBA6EQMA4bBEhUVFAbBmtyYmNuMaOCASowggEmoAMCARChAwIBA6KCARgEggEUoewKZoNfchhCOjox2e4y465Wnz2E94henxLTzQq70A270SbVPef5oIkqXXupDK9/JPhT2QPRUXEhWYoH41KwxfK4Q28H2OLpzkvCCk0wVLjNFrJZjhEm1mxV4eKUl4AAi/nSwYUCZGAEFrEUVwWQwP8apFCsGolvZXP9Skc0XViAZTM5JKL6mVgQcwwuvs+J6Cf6elzZPR+7BLrJRFHlUTvMS3C8H+WClAh9KDeSvSbxYvG4ZRhNgXqRrD259O5iGKySf126ToG00Zece4Fz0rHjSRJUnrV4H2Npku2sMSo7M9DnwBiDW8kQ84yQnpXbc1hOqiFIOWRc31ZuQ2O0OUormHm3mUZY+ABC2zA8BdIPLZvspIHKMIHHoAMCARCigb8EgbxC+6wp6oKhdTtVS6nFomRylJ1p8t1hn8nqs/rsNTHoia6csinnPP2ZR2oqydnaVq9cGMqA6q7U5Yz1iXlf0dGOWmOewucN8URLkYDC6sne2acqNkWCpOVyJJ9/AaC5QTFvVyR+Nj7aRW3LpSgJdrwrMybrfgYawthj0q44z7+FQi1T0K42sLmTvelkqvXFdVBn2/aqcaXCkkA9OI5IrZH0fqwt25nMm/mTkhsgH+ntuzjtm4VDViLV2DuZcA==
> User-Agent: curl/7.29.0
> Host: www.sathish.com
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.3 Python/2.7.5
< Date: Sun, 13 May 2018 17:09:19 GMT
< Content-type: text/html
<
* Closing connection 1
<html><head><title>Title goes here.</title></head><body><p>This is a test.</p></body></html>[root@krbserver ~]#










No comments:

Post a Comment