python-gssapi icon indicating copy to clipboard operation
python-gssapi copied to clipboard

OSError: Could not find KfW installation

Open zenoran opened this issue 2 years ago • 10 comments

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 avatar May 10 '22 15:05 zenoran

Any hope for getting this issue fixed?

zenoran avatar Nov 29 '22 15:11 zenoran

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.

jborean93 avatar Nov 29 '22 18:11 jborean93

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 avatar Sep 21 '23 06:09 lv123123long

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

lv123123long avatar Oct 09 '23 02:10 lv123123long

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?

https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/_win_config.py#L85

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...."""

rcludwick avatar Jan 17 '24 00:01 rcludwick

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.

jborean93 avatar Jan 17 '24 00:01 jborean93

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.

rcludwick avatar Jan 23 '24 20:01 rcludwick

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

https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/creds.py#L17

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

https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/raw/creds.pyx#L58-L77

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

jborean93 avatar Jan 24 '24 23:01 jborean93

You could do it with a series of proxies. No?

On Wed, Jan 24, 2024, 4:02 PM Jordan Borean @.***> wrote:

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

https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/creds.py#L17

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

https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/raw/creds.pyx#L58-L77

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 https://github.com/pythongssapi/python-gssapi/blob/5c316ec3c8f99d3df6d689fadff4a391658e026a/gssapi/raw/creds.pyx#L17-L55. As I don't actually have MIT KfW installed on that test how 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.

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.

— Reply to this email directly, view it on GitHub https://github.com/pythongssapi/python-gssapi/issues/290#issuecomment-1909061045, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAL67IZXSIV3B4LCN3G3OJTYQGHHNAVCNFSM5VSA5DLKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOJQHEYDMMJQGQ2Q . You are receiving this because you commented.Message ID: @.***>

rcludwick avatar Jan 25 '24 17:01 rcludwick

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.

jborean93 avatar Jan 25 '24 20:01 jborean93