python-gssapi
python-gssapi copied to clipboard
OSError: Could not find KfW installation
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?
- Install Kerberos somewhere other than c:\Program Files\MIT\Kerberos
- Install JDK
- 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.
Any hope for getting this issue fixed?
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.
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
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 thekinit
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
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...."""
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 thePATH
, 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.
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:
- It's one options in a large range of optional authenticators your program has and today you're using LDAP say.
- You're running unit tests on restricted runners that will not have kfw.
- 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.
- You want to subclass gssapi.Credentials to do something else if kerberos isn't on the system.
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
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: @.***>
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.