From cda1e052f190c37862f2da48da3153b996de55aa Mon Sep 17 00:00:00 2001 From: "Richard E. Silverman" Date: Sun, 30 Oct 2022 21:44:51 -0400 Subject: [PATCH 1/3] Have krb5.init_context() raise an exception on error. init_context() currently just returns a null pointer if the C routine fails, which is unlike the norm in this module and I assume is an oversight. Typically this will fail if there's a syntax error in the libkrb5 configuration (/etc/krb5.conf, or whatever is read). A program I wrote segfaulted in this situation, because I then passed the null context to other routines. With this change, we get instead: In [2]: ctx = krb5.init_context() --------------------------------------------------------------------------- Krb5Error Traceback (most recent call last) ... Krb5Error: Included profile file could not be read -1429577697 --- src/krb5/_context.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/krb5/_context.pyx b/src/krb5/_context.pyx index e0a89c7..b2fc6b1 100644 --- a/src/krb5/_context.pyx +++ b/src/krb5/_context.pyx @@ -60,8 +60,12 @@ cdef class Context: def init_context() -> Context: + cdef krb5_error_code = 0 context = Context() - krb5_init_context(&context.raw) + + err = krb5_init_context(&context.raw) + if err: + raise Krb5Error(context, err) return context From d141410a875e6ad1111893d786a1892d80180ecf Mon Sep 17 00:00:00 2001 From: "Richard E. Silverman" Date: Sun, 30 Oct 2022 21:44:23 -0400 Subject: [PATCH 2/3] add krb5_aname_to_localname() This is part of the MIT Kerberos "localauth" interface: https://web.mit.edu/kerberos/krb5-devel/doc/plugindev/localauth.html ... so I used that as the "extension" grouping name. There is also a krb5_kuserok() in this interface, that might be added. --- setup.py | 1 + src/krb5/__init__.py | 2 ++ src/krb5/_localauth.pyi | 23 +++++++++++++++ src/krb5/_localauth.pyx | 47 ++++++++++++++++++++++++++++++ tests/test_localauth.py | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 src/krb5/_localauth.pyi create mode 100644 src/krb5/_localauth.pyx create mode 100644 tests/test_localauth.py diff --git a/setup.py b/setup.py index 1d27cf9..b619f0c 100755 --- a/setup.py +++ b/setup.py @@ -235,6 +235,7 @@ def get_krb5_lib_path( ("principal_heimdal", "krb5_principal_get_realm"), "string", ("string_mit", "krb5_enctype_to_name"), + "localauth", ]: name = e canary = None diff --git a/src/krb5/__init__.py b/src/krb5/__init__.py index a3f87f1..a976a18 100644 --- a/src/krb5/__init__.py +++ b/src/krb5/__init__.py @@ -57,6 +57,7 @@ kt_remove_entry, kt_resolve, ) +from krb5._localauth import aname_to_localname from krb5._principal import ( Principal, PrincipalParseFlags, @@ -81,6 +82,7 @@ "Principal", "PrincipalParseFlags", "PrincipalUnparseFlags", + "aname_to_localname", "cc_default", "cc_default_name", "cc_destroy", diff --git a/src/krb5/_localauth.pyi b/src/krb5/_localauth.pyi new file mode 100644 index 0000000..1f97cee --- /dev/null +++ b/src/krb5/_localauth.pyi @@ -0,0 +1,23 @@ +# Copyright: (c) 2022 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from krb5._context import Context +from krb5._principal import Principal + +def aname_to_localname(context: Context, principal: Principal) -> str: + """Translate a Kerberos principal to an authorization ID ("local name"). + + Using configured rules, translate a Kerberos principal name to a + string suitable for use in authorization rules; often, this means + mapping it to a username for the host OS. See krb5.conf(5) + "localauth interface" for details. + + Args: + context: krb5 context + principal: principal name to translate + + Returns: + str: translation + None: principal has no translation + (krb5_aname_to_localname() returns KRB5_LNAME_NOTRANS) + """ diff --git a/src/krb5/_localauth.pyx b/src/krb5/_localauth.pyx new file mode 100644 index 0000000..c7ce14c --- /dev/null +++ b/src/krb5/_localauth.pyx @@ -0,0 +1,47 @@ +# Copyright: (c) 2022 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from libc.stdlib cimport free, malloc + +from krb5._exceptions import Krb5Error + +from krb5._context cimport Context +from krb5._krb5_types cimport * +from krb5._principal cimport Principal + + +cdef extern from "python_krb5.h": + + krb5_error_code krb5_aname_to_localname( + krb5_context context, + krb5_const_principal aname, + int lnsize, + char *lname_out + ) nogil + + krb5_error_code KRB5_LNAME_NOTRANS + +def aname_to_localname( + context: Context, + principal: Principal +) -> str: + + cdef krb5_error_code err = 0 + limit = 256 + + cdef char *localname = malloc(limit + 1) + if not localname: + raise MemoryError() + + err = krb5_aname_to_localname(context.raw, principal.raw, limit, localname) + # The definition of KRB5_LNAME_NOTRANS in the Heimdal header file + # included in the pykrb5 source appears to be out of date; this is + # the value that actually gets returned in the test -- so I'm just + # adding it here for now. + if err in [KRB5_LNAME_NOTRANS, -1765328227]: + return None + elif err != 0: + free(localname) + raise Krb5Error(context, err) + else: + return localname.decode("utf-8") diff --git a/tests/test_localauth.py b/tests/test_localauth.py new file mode 100644 index 0000000..0513832 --- /dev/null +++ b/tests/test_localauth.py @@ -0,0 +1,63 @@ +# Copyright: (c) 2021 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +import os +import tempfile +from typing import Optional + +import k5test +import pytest + +import krb5 + + +def test_aname_to_localname(realm: k5test.K5Realm) -> None: + + # require: translation(p) == t + def test(ctx: krb5.Context, p: str, t: Optional[str]) -> None: + assert t == krb5.aname_to_localname(ctx, krb5.parse_name_flags(ctx, p.encode())) + + # To test, apply our own krb5.conf with a single translation + # rule. The file is read by krb5_init_context(), so set the + # environment variable before calling that. This overrides the + # default rule, so only matching principals will have a + # translation. Require a 2-instance name in the realm TEST. + # + # ... that's for MIT. Heimdal doesn't appear to have RULE + # translations built in, so use auth_to_local_names instead. + # + # Since environment variables are process-wide, this could mess up + # other tests in a multi-threaded test framework -- but that + # probably won't be an issue here. + + with tempfile.NamedTemporaryFile() as config: + saved_conf = os.environ.get("KRB5_CONFIG") + try: + config.write( + b""" +[libdefaults] +default_realm = TEST + +[realms] +TEST = { + auth_to_local = RULE:[2:$1:$2@$0](^.*@TEST$)s/@TEST$// + auth_to_local_names = { + heimdal = mapped + } +} + """ + ) + config.flush() + os.environ["KRB5_CONFIG"] = config.name + ctx = krb5.init_context() + if realm.provider == "mit": + test(ctx, "foo/bar@TEST", "foo:bar") + test(ctx, "foo@TEST", None) + test(ctx, "foo/bar@WRONG", None) + if realm.provider == "heimdal": + test(ctx, "heimdal", "mapped") + finally: + if saved_conf: + os.environ["KRB5_CONFIG"] = saved_conf + else: + del os.environ["KRB5_CONFIG"] From da355d77353fe8ba096b2df7378bb831c13585a2 Mon Sep 17 00:00:00 2001 From: "Richard E. Silverman" Date: Wed, 16 Nov 2022 01:02:50 -0500 Subject: [PATCH 3/3] Revert "Have krb5.init_context() raise an exception on error." I'm moving this to its own PR; it's unrelated to the krb5_aname_to_localname() addition. This reverts commit cda1e052f190c37862f2da48da3153b996de55aa. --- src/krb5/_context.pyx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/krb5/_context.pyx b/src/krb5/_context.pyx index b2fc6b1..e0a89c7 100644 --- a/src/krb5/_context.pyx +++ b/src/krb5/_context.pyx @@ -60,12 +60,8 @@ cdef class Context: def init_context() -> Context: - cdef krb5_error_code = 0 context = Context() - - err = krb5_init_context(&context.raw) - if err: - raise Krb5Error(context, err) + krb5_init_context(&context.raw) return context