Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSError: Could not find KfW installation #290

Open
zenoran opened this issue May 10, 2022 · 10 comments
Open

OSError: Could not find KfW installation #290

zenoran opened this issue May 10, 2022 · 10 comments

Comments

@zenoran
Copy link

zenoran commented May 10, 2022

What went wrong?

Loading gssapi on windows throws "OSError: Could not find KfW installation" when there are multiple kinit.exe in the PATH. Specifically, in the case of having the Java JDK being installed it satisfies the "kinit.exe" under that path and ignores the subsequent one pointing to the kerberos installation.

How do we reproduce?

  1. Install Kerberos somewhere other than c:\Program Files\MIT\Kerberos
  2. Install JDK
  3. Add the jdk\bin directory to the path before the Kerberos installation path

Making Kerberos the first directory in the path is obviously a workaround, but ideally the logic for finding the kerberos installation could be enhanced a bit to use "which --all" and validate the correct path.

@zenoran
Copy link
Author

zenoran commented Nov 29, 2022

Any hope for getting this issue fixed?

@jborean93
Copy link
Contributor

The code for the lookup is at https://github.com/pythongssapi/python-gssapi/blob/main/gssapi/_win_config.py, you are more than welcome to try and fix it and submit a PR.

It already tries to check C:\Program Files\MIT\Kerberos\bin first before it falls back to the kinit check. Unfortunately I don't use KfW and the current method works for basic use cases with a workaround in place (placing it first in PATH) is typically good enough.

@lv123123long
Copy link

I have the same problem, it's strange, my kafka client doesn't have any authentication enabled, using kafka-python keeps reporting errors, OSError: Could not find KfW installation,, looked at it is gssapi throwing, I don't understand, why is this, can someone fix it

@lv123123long
Copy link

The code for the lookup is at https://github.com/pythongssapi/python-gssapi/blob/main/gssapi/_win_config.py, you are more than welcome to try and fix it and submit a PR.

It already tries to check C:\Program Files\MIT\Kerberos\bin first before it falls back to the kinit check. Unfortunately I don't use KfW and the current method works for basic use cases with a workaround in place (placing it first in PATH) is typically good enough.

This is very unreasonable. I did not use Kerberos related protocols, but just imported gssapi, and an error was thrown during the initialization process.Hope someone can fix it

@rcludwick
Copy link

rcludwick commented Jan 17, 2024

If kinit is not found so what? Let it fail on gssapi.Credentials(). Why raise the OS Error on line 85 of _win_config.py?

error_not_found()

Otherwise your users have to go through hoops like this:

try:
    import gssapi
except:
    """now what do we do for unit tests?  Can't mock anything in gssapi...."""

@jborean93
Copy link
Contributor

Ultimately the library needs to be able to load gssapi64.dll (gssapi32.dll for 32-bit processes) that is provided by KfW. If that's not available then it's a critical error and nothing else will work. The code referenced does the following right now with each step being a fallback

  • Checks to see if the dll can be loaded through normal means,
  • Checks to see if a known install location is present %ProgramFiles%\MIT\Kerberos\bin, if so add that to the dll load path and load the dll that way
  • Checks to see if kinit.exe is in the PATH, if so add that dir to the dll load path

Each step attempts to find gssapi64.dll and load that in process required by this library. If it fails then it moves onto the next attempt. At this point we have dimishing returns, your process needs to have the dll present, if it's not then we can't do anything. The logic could technically be expanded but I don't use KfW and Windows provides many ways to provide you a method for finding dlls in the path https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#standard-search-order-for-unpackaged-apps, the easiest one being make sure the PATH env var contains the dir that has the dll require.

Why raise the OS Error on line 85 of _win_config.py?

This is a critical error, nothing will work. Why would you be importing gssapi if you don't expect to use anything with it.

@rcludwick
Copy link

rcludwick commented Jan 23, 2024

Ultimately the library needs to be able to load gssapi64.dll (gssapi32.dll for 32-bit processes) that is provided by KfW. If that's not available then it's a critical error and nothing else will work.

... Including the program that imports it and does not in fact make a call into kerberos.

If I import requests and don't have a working network connection, requests doesn't crash upon import. It only errors on requests.get().

Likewise if I import gssapi, I would expect gssapi should error on gssapi.Credentials() -- that is when it's trying to actually interface with Kerberos.

Many things use late binding in python via the following pattern.

class LazyLoadingClass:
    _instance = None

   def __init__(self, *args, **kwargs):
     if self._instance is None:
         self._instance = construct_instance(*args, **kwargs)

This is similar to the code I wrote:

class LazyGssBinding:
    """gss has to early bind.. for reasons"""
    _gssapi = None

    @classmethod
    def import_lazy(cls):
        if cls._gssapi is None:
            try:
                import gssapi
            except:
                raise SomeException("gssapi error")
            cls._gssapi = gssapi
        return cls._gssapi

