Skip to content
Advertisement

“TypeError: argument 1 must be str, not None” when running Flask-Kerberos

I am trying to run the Flask-Kerberos example with a valid keytab file (it works with WSGI-Kerberos).

Here is the content of my ‘example.py’ file

from flask import Flask
from flask import render_template
from flask_kerberos import init_kerberos
from flask_kerberos import requires_authentication
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

@app.route("/")
@requires_authentication
def index(user):
    return render_template('index.html', user=user)

if __name__ == '__main__':
    init_kerberos(app)
    app.run()

here is a ‘config.py’

import os
import base64
from dotenv import load_dotenv

basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.flaskenv'))

class Config(object):

        # Setup Secret Key for Application
        SECRET_KEY = os.environ.get('SECRET_KEY') or str(base64.b64encode('you-will-never-guess'.encode("utf-8")))

        # Location of the keytab file
        KRB5_KTNAME = "K000007.keytab"

and here is a ‘.flaskenv’

FLASK_APP=example.py
FLASK_RUN_HOST="0.0.0.0"
FLASK_RUN_PORT=5000
FLASK_ENV=development

However, when start the Flask via flask run I am getting the following error in CMD:

(venv) Server@User:~/.../flask_kerberos_example$ flask run
 * Serving Flask app "example.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 603-674-916
a.b.c.d - - [23/Jun/2021 08:47:51] "GET / HTTP/1.1" 401 -
a.b.c.d - - [23/Jun/2021 08:47:51] "GET / HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/venv/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/venv/lib/python3.7/site-packages/flask_kerberos.py", line 106, in decorated
    rc = _gssapi_authenticate(token)
  File "/venv/lib/python3.7/site-packages/flask_kerberos.py", line 70, in _gssapi_authenticate
    rc, state = kerberos.authGSSServerInit(_SERVICE_NAME)
TypeError: argument 1 must be str, not None
a.b.c.d - - [23/Jun/2021 08:47:51] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
a.b.c.d - - [23/Jun/2021 08:47:51] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
a.b.c.d - - [23/Jun/2021 08:47:51] "GET /?__debugger__=yes&cmd=resource&f=jquery.js HTTP/1.1" 200 -
a.b.c.d - - [23/Jun/2021 08:47:51] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
a.b.c.d - - [23/Jun/2021 08:47:51] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -

And I have seen some related topics (were not helpful):

Advertisement

Answer

I started with brutally integrating the Flask-Kerberos code directly into my ‘example.py’ file and using some print()s:

"""HERE GOES THE CONTENT OF flask_kerberos.py"""

import kerberos
from flask import Response
from flask import _request_ctx_stack as stack
from flask import make_response
from flask import request
from functools import wraps
from socket import gethostname
from os import environ

_SERVICE_NAME = None #_SERVICE_NAME = 'HTTP/l.s.d'

def init_kerberos(app, service='HTTP', hostname=gethostname()):
    '''
    Configure the GSSAPI service name, and validate the presence of the
    appropriate principal in the kerberos keytab.

    :param app: a flask application
    :type app: flask.Flask
    :param service: GSSAPI service name
    :type service: str
    :param hostname: hostname the service runs under
    :type hostname: str
    '''
    global _SERVICE_NAME
    _SERVICE_NAME = "%s@%s" % (service, hostname)
    print(_SERVICE_NAME) #HTTP@Server.l.s.d

    if 'KRB5_KTNAME' not in environ:
        app.logger.warn("Kerberos: set KRB5_KTNAME to your keytab file")
    else:
        try:
            principal = kerberos.getServerPrincipalDetails(service, hostname)
        except kerberos.KrbError as exc:
            app.logger.warn("Kerberos: %s" % exc.message[0])
        else:
            app.logger.info("Kerberos: server is %s" % principal)


def _unauthorized():
    '''
    Indicate that authentication is required
    '''
    return Response('Unauthorized', 401, {'WWW-Authenticate': 'Negotiate'})


def _forbidden():
    '''
    Indicate a complete authentication failure
    '''
    return Response('Forbidden', 403)


def _gssapi_authenticate(token):
    '''
    Performs GSSAPI Negotiate Authentication

    On success also stashes the server response token for mutual authentication
    at the top of request context with the name kerberos_token, along with the
    authenticated user principal with the name kerberos_user.

    @param token: GSSAPI Authentication Token
    @type token: str
    @returns gssapi return code or None on failure
    @rtype: int or None
    '''
    state = None
    ctx = stack.top
    try:
        rc, state = kerberos.authGSSServerInit(_SERVICE_NAME)
        if rc != kerberos.AUTH_GSS_COMPLETE:
            return None
        rc = kerberos.authGSSServerStep(state, token)
        if rc == kerberos.AUTH_GSS_COMPLETE:
            ctx.kerberos_token = kerberos.authGSSServerResponse(state)
            ctx.kerberos_user = kerberos.authGSSServerUserName(state)
            return rc
        elif rc == kerberos.AUTH_GSS_CONTINUE:
            return kerberos.AUTH_GSS_CONTINUE
        else:
            return None
    except kerberos.GSSError:
        return None
    finally:
        if state:
            kerberos.authGSSServerClean(state)


