【问题标题】:Passwordless Python LDAP3 authentication from Windows client来自 Windows 客户端的无密码 Python LDAP3 身份验证
【发布时间】:2018-09-11 15:32:49
【问题描述】:

我正在使用出色的 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 可能是解决方案,但我在互联网上没有找到任何可理解的示例。

【问题讨论】:

    标签: python windows authentication active-directory ldap


    【解决方案1】:

    感谢您提出这个问题。我今天给了它最后一枪,让它开始工作。

    See Davide's answer

    它要求您拥有 ldap3 包并安装 winkerberos 包:

    pip install winkerberos
    

    然后您需要将站点包 (PYTHON_HOME\Lib\site-packages\ldap3\protocol\sasl\kerberos.py) 中的 kerberos.py 文件替换为他链接到 replacement kerberos.py 的文件。

    您需要在替换的kerberos.py文件中更改以下行:

    from treadmill import kerberoswrapper as kerberos 
    

    改成

    import winkerberos as 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 服务器使用的任何协议。

    如果有人有更简单的方法来完成此任务,请加入!

    【讨论】:

    • 成功了!但是我不能用 gorilla 包做一个适当的猴子补丁。因为异常在导入而不是在函数内引发。你有什么想法吗?
    • 我将 ldap3 包从站点包复制到与我的脚本相同的位置,并在我的本地副本中替换了 kerberos.py 文件,这似乎是一种更简洁的方法,并且不需要猴子修补。我无法使用猴子修补方法。
    • 您可以通过替换位于 ldap3/core/connection.py 中的 Connection 类的 do_sasl_bind 函数来对其进行修补
    • 感谢您的回答 - 您是否遇到过 SecurityContexts 和 winkerberos 的任何问题?无论出于何种原因,我都遇到了未知/无法到达的目标
    • 我没有遇到任何问题。您将 server_fqdn 替换为您的域控制器之一的全名?
    【解决方案2】:

    使用最初的答案,为了避免猴子补丁,可以使用以下代码,基于提供的文件 thereldap3\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
    

    安装 winkerberospip install winkerberos

    在您的脚本中,使用以下代码(connect_timeoutmodereceive_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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多