The real down side with using this here is that I can't mock out your classes, or subclass them, or whatever, because the import must work in order for the python mock to introspect your classes. At least now I can mock out LazyGssBinding.

Here's a few reasons you might not have kfw installed:

  1. It's one options in a large range of optional authenticators your program has and today you're using LDAP say.
  2. You're running unit tests on restricted runners that will not have kfw.
  3. You're writing unit tests on restricted laptops that will not have kfw, and you want to use mock to introspect and make mocks of gssapi classes.
  4. You want to subclass gssapi.Credentials to do something else if kerberos isn't on the system.

@jborean93
Copy link
Contributor

jborean93 commented Jan 24, 2024

Unfortunately what you want just isn't possible with how this module is structured. Things like the gssapi.Credential or gssapi.SecurityContext imports the base structures from gssapi.raw.* which are derived from a compiled Python binary. These binaries have references to symbols that are provided by the gssapi*.dll which is why during import the code tries to be helpful and checks a few places where those symbols could be. For example using your gssapi.Credentials() example we can see that it is based on the rcreds.Creds object

class Credentials(rcreds.Creds):

This rcreds.Creds base type is defined in the Cython pyx and pyd files under gssapi.raw.creds

cdef class Creds:
# defined in pxd
# cdef gss_cred_id_t raw_creds
def __cinit__(self, Creds cpy=None):
if cpy is not None:
self.raw_creds = cpy.raw_creds
cpy.raw_creds = GSS_C_NO_CREDENTIAL
else:
self.raw_creds = GSS_C_NO_CREDENTIAL
def __dealloc__(self):
# essentially just releaseCred(self), but it is unsafe to call
# methods
cdef OM_uint32 maj_stat, min_stat
if self.raw_creds is not GSS_C_NO_CREDENTIAL:
maj_stat = gss_release_cred(&min_stat, &self.raw_creds)
if maj_stat != GSS_S_COMPLETE:
raise GSSError(maj_stat, min_stat)
self.raw_creds = NULL

When compiled we can see that this .pyd has a dependency on gssapi64.dll

$ dumpbin C:\temp\gssapi-venv\Lib\site-packages\gssapi\raw\creds.cp312-win_amd64.pyd /IMPORTS
Microsoft (R) COFF/PE Dumper Version 14.38.33133.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\temp\gssapi-venv\Lib\site-packages\gssapi\raw\creds.cp312-win_amd64.pyd

File Type: DLL

  Section contains the following imports:

    gssapi64.dll
             18000E108 Import Address Table
             180010F60 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                             Ordinal    54
                             Ordinal    29
                             Ordinal    30
                             Ordinal    10
                             Ordinal    57
                             Ordinal    11

...

The Ordinal are references inside that dll that is required, these will be things like the C methods being called in this file. As I don't actually have MIT KfW installed on that test host I cannot see what those ordinals match up to but it shows the compiled .pyd requires them to load the dll.

This also applies to the other types that are being exposed at the base level, fundamentally for Python to import them the gssapi*.dll must be present and found by the Windows dll loader logic.

Even if you were to comment out the checks in _win_config.py so they don't run you will get this error when you try to import gssapi without the dll's being present

(gssapi-venv) PS C:\Users\vagrant-domain> python -c "import gssapi"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\temp\gssapi-venv\Lib\site-packages\gssapi\__init__.py", line 31, in <module>
    from gssapi.raw.types import NameType, RequirementFlag, AddressType  # noqa
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\temp\gssapi-venv\Lib\site-packages\gssapi\raw\__init__.py", line 50, in <module>
    from gssapi.raw.creds import *  # noqa
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ImportError: DLL load failed while importing creds: The specified module could not be found.

Ultimately what you want can't really be done, to import this library you must have the dependencies met, it is critical for the operation for this library and it cannot be lazily loaded. The lazy loading must be in the callers code to optionally import gssapi and handle accordingly.

I currently use this pattern in a library that might be used where gssapi itself is not present. I have a try/except guard during the import which sets a bool value

https://github.com/jborean93/pyspnego/blob/cba319d9795148702ce6d5575f13508a12fab4ec/src/spnego/_gss.py#L44-L67

Then when someone tries to instanciate one of the classes that use gssapi it checks to see whether it was imported or not and fails

https://github.com/jborean93/pyspnego/blob/cba319d9795148702ce6d5575f13508a12fab4ec/src/spnego/_gss.py#L303-L318

@rcludwick
Copy link

rcludwick commented Jan 25, 2024 via email

@jborean93
Copy link
Contributor

That’s up to the caller in my opinion here. They can proxy it as the C library is a mandatory dependency of this package. It’ll be the same problem as any other Python library where the dependency isn’t met, you’ll find they will most likely also error if the dependency is missing. The trouble with this dependency is that it is a C one and we cannot host it on PyPI. Ultimately I’m not going to do the work, things work today they just might not work the way you wish them to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants