parent
89e6c541ab
commit
95d083fca1
@ -0,0 +1,55 @@ |
||||
from base64 import b64decode, b64encode |
||||
import functools |
||||
import socket |
||||
from typing import Union |
||||
|
||||
from flask import request, Response, make_response |
||||
import gssapi |
||||
|
||||
_server_name = None |
||||
|
||||
|
||||
def init_spnego(service_name: str, fqdn: Union[str, None] = None): |
||||
"""Set the server principal which will be used for SPNEGO.""" |
||||
global _server_name |
||||
if fqdn is None: |
||||
fqdn = socket.getfqdn() |
||||
_server_name = gssapi.Name('ceod/' + fqdn) |
||||
|
||||
# make sure that we're actually capable of acquiring credentials |
||||
gssapi.Credentials(usage='accept', name=_server_name) |
||||
|
||||
|
||||
def requires_authentication(f): |
||||
""" |
||||
Requires that all requests to f have a GSSAPI initiator token. |
||||
The initiator principal will be passed to the first argument of f |
||||
in the form user@REALM. |
||||
""" |
||||
@functools.wraps(f) |
||||
def wrapper(*args, **kwargs): |
||||
if 'authorization' not in request.headers: |
||||
return Response('Unauthorized', 401, {'WWW-Authenticate': 'Negotiate'}) |
||||
header = request.headers['authorization'] |
||||
client_token = b64decode(header.split()[1]) |
||||
creds = gssapi.Credentials(usage='accept', name=_server_name) |
||||
ctx = gssapi.SecurityContext(creds=creds, usage='accept') |
||||
server_token = ctx.step(client_token) |
||||
|
||||
# OK so we're going to cheat a bit here by assuming that Kerberos is the |
||||
# mechanism being used (which we know will be true). We know that Kerberos |
||||
# only requires one round-trip for the service handshake, so we don't need |
||||
# to store state between requests. Just to be sure, we assert that this is |
||||
# indeed the case. |
||||
# (This isn't compliant with the GSSAPI spec, but why write more code than |
||||
# necessary?) |
||||
assert ctx.complete, 'only one round trip expected' |
||||
|
||||
resp = make_response(f(str(ctx.initiator_name), *args, **kwargs)) |
||||
# RFC 2744, section 5.1: |
||||
# "If no token need be sent, gss_accept_sec_context will indicate this |
||||
# by setting the length field of the output_token argument to zero." |
||||
if server_token is not None: |
||||
resp.headers['WWW-Authenticate'] = 'Negotiate ' + b64encode(server_token).decode() |
||||
return resp |
||||
return wrapper |
Loading…
Reference in new issue