Merge branch 'v1' into db-api

This commit is contained in:
Max Erenberg 2021-08-29 16:42:08 +00:00
commit 01b4412b42
7 changed files with 29 additions and 21 deletions

View File

@ -30,10 +30,10 @@ class HTTPClient:
'opportunistic_auth': True, 'opportunistic_auth': True,
'target_name': gssapi.Name('ceod/' + host), 'target_name': gssapi.Name('ceod/' + host),
} }
if flask.has_request_context() and 'client_creds' in flask.g: if flask.has_request_context() and 'client_token' in flask.g:
# This is reached when we are the server and the client has forwarded # This is reached when we are the server and the client has forwarded
# their credentials to us. # their credentials to us.
spnego_kwargs['creds'] = flask.g.client_creds spnego_kwargs['creds'] = gssapi.Credentials(token=flask.g.client_token)
if delegate: if delegate:
# This is reached when we are the client and we want to forward our # This is reached when we are the client and we want to forward our
# credentials to the server. # credentials to the server.

View File

@ -52,7 +52,11 @@ def requires_authentication(f):
# Store the delegated credentials, if they were given # Store the delegated credentials, if they were given
if ctx.actual_flags & RequirementFlag.delegate_to_peer: if ctx.actual_flags & RequirementFlag.delegate_to_peer:
g.client_creds = ctx.delegated_creds # For some reason, shit gets screwed up when you try to use a
# gssapi.Credentials object which was created in another function.
# So we're going to export the token instead (which is a bytes
# object) and pass it to Credentials() whenever we need it.
g.client_token = ctx.delegated_creds.export()
# TODO: don't pass client_princ to f anymore since it's stored in flask.g # TODO: don't pass client_princ to f anymore since it's stored in flask.g
resp = make_response(f(client_princ, *args, **kwargs)) resp = make_response(f(client_princ, *args, **kwargs))

View File

@ -4,6 +4,7 @@ import pwd
from typing import Union, Dict, List from typing import Union, Dict, List
from flask import g from flask import g
import gssapi
import ldap3 import ldap3
from zope import component from zope import component
from zope.interface import implementer from zope.interface import implementer
@ -34,11 +35,12 @@ class LDAPService:
if 'ldap_conn' in g: if 'ldap_conn' in g:
return g.ldap_conn return g.ldap_conn
kwargs = {'auto_bind': True, 'raise_exceptions': True} kwargs = {'auto_bind': True, 'raise_exceptions': True}
if 'client_creds' in g: if 'client_token' in g:
kwargs['authentication'] = ldap3.SASL kwargs['authentication'] = ldap3.SASL
kwargs['sasl_mechanism'] = ldap3.KERBEROS kwargs['sasl_mechanism'] = ldap3.KERBEROS
creds = gssapi.Credentials(token=g.client_token)
# see https://github.com/cannatag/ldap3/blob/master/ldap3/protocol/sasl/kerberos.py # see https://github.com/cannatag/ldap3/blob/master/ldap3/protocol/sasl/kerberos.py
kwargs['sasl_credentials'] = (None, None, g.client_creds) kwargs['sasl_credentials'] = (None, None, creds)
conn = ldap3.Connection(self.ldap_server_url, **kwargs) conn = ldap3.Connection(self.ldap_server_url, **kwargs)
# cache the connection for a single request # cache the connection for a single request
g.ldap_conn = conn g.ldap_conn = conn

View File

