diff --git a/ceo_common/model/HTTPClient.py b/ceo_common/model/HTTPClient.py index d4cb8cf..d63b85a 100644 --- a/ceo_common/model/HTTPClient.py +++ b/ceo_common/model/HTTPClient.py @@ -30,10 +30,10 @@ class HTTPClient: 'opportunistic_auth': True, '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 # their credentials to us. - spnego_kwargs['creds'] = flask.g.client_creds + spnego_kwargs['creds'] = gssapi.Credentials(token=flask.g.client_token) if delegate: # This is reached when we are the client and we want to forward our # credentials to the server. diff --git a/ceod/api/spnego.py b/ceod/api/spnego.py index 16e0c1c..007dac1 100644 --- a/ceod/api/spnego.py +++ b/ceod/api/spnego.py @@ -52,7 +52,11 @@ def requires_authentication(f): # Store the delegated credentials, if they were given 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 resp = make_response(f(client_princ, *args, **kwargs)) diff --git a/ceod/model/LDAPService.py b/ceod/model/LDAPService.py index 67c961e..800fbcf 100644 --- a/ceod/model/LDAPService.py +++ b/ceod/model/LDAPService.py @@ -4,6 +4,7 @@ import pwd from typing import Union, Dict, List from flask import g +import gssapi import ldap3 from zope import component from zope.interface import implementer @@ -34,11 +35,12 @@ class LDAPService: if 'ldap_conn' in g: return g.ldap_conn kwargs = {'auto_bind': True, 'raise_exceptions': True} - if 'client_creds' in g: + if 'client_token' in g: kwargs['authentication'] = ldap3.SASL 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 - kwargs['sasl_credentials'] = (None, None, g.client_creds) + kwargs['sasl_credentials'] = (None, None, creds) conn = ldap3.Connection(self.ldap_server_url, **kwargs) # cache the connection for a single request g.ldap_conn = conn diff --git a/tests/conftest.py b/tests/conftest.py index 4fefa46..e6c51d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,13 +11,14 @@ import time from unittest.mock import patch, Mock import flask +import gssapi import ldap3 import pytest import requests import socket 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, \ IFileService, IMailmanService, IHTTPClient, IUWLDAPService, IMailService from ceo_common.model import Config, HTTPClient @@ -99,13 +100,13 @@ def g_admin_ctx(app): """ @contextlib.contextmanager 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: flask.g.auth_user = 'ceod/admin' - flask.g.client_creds = creds + flask.g.client_token = token yield finally: - flask.g.pop('client_creds') + flask.g.pop('client_token') flask.g.pop('auth_user') return wrapper @@ -117,13 +118,13 @@ def g_syscom(app): Use this fixture if you need syscom credentials for an HTTP request 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: flask.g.sasl_user = 'ctdalek' - flask.g.client_creds = creds + flask.g.client_token = token yield finally: - flask.g.pop('client_creds') + flask.g.pop('client_token') flask.g.pop('sasl_user') @@ -135,7 +136,8 @@ def ldap_conn(cfg) -> ldap3.Connection: server_url = cfg.get('ldap_server_url') # sanity check 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( server_url, auto_bind=True, raise_exceptions=True, authentication=ldap3.SASL, sasl_mechanism=ldap3.KERBEROS, @@ -369,7 +371,7 @@ def app_process(cfg, app, http_client): try: # Currently the HTTPClient uses SPNEGO for all requests, # even GETs - with gssapi_creds_ctx('ctdalek'): + with gssapi_token_ctx('ctdalek'): for i in range(5): try: http_client.get(hostname, '/ping', delegate=False) diff --git a/tests/conftest_ceo.py b/tests/conftest_ceo.py index 07a7aca..5eb712b 100644 --- a/tests/conftest_ceo.py +++ b/tests/conftest_ceo.py @@ -2,7 +2,7 @@ import os import pytest -from .utils import gssapi_creds_ctx +from .utils import gssapi_token_ctx @pytest.fixture(scope='module') @@ -14,5 +14,5 @@ def cli_setup(app_process): # messy because they would be sharing the same environment variables, # Kerberos cache, and registered utilities (via zope). So we're just # going to start the app in a child process intead. - with gssapi_creds_ctx('ctdalek'): + with gssapi_token_ctx('ctdalek'): yield diff --git a/tests/conftest_ceod_api.py b/tests/conftest_ceod_api.py index 6a53ed1..c2941ab 100644 --- a/tests/conftest_ceod_api.py +++ b/tests/conftest_ceod_api.py @@ -8,7 +8,7 @@ import pytest from requests import Request from requests_gssapi import HTTPSPNEGOAuth -from .utils import gssapi_creds_ctx +from .utils import gssapi_token_ctx __all__ = ['client'] @@ -30,11 +30,11 @@ class CeodTestClient: def get_auth(self, principal: str, delegate: bool): """Acquire a HTTPSPNEGOAuth instance for the principal.""" - with gssapi_creds_ctx(principal) as creds: + with gssapi_token_ctx(principal) as token: return HTTPSPNEGOAuth( opportunistic_auth=True, target_name=self.target_name, - creds=creds, + creds=gssapi.Credentials(token=token), delegate=delegate, ) diff --git a/tests/utils.py b/tests/utils.py index d7a12e1..1feaec1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,7 +13,7 @@ _cache = {} @contextlib.contextmanager -def gssapi_creds_ctx(principal: str): +def gssapi_token_ctx(principal: str): """ Temporarily set KRB5CCNAME to a ccache storing credentials for the specified user, and yield the GSSAPI credentials. @@ -35,7 +35,7 @@ def gssapi_creds_ctx(principal: str): else: creds, f = _cache[principal] os.environ['KRB5CCNAME'] = 'FILE:' + f.name - yield creds + yield creds.export() finally: os.environ['KRB5CCNAME'] = old_krb5ccname