Проверка пароля Python LDAP3 от клиента Windows без пароля - PullRequest
0 голосов
/ 11 сентября 2018

Я использую отличный пакет ldap3 и пытаюсь подключиться к активному серверу каталогов, но не требую предоставления фактических учетных данных в виде простого текста.

Поддерживаются следующие механизмы SASL.['GSSAPI', 'GSS-SPNEGO', 'EXTERNAL', 'DIGEST-MD5']

Я пытался установить пакет GSSAPI, но он не работает на моей машине с Windows.Ошибка на pip install gssapi была: subprocess.CalledProcessError: Command 'krb5-config --libs gssapi' returned non-zero exit status 1.

Кто-нибудь может привести простой пример для этого?Я верю, что GSS-SPNEGO может быть решением, но я не нашел в интернете ни одного понятного примера.

Ответы [ 2 ]

0 голосов
/ 11 июля 2019

Используя первоначальный ответ, и для того, чтобы избежать мартышек, можно использовать следующий код, основанный на предоставленном файле там и модуле ldap3\core\connection.py.

ldap3kerberos.py

"""Replaces the use of python-gssapi with kerberos in ldap3.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import base64
import socket

import ldap3
from ldap3.core.exceptions import LDAPCommunicationError
from ldap3.protocol.sasl.sasl import send_sasl_negotiation
from ldap3.protocol.sasl.sasl import abort_sasl_negotiation

from ldap3.protocol.sasl.external import sasl_external
from ldap3.protocol.sasl.digestMd5 import sasl_digest_md5
from ldap3.protocol.sasl.plain import sasl_plain
from ldap3.utils.log import log, log_enabled, BASIC
from ldap3 import EXTERNAL, DIGEST_MD5, GSSAPI


import winkerberos as kerberos

NO_SECURITY_LAYER = 1
INTEGRITY_PROTECTION = 2
CONFIDENTIALITY_PROTECTION = 4


class Connection(ldap3.Connection):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_sasl_bind(self,
                     controls):
        if log_enabled(BASIC):
            log(BASIC, 'start SASL BIND operation via <%s>', self)
        self.last_error = None
        with self.connection_lock:
            result = None

            if not self.sasl_in_progress:
                self.sasl_in_progress = True
                try:
                    if self.sasl_mechanism == EXTERNAL:
                        result = sasl_external(self, controls)
                    elif self.sasl_mechanism == DIGEST_MD5:
                        result = sasl_digest_md5(self, controls)
                    elif self.sasl_mechanism == GSSAPI:
                        result = sasl_gssapi(self, controls)
                    elif self.sasl_mechanism == 'PLAIN':
                        result = sasl_plain(self, controls)
                finally:
                    self.sasl_in_progress = False

            if log_enabled(BASIC):
                log(BASIC, 'done SASL BIND operation, result <%s>', result)

            return result


def sasl_gssapi(connection, controls):
    """
    Performs a bind using the Kerberos v5 ("GSSAPI") SASL mechanism
    from RFC 4752. Does not support any security layers, only authentication!
    sasl_credentials can be empty or a tuple with one or two elements.
    The first element determines which service principal to request a ticket
    for and can be one of the following:
    - None or False, to use the hostname from the Server object
    - True to perform a reverse DNS lookup to retrieve the canonical hostname
      for the hosts IP address
    - A string containing the hostname
    The optional second element is what authorization ID to request.
    - If omitted or None, the authentication ID is used as the authorization ID
    - If a string, the authorization ID to use. Should start with "dn:" or
      "user:".
    """
    # pylint: disable=too-many-branches
    target_name = None
    authz_id = b''
    if connection.sasl_credentials:
        if (len(connection.sasl_credentials) >= 1 and
                connection.sasl_credentials[0]):
            if connection.sasl_credentials[0] is True:
                hostname = \
                    socket.gethostbyaddr(connection.socket.getpeername()[0])[0]
                target_name = 'ldap@' + hostname

            else:
                target_name = 'ldap@' + connection.sasl_credentials[0]
        if (len(connection.sasl_credentials) >= 2 and
                connection.sasl_credentials[1]):
            authz_id = connection.sasl_credentials[1].encode("utf-8")
    if target_name is None:
        target_name = 'ldap@' + connection.server.host

    gssflags = (
        kerberos.GSS_C_MUTUAL_FLAG |
        kerberos.GSS_C_SEQUENCE_FLAG |
        kerberos.GSS_C_INTEG_FLAG |
        kerberos.GSS_C_CONF_FLAG
    )

    _, ctx = kerberos.authGSSClientInit(target_name, gssflags=gssflags)

    in_token = b''
    try:
        while True:
            status = kerberos.authGSSClientStep(
                ctx,
                base64.b64encode(in_token).decode('ascii')
            )
            out_token = kerberos.authGSSClientResponse(ctx) or ''
            result = send_sasl_negotiation(
                connection,
                controls,
                base64.b64decode(out_token)
            )
            in_token = result['saslCreds'] or b''
            if status == kerberos.AUTH_GSS_COMPLETE:
                break

        kerberos.authGSSClientUnwrap(
            ctx,
            base64.b64encode(in_token).decode('ascii')
        )
        unwrapped_token = base64.b64decode(
            kerberos.authGSSClientResponse(ctx) or ''
        )

        if len(unwrapped_token) != 4:
            raise LDAPCommunicationError('Incorrect response from server')

        server_security_layers = unwrapped_token[0]
        if not isinstance(server_security_layers, int):
            server_security_layers = ord(server_security_layers)
        if server_security_layers in (0, NO_SECURITY_LAYER):
            if unwrapped_token.message[1:] != '\x00\x00\x00':
                raise LDAPCommunicationError(
                    'Server max buffer size must be 0 if no security layer'
                )
        if not server_security_layers & NO_SECURITY_LAYER:
            raise LDAPCommunicationError(
                'Server requires a security layer, but this is not implemented'
            )

        client_security_layers = bytearray([NO_SECURITY_LAYER, 0, 0, 0])
        kerberos.authGSSClientWrap(
            ctx,
            base64.b64encode(
                bytes(client_security_layers) + authz_id
            ).decode('ascii')
        )
        out_token = kerberos.authGSSClientResponse(ctx) or ''

        return send_sasl_negotiation(
            connection,
            controls,
            base64.b64decode(out_token)
        )
    except (kerberos.GSSError, LDAPCommunicationError):
        abort_sasl_negotiation(connection, controls)
        raise

Установить winkerberos : pip install winkerberos

В вашем скрипте используйте следующий код (параметры connect_timeout, mode и receive_timeout приведены только для примера и могут быть пропущены или изменены):

import ldap
import ldap3kerberos

server = ldap3.Server(fqdn, connect_timeout=10, mode=ldap3.IP_V4_ONLY)
conn = ldap3kerberos.Connection(
    server, authentication=ldap3.SASL, sasl_mechanism=ldap3.GSSAPI,
    auto_bind=True, receive_timeout=10
)

Если у вас есть несколько серверов контроллера домена для домена AD, убедитесь, что вы подключаетесь к какому-то конкретному серверу, в противном случае вы получите исключение:

winkerberos.GSSError: SSPI: InitializeSecurityContext: The specified target is unknown or unreachable

0 голосов
/ 11 сентября 2018

Спасибо, что спросили это.Сегодня я сделал последний снимок и заставил его работать.

См. Ответ Давиде по этой ссылке: Как установить модуль Python gssapi на Windows?

Это требует от васчтобы получить пакет ldap3 и установить пакет winkerberos:

pip install winkerberos

Затем вам нужно заменить файл kerberos.py в ваших пакетах сайта (PYTHON_HOME \ Lib \ site-packages\ ldap3 \ protocol \ sasl \ kerberos.py) с тем, который он связывает с заменой kerberos.py .

Вам необходимо изменить следующую строку в файле замены kerberos.py:

из беговой дорожки импортировать kerberoswrapper as kerberos

Изменить на

импорт Winkerberos как Kerberos

Затем вы можете подключиться так:

from ldap3 import Server, Connection, Tls, SASL, GSSAPI
import ssl

tls = Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1)
server = Server('server_fqdn', use_ssl=True, tls=tls)
c = Connection(server, authentication=SASL, sasl_mechanism=GSSAPI)
c.bind()
print(c.extend.standard.who_am_i())
c.unbind()

Замените server_fqdn на полное доменное имя вашего сервера AD.

Возможно, вы захотите изменить значение версии на любой протокол, используемый вашим сервером AD.

Если у кого-то есть менее грязный метод для этого, пожалуйста, включите!

...