@ -12,13 +12,14 @@ import time
from unittest.mock import patch, Mock from unittest.mock import patch, Mock
import flask import flask
import gssapi
import ldap3 import ldap3
import pytest import pytest
import requests import requests
import socket import socket
from zope import component from zope import component
from .utils import gssapi_creds_ctx, ccache_cleanup # noqa: F401 from .utils import gssapi_token_ctx, ccache_cleanup # noqa: F401
from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, \ from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, \
IFileService, IMailmanService, IHTTPClient, IUWLDAPService, IMailService, \ IFileService, IMailmanService, IHTTPClient, IUWLDAPService, IMailService, \
IDatabaseService IDatabaseService
@ -102,13 +103,13 @@ def g_admin_ctx(app):
""" """
@contextlib.contextmanager @contextlib.contextmanager
def wrapper(): def wrapper():
with gssapi_creds_ctx('ceod/admin') as creds, app.app_context(): with gssapi_token_ctx('ceod/admin') as token, app.app_context():
try: try:
flask.g.auth_user = 'ceod/admin' flask.g.auth_user = 'ceod/admin'
flask.g.client_creds = creds flask.g.client_token = token
yield yield
finally: finally:
flask.g.pop('client_creds') flask.g.pop('client_token')
flask.g.pop('auth_user') flask.g.pop('auth_user')
return wrapper return wrapper
@ -120,13 +121,13 @@ def g_syscom(app):
Use this fixture if you need syscom credentials for an HTTP request Use this fixture if you need syscom credentials for an HTTP request
to a different process. to a different process.
""" """
with gssapi_creds_ctx('ctdalek') as creds, app.app_context(): with gssapi_token_ctx('ctdalek') as token, app.app_context():
try: try:
flask.g.sasl_user = 'ctdalek' flask.g.sasl_user = 'ctdalek'
flask.g.client_creds = creds flask.g.client_token = token
yield yield
finally: finally:
flask.g.pop('client_creds') flask.g.pop('client_token')
flask.g.pop('sasl_user') flask.g.pop('sasl_user')
@ -138,7 +139,8 @@ def ldap_conn(cfg) -> ldap3.Connection:
server_url = cfg.get('ldap_server_url') server_url = cfg.get('ldap_server_url')
# sanity check # sanity check
assert server_url == cfg.get('uwldap_server_url') assert server_url == cfg.get('uwldap_server_url')
with gssapi_creds_ctx('ceod/admin') as creds: with gssapi_token_ctx('ceod/admin') as token:
creds = gssapi.Credentials(token=token)
conn = ldap3.Connection( conn = ldap3.Connection(
server_url, auto_bind=True, raise_exceptions=True, server_url, auto_bind=True, raise_exceptions=True,
authentication=ldap3.SASL, sasl_mechanism=ldap3.KERBEROS, authentication=ldap3.SASL, sasl_mechanism=ldap3.KERBEROS,
@ -392,7 +394,7 @@ def app_process(cfg, app, http_client):
try: try:
# Currently the HTTPClient uses SPNEGO for all requests, # Currently the HTTPClient uses SPNEGO for all requests,
# even GETs # even GETs
with gssapi_creds_ctx('ctdalek'): with gssapi_token_ctx('ctdalek'):
for i in range(5): for i in range(5):
try: try:
http_client.get(hostname, '/ping', delegate=False) http_client.get(hostname, '/ping', delegate=False)

View File

@ -2,7 +2,7 @@ import os
import pytest import pytest
from .utils import gssapi_creds_ctx from .utils import gssapi_token_ctx
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
@ -14,5 +14,5 @@ def cli_setup(app_process):
# messy because they would be sharing the same environment variables, # messy because they would be sharing the same environment variables,
# Kerberos cache, and registered utilities (via zope). So we're just # Kerberos cache, and registered utilities (via zope). So we're just
# going to start the app in a child process intead. # going to start the app in a child process intead.
with gssapi_creds_ctx('ctdalek'): with gssapi_token_ctx('ctdalek'):
yield yield

View File

@ -8,7 +8,7 @@ import pytest
from requests import Request from requests import Request
from requests_gssapi import HTTPSPNEGOAuth from requests_gssapi import HTTPSPNEGOAuth
from .utils import gssapi_creds_ctx from .utils import gssapi_token_ctx
__all__ = ['client'] __all__ = ['client']
@ -30,11 +30,11 @@ class CeodTestClient:
def get_auth(self, principal: str, delegate: bool): def get_auth(self, principal: str, delegate: bool):
"""Acquire a HTTPSPNEGOAuth instance for the principal.""" """Acquire a HTTPSPNEGOAuth instance for the principal."""
with gssapi_creds_ctx(principal) as creds: with gssapi_token_ctx(principal) as token:
return HTTPSPNEGOAuth( return HTTPSPNEGOAuth(
opportunistic_auth=True, opportunistic_auth=True,
target_name=self.target_name, target_name=self.target_name,
creds=creds, creds=gssapi.Credentials(token=token),
delegate=delegate, delegate=delegate,
) )

View File

@ -13,7 +13,7 @@ _cache = {}
@contextlib.contextmanager @contextlib.contextmanager
def gssapi_creds_ctx(principal: str): def gssapi_token_ctx(principal: str):
""" """
Temporarily set KRB5CCNAME to a ccache storing credentials Temporarily set KRB5CCNAME to a ccache storing credentials
for the specified user, and yield the GSSAPI credentials. for the specified user, and yield the GSSAPI credentials.
@ -35,7 +35,7 @@ def gssapi_creds_ctx(principal: str):
else: else:
creds, f = _cache[principal] creds, f = _cache[principal]
os.environ['KRB5CCNAME'] = 'FILE:' + f.name os.environ['KRB5CCNAME'] = 'FILE:' + f.name
yield creds yield creds.export()
finally: finally:
os.environ['KRB5CCNAME'] = old_krb5ccname os.environ['KRB5CCNAME'] = old_krb5ccname