def requires_authentication(function):
    '''
    Require that the wrapped view function only be called by users
    authenticated with Kerberos. The view function will have the authenticated
    users principal passed to it as its first argument.

    :param function: flask view function
    :type function: function
    :returns: decorated function
    :rtype: function
    '''
    @wraps(function)
    def decorated(*args, **kwargs):
        header = request.headers.get("Authorization")
        if header:
            ctx = stack.top
            token = ''.join(header.split()[1:])
            rc = _gssapi_authenticate(token)
            if rc == kerberos.AUTH_GSS_COMPLETE:
                response = function(ctx.kerberos_user, *args, **kwargs)
                response = make_response(response)
                if ctx.kerberos_token is not None:
                    response.headers['WWW-Authenticate'] = ' '.join(['negotiate',
                                                                     ctx.kerberos_token])
                return response
            elif rc != kerberos.AUTH_GSS_CONTINUE:
                return _forbidden()
        return _unauthorized()
    return decorated

"""END OF THE flask_kerberos.py"""

from flask import Flask
from flask import render_template
from config import Config

app = Flask(__name__)
app.config.from_object(Config)


@app.route("/")
@requires_authentication
def index(user):
    return render_template('index.html', user=user)


if __name__ == '__main__':
    init_kerberos(app, hostname='Server.l.s.d')
    app.run()

As was mentioned in this answer:

The problem is exactly as the error message states – you’ve told the kerberos library to get a service principal from the keytab, but the keytab doesn’t contain an entry for that service principal.

So, I decided to check several variables and their values, i.e. _SERVICE_NAME and getServerPrincipalDetails(service, hostname).

Firstly I set the _SERVICE_NAME='L.S.D' and after I got the ‘Forbidden’ response in my Browser. And here is an output in the CMD:

(venv) Server@User:~/.../flask_kerberos_example$ flask run

 * Serving Flask app "example.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 417-811-792
a.b.c.d - - [06/Jul/2021 11:01:06] "GET / HTTP/1.1" 401 -
a.b.c.d - - [06/Jul/2021 11:01:06] "GET / HTTP/1.1" 403 -

I run the the above code via Vim I received this message:

Traceback (most recent call last):
  File "example.py", line 34, in init_kerberos
    principal = kerberos.getServerPrincipalDetails(service, hostname)
kerberos.KrbError: ('Cannot get sequence cursor from keytab', 2)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "example.py", line 140, in <module>
    init_kerberos(app)
  File "example.py", line 36, in init_kerberos
    app.logger.warn("Kerberos: %s" % exc.message[0])
AttributeError: 'KrbError' object has no attribute 'message'

shell returned 1

This issue brought me further to this issue on the GitHub. Where author stated:

Nevermind, regardless of how I interpret the code it works fine with service=”HTTP” and hostname=”my.host.name”.

Therefore, I tried to adjust service and hostname variables of the getServerPrincipalDetails(service, hostname) function. The most convenient way for me to test it was:

import kerberos

service = 'HTTP'
hostname = 'Server.l.s.d'

try:
    principal = kerberos.getServerPrincipalDetails(service, hostname)
except kerberos.KrbError as exc:
    print("Kerberos: %s" % exc.message[0])
else:
    print("Kerberos: server is %s" % principal)

So, I ended up with the following variables and their values

_SERVICE_NAME = None
service = 'HTTP'
hostname = 'Server.l.s.d'

And after I got the response in Browser

Flask Kerberos Example

It worked, I think you are username@L.S.D.

and in CMD correspondingly

HTTP@Server.l.s.d
 * Serving Flask app "example" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
a.b.c.d - - [08/Jul/2021 13:02:18] "GET / HTTP/1.1" 401 -
a.b.c.d - - [08/Jul/2021 13:02:18] "GET / HTTP/1.1" 200 -
a.b.c.d - - [08/Jul/2021 13:02:18] "GET /static/style.css HTTP/1.1" 304 -

Unfortunatelly it still does not work via flask run. It was asked as a new question here: Flask-Kerberos yields different results when running the code via “flask run” and with Vim

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement