コード例 #1
0
def rotate_skeys(duo_client):
    """
    Generate new DRPC skeys (signing and encryption) via ECDHE with the Duo cloud service.

    Args:
        duo_client (CloudSSODuoClient): a duo client with the current credentials

    Returns:
        (signing_skey, encryption_skey) tuple with the new keys as bytes, or reraises API errors

    """
    proxy_public_key, proxy_private_key = drpc_crypto.generate_ephemeral_keys()
    try:
        ser_proxy_public_key = drpc_crypto.serialize_ephemeral_key(
            proxy_public_key)
        rotate_result = yield duo_client.proxy_rotate_skeys(
            ser_proxy_public_key)
        duo_public_key = drpc_crypto.deserialize_ephemeral_key(
            rotate_result['duo_public_key'])
    except duo_async.DuoAPIError as e:
        log.err(e, 'Rotate call failed')
        raise e

    new_signing_skey_bytes, new_encryption_skey_bytes = drpc_crypto.derive_shared_keys(
        duo_public_key, proxy_private_key)

    defer.returnValue((new_signing_skey_bytes, new_encryption_skey_bytes))
コード例 #2
0
    def duo_auth_only(self, request, factor):
        """
        duo_auth and return its result.  On failure, generate an appropriate
        response based on the configured failmode.
        """
        try:
            client_ip = request.client_ip
            if not ip_util.is_valid_ip(client_ip):
                client_ip = None
            auth_res = yield self.client.auth(request.username, factor,
                                              client_ip)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo auth call failed')
            if duo_async.should_server_fail_open(self.failmode, e.fail_open):
                msg = duo_async.get_fail_open_msg()
                self.log_request(request, msg)
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_SECONDARY,
                                  status=log.AUTH_ALLOW,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                ret = {'result': duo_async.API_RESULT_ALLOW, 'status': msg}
                defer.returnValue(ret)
            else:
                msg = duo_async.FAILMODE_SECURE_MSG
                self.log_request(request, msg)
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_SECONDARY,
                                  status=log.AUTH_ERROR,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                ret = {'result': duo_async.API_RESULT_DENY, 'status': msg}
                defer.returnValue(ret)

        if auth_res['result'] == duo_async.API_RESULT_ALLOW:
            log.auth_standard(msg=auth_res['status'],
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        else:
            log.auth_standard(msg=auth_res['status'],
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)

        defer.returnValue(auth_res)
コード例 #3
0
ファイル: server.py プロジェクト: slayer/duoauthproxy-freebsd
 def datagramReceived(self, datagram, addr):
     """addr is a tuple of (host, port). If calling from a test case, you
     probably want to call handle_datagram_received instead so exceptions
     aren't lost."""
     host, port = addr
     try:
         yield self.handle_datagram_received(datagram, host, port)
     except packet.PacketError as err:
         log.msg("dropping packet from %s:%s - %s" % (host, port, err))
     except Exception:
         log.err(
             None,
             "unhandled error processing request from %s:%s" % (host, port))
コード例 #4
0
    def get_creds_for_ldap_idp(self, rikey, auth_type):
        try:
            creds = self.credential_mapping[rikey]
            if auth_type == const.AD_AUTH_TYPE_SSPI:
                creds = ldap_base.ServiceAccountCredential(username=None,
                                                           password=None)

            return creds
        except KeyError as e:
            log.err(e, ERR_LDAP_SSO_MISSING_RIKEY)
            raise drpc.CallError(ERR_LDAP_SSO_MISSING_RIKEY, {
                'error': str(e),
            })
コード例 #5
0
def _info_callback(conn, where, ret):
    """Use the info callback to gather information about attempted
    SSL connections and warn about incompatibilities. See the man
    page[1] for further information.

    [1] https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_info_callback.html
    """
    if where & OpenSSL.SSL.SSL_CB_WRITE_ALERT:
        if conn.get_cipher_name() is None:
            log.err(
                "Unable to establish SSL connection. "
                "Client may be attemping incompatible protocol version or cipher."
            )
コード例 #6
0
    def get_challenge_response(self, request, state):
        # Do not have access to factor - request.password is users cookie
        success = False
        self.log_request(request, 'Challenge Response: %r' % request.password)

        if state and 'primary_res' in state:
            radius_attrs = state['primary_res'].radius_attrs
        else:
            radius_attrs = {}

        auth_cookie = urllib.parse.unquote(self._get_authcookie(request))
        try:
            finish_res = yield self.client.proxy_finish(auth_cookie)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo proxy_finish call failed')
            response_packet = self.response_for_api_error(
                request,
                state['primary_res'],
                e,
                state['primary_res'].radius_attrs,
            )
            defer.returnValue(response_packet)

        self.log_request(request,
                         'Authcookie validation result: %r' % finish_res)
        if (finish_res['valid_cookie']
                and (finish_res['user'] == request.username)):
            success = True

        if success:
            log.auth_standard(msg='Valid login from iframe',
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(
                self.create_accept_packet(
                    request,
                    radius_attrs=radius_attrs,
                ))
        else:
            log.auth_standard(msg='Invalid login from iframe',
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request))
コード例 #7
0
ファイル: client.py プロジェクト: slayer/duoauthproxy-freebsd
 def datagramReceived(self, datagram, addr):
     """addr is a tuple of (host, port). If calling from a test case, you
     probably want to call handle_response instead so exceptions aren't
     lost."""
     host, port = addr
     if self.debug:
         log.msg('Packet dump - received from %s:' % (host))
         log.msg(repr(datagram))
     try:
         self.handle_response(datagram, (host, port))
     except packet.PacketError as err:
         log.msg("dropping packet from %s:%s - %s" % (host, port, err))
     except Exception as err:
         traceback.print_exc()
         log.err(err)
コード例 #8
0
 def _verify_ldap_config_args(self, bind_dn: str, bind_pw: str,
                              auth_type: str, transport_type: str) -> None:
     if auth_type not in const.AD_AUTH_TYPES:
         raise drpc_exceptions.CallBadArgError(['auth_type'])
     if transport_type not in const.AD_TRANSPORTS:
         raise drpc_exceptions.CallBadArgError(['transport_type'])
     if auth_type != const.AD_AUTH_TYPE_SSPI \
             and (bind_dn == '' or bind_pw == ''):
         e = drpc_exceptions.CallError(
             ERR_LDAP_CONFIGURATION_FAILED, {
                 'authproxy_configuration_error':
                 'Missing {0} or {1}'.format(CONFIG_BIND_USER,
                                             CONFIG_BIND_PASSWORD)
             })
         log.err(e, ERR_LDAP_CONFIGURATION_FAILED)
         raise e
コード例 #9
0
    def do_get_proxy_counter(self):
        log.msg('Reporting proxy counter')
        try:
            counter = secret_storage.access_proxy_counter()
            if self.debug:
                log.msg(
                    'Found counter value {counter}'.format(counter=counter))
        except Exception as e:
            log.err(e, ERR_COUNTER_FAILURE)
            raise drpc.CallError(ERR_COUNTER_FAILURE, {
                'during': 'get_proxy_counter',
                'error': str(e)
            })

        return {
            'counter': counter,
        }
コード例 #10
0
        def handle_msg(value, controls, d):
            try:
                if isinstance(value, pureldap.LDAPSearchResultDone):
                    e = ldaperrors.get(value.resultCode, value.errorMessage)
                    if isinstance(e, (ldaperrors.Success,
                                      ldaperrors.LDAPSizeLimitExceeded)):
                        cookie = get_cookie(controls)
                        d.callback((None, cookie))
                    else:
                        d.callback((e, None))
                elif isinstance(value, pureldap.LDAPSearchResultEntry):
                    # Always send DN. Overwrite DN from attribute set, if any.
                    obj = {
                        'distinguishedname': [escape_bytes(value.objectName)]
                    }

                    for k, vs in value.attributes:
                        # Smash attribute name case.
                        k = k.decode().lower()

                        # Server may not honor attributes (e.g.
                        # SearchByTreeWalkingMixin).
                        if attributes and k.encode() not in attributes:
                            continue

                        # Covert value to list and encode for JSON.
                        vs = [escape_bytes(v) for v in vs]

                        obj[k] = vs

                    # Refuse to return certain attributes even if all
                    # attributes were requested.
                    obj.pop('userpassword', None)

                    res.append(obj)
            except Exception:
                log.err()
            finally:
                return isinstance(value, (
                    pureldap.LDAPBindResponse,
                    pureldap.LDAPSearchResultDone,
                ))
コード例 #11
0
    def _get_client(self, host, port, transport_type, ssl_verify_depth,
                    ssl_verify_hostname, ssl_ca_certs, timeout, debug,
                    is_logging_insecure):
        if ssl_ca_certs:
            ssl_ca_certs = load_ca_bundle(ssl_ca_certs)
            if not ssl_ca_certs:
                # Didn't parse out any PEM certificates.
                raise drpc_exceptions.CallBadArgError(['ssl_ca_certs'])
        else:
            # Ensure ssl_ca_certs is a list.
            ssl_ca_certs = []

        is_ssl = transport_type != const.AD_TRANSPORT_CLEAR
        if is_ssl:
            if ssl_verify_hostname and not ssl_ca_certs:
                log.msg('Missing required configuration item: '
                        "'SSL verify hostname' requires that "
                        "'SSL CA certs' also be specified "
                        '(and non-empty).')
                raise drpc_exceptions.CallError(ERR_LDAP_CONFIGURATION_FAILED)

        try:
            factory = self.ldap_client_factory(
                timeout=timeout,
                transport_type=transport_type,
                ssl_verify_depth=ssl_verify_depth,
                ssl_verify_hostname=ssl_verify_hostname,
                ssl_ca_certs=ssl_ca_certs,
                debug=debug,
                is_logging_insecure=is_logging_insecure,
            )
        except Exception as e:
            log.err(e, ERR_LDAP_CONFIGURATION_FAILED)
            raise drpc_exceptions.CallError(ERR_LDAP_CONFIGURATION_FAILED, {
                'error': str(e),
            })

        try:
            factory.connect_ldap(host, port)
            client = yield factory.deferred
        except ldap.client.ADClientError as e:
            if isinstance(e.underlying_exception, DNSLookupError):
                log.err(e, ERR_LDAP_HOSTNAME_RESOLUTION_FAILED)
                raise drpc_exceptions.CallError(
                    ERR_LDAP_HOSTNAME_RESOLUTION_FAILED, {
                        'error': str(e),
                    })
            else:
                log.err(e, ERR_LDAP_CONNECTION_FAILED)
                raise drpc_exceptions.CallError(ERR_LDAP_CONNECTION_FAILED, {
                    'error': str(e),
                })
        except Exception as e:
            log.err(e, ERR_LDAP_CONNECTION_FAILED)
            raise drpc_exceptions.CallError(ERR_LDAP_CONNECTION_FAILED, {
                'error': str(e),
            })

        def timeout_cb():
            log.msg('LDAP operation timed out')
            client.transport.abortConnection()

        timeout_dc = reactor.callLater(timeout, timeout_cb)

        defer.returnValue((client, timeout_dc))
コード例 #12
0
    def authenticate_against_server(
            self,
            host: str,
            port: str,
            username: str,
            password: str,
            service_account_username: str,
            service_account_password: str,
            base_dns: Optional[List[str]] = None,
            ntlm_domain: Optional[str] = None,
            ntlm_workstation: Optional[str] = None,
            auth_type: str = const.AD_AUTH_TYPE_NTLM_V2,
            transport_type: str = const.AD_TRANSPORT_STARTTLS,
            ssl_verify_depth: int = const.DEFAULT_SSL_VERIFY_DEPTH,
            ssl_verify_hostname: bool = True,
            ssl_ca_certs: Optional[str] = None,
            timeout: int = 60,
            username_attributes: Optional[List[str]] = None,
            call_id: Optional[int] = None):
        """
        * username_attribute: attribute within AD that we will compare the
          username against.

        See do_ldap_search for further argument documentation.

        Returns: tuple(bool, str).
            - True if the auth was successful,
            - The full dn of the user who authed
        """

        bind_dn = service_account_username
        bind_pw = service_account_password
        # At this point we *should* have a username attribute passed to us. But if we don't have one
        # we will default to the most popular attribute, samaccountname. This will only work for AD.
        username_attributes = username_attributes if username_attributes else [
            'samaccountname'
        ]
        base_dns = base_dns if base_dns else ['']

        log.msg((
            "Performing LDAP authentication: "
            "call_id={call_id} host={host} port={port} base_dns={base_dns} "
            "auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs} username={username} username_attributes={username_attributes}"
        ).format(call_id=call_id,
                 host=host,
                 port=port,
                 base_dns=base_dns,
                 auth_type=auth_type,
                 transport_type=transport_type,
                 ssl_verify_depth=ssl_verify_depth,
                 ssl_verify_hostname=ssl_verify_hostname,
                 username=username,
                 username_attributes=username_attributes,
                 ssl_ca_certs=ssl_ca_certs is not None))

        # The factory has a username attribute field on it but we will not be using it
        cl, timeout_dc = yield self._get_client(
            host,
            port,
            transport_type,
            ssl_verify_depth,
            ssl_verify_hostname,
            ssl_ca_certs,
            timeout,
            self.debug,
            self.is_logging_insecure,
        )

        try:
            # Primary bind. This authenticates us to AD and allows us to
            # make search queries.
            try:
                yield cl.perform_bind(auth_type=auth_type,
                                      dn=bind_dn,
                                      username=bind_dn,
                                      password=bind_pw,
                                      domain=ntlm_domain,
                                      workstation=ntlm_workstation,
                                      permit_implicit=True)
            except Exception as e:
                if timeout_dc.active():
                    log.err(e, ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, {
                        'error': str(e),
                    })
                else:
                    log.err(e, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'bind',
                        'error': str(e),
                    })

            username_match = dict.fromkeys(username_attributes, username)
            filterObject = yield cl.user_filter_object(
                username_matches=username_match)

            # Search for the user. With AD, the user's cn is their full
            # name, not username. So, we need to search for the user by comparing
            # the username to a selection of specific attributes. For example:
            # sAMAccountName or userprincipalname

            search_hits: List[BytesLDAPEntry] = []
            try:
                # Always fetch msDS-PrincipalName since we need the user's
                # sAMAccountName and domain. The username entered by the user
                # may not be in a valid format for SSPI or NTLM depending on
                # the username attributes configured, but the sAMAccountName
                # that's part of msDS-PrincipalName will always work.
                attributes_to_fetch = {
                    str(attr).lower()
                    for attr in username_attributes
                }
                attributes_to_fetch.add('msds-principalname')
                # Also fetch some helpful attributes for debugging if something goes wrong
                attributes_to_fetch.add('objectclass')
                attributes_to_fetch.add('objectcategory')

                # To check multiple base dns we need to perform a search for each one. If the
                # combination of all of these searches returns more than 1 unique user we will
                # fail the auth
                for base_dn in base_dns:
                    result = yield cl.perform_search(
                        base_dn,
                        filterObject,
                        attributes=tuple(attributes_to_fetch))
                    search_hits.extend(result)
            except distinguishedname.InvalidRelativeDistinguishedName as e:
                log.err(e, ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION)
                raise drpc.CallError(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION, {
                    'error': str(e),
                })
            except Exception as e:
                log.err(
                    e, '{msg} for {username}'.format(
                        msg=ldap_base.ERR_LDAP_SEARCH_FAILED,
                        username=username))
                raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    'error': str(e),
                })

            if len(search_hits) == 0:
                err_msg = AUTH_INVALID_USER
                err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    'error': err_msg,
                })
                log.err(
                    err, '{msg} {username}'.format(msg=err_msg,
                                                   username=username))
                raise err

            if len(search_hits) > 1:
                err_msg = AUTH_TOO_MANY_USERS
                err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    'error': err_msg,
                })
                log.err(
                    err,
                    '{msg} while searching for {username}: {users}'.format(
                        msg=err_msg,
                        username=username,
                        users=[str(user.dn) for user in search_hits]))
                raise err

            user_result = search_hits[0]
            user_full_dn = str(user_result.dn)
            # Log out the search result
            matched_attributes = determine_matched_attributes(
                username, username_attributes, user_result)
            log.msg('Found {username} with attributes {atts}'.format(
                username=username, atts=matched_attributes))

            # Initialize these as None since Plain binds don't these values
            bind_username = None
            user_domain = None
            # If the auth type is not plain, determine the user's domain and
            # sAMAccountName by pulling them from msDS-PrincipalName. We need
            # both in order to do NTLM and SSPI authentications.
            if auth_type != AD_AUTH_TYPE_PLAIN:
                msds_principalname_attribute_set = user_result.get(
                    'msDS-PrincipalName')
                if not msds_principalname_attribute_set:
                    # msDS-PrincipalName must be provided so we can determine
                    # the domain of the user authenticating. Abort the
                    # authentication if the attribute doesn't exist for the user.
                    err = drpc.CallError(ERR_LDAP_MISSING_REQUIRED_ATTRIBUTE, {
                        'error':
                        MSDS_PRINCIPAL_NAME_MISSING.format(username)
                    })
                    log.err(err, user_result)
                    raise err

                msds_principalname = list(
                    user_result.get('msDS-PrincipalName'))[0].decode('utf8')
                if not msds_principalname or '\\' not in msds_principalname:
                    # This means that msDS-PrincipalName was empty, a form
                    # we can't work with (e.g. SID), or didn't have any
                    # domain information on it. We can't guarantee we'll
                    # log in the correct user without knowing their domain,
                    # so abort the authentication.
                    err = drpc.CallError(ERR_LDAP_INVALID_ATTRIBUTE_VALUE, {
                        'error':
                        INVALID_MSDS_PRINCIPAL_NAME.format(username)
                    })
                    log.err(err, user_result)
                    raise err

                user_domain, bind_username = msds_principalname.split('\\', 1)

            # Secondary bind. Assuming the user we queried exists, attempt
            # to bind as them (this is essentially performing an authentication).
            try:
                yield cl.perform_bind(auth_type=auth_type,
                                      dn=user_full_dn,
                                      username=bind_username,
                                      password=password,
                                      domain=user_domain,
                                      workstation=ntlm_workstation,
                                      permit_implicit=False)
            except Exception as e:
                log.msg(
                    'Authentication failed for user {username} ({dn})'.format(
                        username=username, dn=user_full_dn))
                log.msg(e)
                defer.returnValue((False, None))
        finally:
            if timeout_dc.active():
                timeout_dc.cancel()
            try:
                cl.transport.abortConnection()
            except Exception:
                pass

        log.msg('Authentication succeeded for user {username} ({dn})'.format(
            username=username, dn=user_full_dn))
        defer.returnValue((True, user_full_dn))
コード例 #13
0
    def _paged_search(self,
                      client,
                      base_dn,
                      filter_text,
                      attributes,
                      pagination=PAGINATION_TYPE_CRITICAL,
                      max_result_size=None,
                      page_size=DEFAULT_PAGE_SIZE):
        """
        Given a bound client, search exhaustively using RFC 2696.
        Return a list of dictionaries containing the attributes
        of the resulting entries.

        * attributes: Set of lower-case byte-strings.
        """
        res = []

        def handle_msg(value, controls, d):
            try:
                if isinstance(value, pureldap.LDAPSearchResultDone):
                    e = ldaperrors.get(value.resultCode, value.errorMessage)
                    if isinstance(e, (ldaperrors.Success,
                                      ldaperrors.LDAPSizeLimitExceeded)):
                        cookie = get_cookie(controls)
                        d.callback((None, cookie))
                    else:
                        d.callback((e, None))
                elif isinstance(value, pureldap.LDAPSearchResultEntry):
                    # Always send DN. Overwrite DN from attribute set, if any.
                    obj = {
                        'distinguishedname': [escape_bytes(value.objectName)]
                    }

                    for k, vs in value.attributes:
                        # Smash attribute name case.
                        k = k.decode().lower()

                        # Server may not honor attributes (e.g.
                        # SearchByTreeWalkingMixin).
                        if attributes and k.encode() not in attributes:
                            continue

                        # Covert value to list and encode for JSON.
                        vs = [escape_bytes(v) for v in vs]

                        obj[k] = vs

                    # Refuse to return certain attributes even if all
                    # attributes were requested.
                    obj.pop('userpassword', None)

                    res.append(obj)
            except Exception:
                log.err()
            finally:
                return isinstance(value, (
                    pureldap.LDAPBindResponse,
                    pureldap.LDAPSearchResultDone,
                ))

        if filter_text:
            filter_obj = ldapfilter.parseFilter(filter_text)
        else:
            filter_obj = None
        op = pureldap.LDAPSearchRequest(
            baseObject=base_dn,
            scope=pureldap.LDAP_SCOPE_wholeSubtree,
            derefAliases=0,
            sizeLimit=0,
            timeLimit=0,
            typesOnly=0,
            filter=filter_obj,
            attributes=attributes,
        )

        if pagination == PAGINATION_TYPE_CRITICAL:
            # AD may ignore the RFC 2696 control if it is not
            # critical. The caller can override this default if the
            # control should be present but not critical or absent.
            criticality = True
        else:
            criticality = False

        cookie = pureber.BEROctetString('')
        while True:
            if pagination == PAGINATION_TYPE_DISABLE:
                controls = None
            else:
                controls = [
                    (
                        RFC_2696_CONTROL_TYPE,
                        criticality,
                        pureber.BERSequence([
                            pureber.BERInteger(page_size),
                            cookie,
                        ]),
                    ),
                ]

            d = defer.Deferred()
            yield client.send(
                op=op,
                controls=controls,
                handler=functools.partial(handle_msg, d=d),
                handle_msg=True,
            )

            # handle_msg() is synchronous so d should be called by the
            # time it returns to LDAPClient.handle().
            if d.called:
                e, cookie = yield d
            else:
                log.err('Paging cookie not found!')
                break
            if e is not None:
                # So the RPC caller can distinguish between problems
                # with the search (e.g. bad configuration) and
                # searches that return no results.
                raise e
            if not cookie.value:
                break
            if max_result_size is not None and len(res) > max_result_size:
                break
        defer.returnValue(res)
コード例 #14
0
    def do_ldap_health_check(self,
                             rikey,
                             host,
                             port,
                             base_dns,
                             filter_text=None,
                             attributes=None,
                             ntlm_domain=None,
                             ntlm_workstation=None,
                             auth_type=const.AD_AUTH_TYPE_NTLM_V2,
                             transport_type=const.AD_TRANSPORT_STARTTLS,
                             ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH,
                             ssl_verify_hostname=True,
                             ssl_ca_certs=None,
                             timeout=60,
                             call_id=None):
        """
        Performs a health check against the specified host by binding as the
        service user and executing a dummy search against each provided base DN
        to ensure the searches are executed without error.
        """

        # Do this outside of the try/except. If something goes wrong with these
        # operations, then we should raise that error and not treat it as an
        # unhealthy result.
        service_account_credentials = self.get_creds_for_ldap_idp(
            rikey, auth_type)

        self._verify_ldap_config_args(service_account_credentials.username,
                                      service_account_credentials.password,
                                      auth_type, transport_type)

        log.msg((
            "Performing health check: "
            "call_id={call_id} host={host} port={port} rikey={rikey} "
            "attributes={attributes} base_dns={base_dns} ntlm_domain={ntlm_domain} "
            "ntlm_workstation={ntlm_workstation} auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs}").format(
                call_id=call_id,
                host=host,
                port=port,
                rikey=rikey,
                attributes=attributes,
                base_dns=base_dns,
                ntlm_domain=ntlm_domain,
                ntlm_workstation=ntlm_workstation,
                auth_type=auth_type,
                transport_type=transport_type,
                ssl_verify_depth=ssl_verify_depth,
                ssl_verify_hostname=ssl_verify_hostname,
                ssl_ca_certs=ssl_ca_certs is not None))

        client, timeout_dc = yield self._get_client(
            host, port, transport_type, ssl_verify_depth, ssl_verify_hostname,
            ssl_ca_certs, timeout, self.debug, self.is_logging_insecure)

        try:
            try:
                # First make sure we can bind as the service user
                yield client.perform_bind(
                    auth_type=auth_type,
                    dn=service_account_credentials.username,
                    username=service_account_credentials.username,
                    password=service_account_credentials.password,
                    domain=ntlm_domain,
                    workstation=ntlm_workstation,
                    permit_implicit=True)
            except OpenSSL.SSL.Error as e:
                error_message = util.retrieve_error_string_from_openssl_error(
                    e)
                if OPENSSL_ERROR_INVALID_CERT in error_message:
                    drpc_error = ERR_TLS_CERT
                elif OPENSSL_ERROR_INVALID_PROTOCOL in error_message:
                    drpc_error = ERR_TLS_INVALID_PROTOCOL
                else:
                    drpc_error = ERR_TLS_GENERIC
                log.err(e, drpc_error)
                raise drpc.CallError(drpc_error, {
                    'error': str(e),
                })
            except ldaperrors.LDAPInvalidCredentials as e:
                raise drpc.CallError(ERR_LDAP_BIND_INVALID_CREDS, {
                    'error': str(e),
                })
            except Exception as e:
                # T76043: add better/more specific error handling so we can tell
                # Gary why the search failed
                if timeout_dc.active():
                    log.err(e, ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, {
                        'error': str(e),
                    })
                else:
                    log.err(e, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'bind',
                        'error': str(e),
                    })

            # Then execute a search over each provided base DN to make sure that:
            #   1) The service user has search permissions
            #   2) Each of the provided base DN's exists
            filter_obj = ldapfilter.parseFilter(
                filter_text.encode('utf-8')) if filter_text else None
            try:
                for base_dn in base_dns:
                    yield client.perform_search(
                        dn=base_dn,
                        filter_object=filter_obj,
                        attributes=attributes,
                        scope=pureldap.LDAP_SCOPE_baseObject,
                        sizeLimit=1)
            except (ldaperrors.LDAPNoSuchObject, ldaperrors.LDAPReferral) as e:
                # https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes/#rc-noSuchObject
                # Object at the requested BaseDN doesn't exist. Since we are searching for just anything at all
                # this likely means the Base DN is invalid.
                # https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes/#rc-referral
                # We dont support referrals so it's basically an unknown/bad DN
                log.err(e, ERR_LDAP_INVALID_BASE_DN)
                raise drpc.CallError(ERR_LDAP_INVALID_BASE_DN, {
                    'error': str(e),
                    'base_dn': str(base_dn)
                })
            except Exception as e:
                # T76043: add better/more specific error handling so we can tell
                # Gary why the search failed
                if timeout_dc.active():
                    log.err(e, ldap_base.ERR_LDAP_SEARCH_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                        'error': str(e),
                    })
                else:
                    log.err(e, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'search',
                        'error': str(e),
                    })
        finally:
            if timeout_dc.active():
                timeout_dc.cancel()
            try:
                client.transport.abortConnection()
            except Exception as e:
                log.err(e,
                        "Error cleaning up connection to host {}".format(host))

        # If nothing went wrong by this point, then everything's healthy as
        # far as we can tell.
        result = {
            'healthy': True,
        }

        log.msg(result)
        defer.returnValue(result)
コード例 #15
0
    def fetch_ldap_attributes_from_server(
        self,
        host,
        port,
        rikey,
        desired_attributes,
        user_dn,
        ntlm_domain=None,
        ntlm_workstation=None,
        auth_type=const.AD_AUTH_TYPE_NTLM_V2,
        transport_type=const.AD_TRANSPORT_STARTTLS,
        ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH,
        ssl_verify_hostname=True,
        ssl_ca_certs=None,
        timeout=60,
        call_id=None,
    ):
        creds = self.get_creds_for_ldap_idp(rikey, auth_type)
        bind_dn = creds.username
        bind_pw = creds.password

        log.msg((
            "Performing user attributes fetch: "
            "call_id={call_id} host={host} port={port} rikey={rikey} "
            "desired_attributes={desired_attributes} user_dn={user_dn} ntlm_domain={ntlm_domain} "
            "ntlm_workstation={ntlm_workstation} auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs}").format(
                call_id=call_id,
                host=host,
                port=port,
                rikey=rikey,
                desired_attributes=desired_attributes,
                user_dn=user_dn,
                ntlm_domain=ntlm_domain,
                ntlm_workstation=ntlm_workstation,
                auth_type=auth_type,
                transport_type=transport_type,
                ssl_verify_depth=ssl_verify_depth,
                ssl_verify_hostname=ssl_verify_hostname,
                ssl_ca_certs=ssl_ca_certs is not None))

        self._verify_ldap_config_args(bind_dn, bind_pw, auth_type,
                                      transport_type)

        client, timeout_dc = yield self._get_client(
            host, port, transport_type, ssl_verify_depth, ssl_verify_hostname,
            ssl_ca_certs, timeout, self.debug, self.is_logging_insecure)

        # Try to do everything network-related
        try:
            # Bind as service user, for searching
            try:
                yield client.perform_bind(auth_type=auth_type,
                                          dn=bind_dn,
                                          username=bind_dn,
                                          password=bind_pw,
                                          domain=ntlm_domain,
                                          workstation=ntlm_workstation,
                                          permit_implicit=True)
            except Exception as e:
                if timeout_dc.active():
                    log.err(e, ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED,
                                         {'error': six.text_type(e)})
                else:
                    log.err(e, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'bind',
                        'error': six.text_type(e)
                    })

            # Search for the user
            try:
                result = yield client.perform_search(
                    user_dn,
                    None,
                    attributes=desired_attributes,
                    scope=pureldap.LDAP_SCOPE_baseObject)

                if len(result) != 1:
                    log.err(AUTH_INVALID_USER)
                    raise Exception(AUTH_INVALID_USER)

                result = result[0]
            except distinguishedname.InvalidRelativeDistinguishedName as irdn:
                log.err(irdn, ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION)
                raise drpc.CallError(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION,
                                     {'error': six.text_type(irdn)})
            except Exception as e:
                log.err(e, ldap_base.ERR_LDAP_SEARCH_FAILED)
                raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED,
                                     {'error': six.text_type(e)})
        finally:
            # Clean up networking
            if timeout_dc.active():
                timeout_dc.cancel()

            try:
                client.transport.abortConnection()
            except Exception:
                pass

        # At this point, result is a single LDAPEntry, or something has gone wrong and we ideally raised somewhere above
        encoded_dict = transform_result(result, desired_attributes)

        logged_atts = transform_result(
            result,
            desired_attributes,
            value_transform=self.to_unicode_ignore_errors)
        log.msg('For user dn {dn}, found attributes {atts}'.format(
            dn=user_dn, atts=logged_atts))

        defer.returnValue(encoded_dict)
コード例 #16
0
    def get_func_for_drpc_call(self, call_name):
        if call_name in self.drpc_calls:
            return self.drpc_calls[call_name]

        log.err('Unknown DRPC call \'{0}\''.format(call_name))
        return None
コード例 #17
0
    def try_ranged_search(self, client, base_dn, objs):
        """ So, you are an empty group object or a group with too many members
            to return in one shot.
            Let's try to grab those members a few at a time until we determine
            that the group is empty or we've built up the full list of members.
        """
        for obj in objs:
            member_range = None
            previous_result_empty = False
            # loop until we've exhausted all range segments
            while True:
                # can't help you without a dn. sorry.
                if not obj.get('distinguishedname'):
                    break
                previous_range = member_range
                member_range = next_range(previous_range,
                                          previous_result_empty)

                # create an ldap filter object to search with.
                # pureldap will escape the distinguishedname, making it safe.
                # Since we're reading the distinguishedname from search results,
                # we'll need to unescape it before it gets re-escaped.
                attr_obj = pureldap.LDAPAttributeDescription(
                    'distinguishedname')
                value_obj = pureldap.LDAPAssertionValue(
                    distinguishedname.unescape(obj['distinguishedname'][0]))
                filter_object = pureldap.LDAPFilter_equalityMatch(
                    attr_obj, value_obj)

                attributes = [
                    'member;range={0}-{1}'.format(member_range[0],
                                                  member_range[1])
                ]
                try:
                    ranged_res = yield client.perform_search(
                        base_dn, filter_object, attributes=attributes)
                except LDAPOperationsError as e:
                    # searched for a range that exceeds the total number of
                    # elements in the attribute.
                    # if this is the first instance of this error, we may
                    # just need to try again with a '*' as the upper bound
                    # to get the last few members.
                    # if this is the second time (we are using the '*'), then
                    # we already have all of the members and can break.
                    if ldap.client.ERR_AD_CANT_RETRIEVE_ATTS in e.message:
                        if previous_result_empty:
                            break
                        previous_result_empty = True
                        continue
                    # something bad happened... give up
                    else:
                        log.err(e)
                        break
                # no results; we're done here
                if len(ranged_res) == 0:
                    break
                else:
                    # update the result object with the newly discovered
                    # members
                    for k, vs in ranged_res[0].items():
                        k = k.decode().lower()
                        vs = [escape_bytes(v) for v in vs]
                        if k.startswith('member;'):
                            obj['member'].extend(vs)
コード例 #18
0
    def preauth(self, request, primary_res, radius_reject_attrs=None):
        # perform preauth request
        if radius_reject_attrs is None:
            radius_reject_attrs = {}
        try:
            if request.username in self.exempt_usernames:
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': 'User exempted from 2FA'
                }
            else:
                preauth_res = yield self.client.preauth(
                    request.username, request.client_ip, self.failmode)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo preauth call failed')
            response = self.response_for_api_error(request, primary_res, e,
                                                   radius_reject_attrs)
            defer.returnValue((response, None))

        self.log_request(
            request, 'Got preauth result for: %r' % (preauth_res['result'], ))

        if preauth_res['result'] == duo_async.API_RESULT_ALLOW:
            msg = preauth_res['status']
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_accept_packet(
                request,
                msg,
                primary_res.radius_attrs,
            )
        elif preauth_res['result'] == duo_async.API_RESULT_DENY:
            msg = preauth_res['status']
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_reject_packet(
                request, msg, radius_attrs=radius_reject_attrs)
        elif preauth_res['result'] == duo_async.API_RESULT_ENROLL:
            msg = preauth_res['status']
            log.auth_standard(msg=log.AUTH_ENROLL_MSG,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_reject_packet(
                request, msg, radius_attrs=radius_reject_attrs)
        else:
            # duo_async.API_RESULT_AUTH - return a sentinel value
            # saying we should continue
            response = None
        defer.returnValue((response, preauth_res))
コード例 #19
0
    def do_ldap_search(self,
                       host: str,
                       port: int,
                       base_dn: str,
                       filter_text: Optional[str] = None,
                       attributes: Optional[Set[str]] = None,
                       ntlm_domain: Optional[str] = None,
                       ntlm_workstation: Optional[str] = None,
                       auth_type: str = const.AD_AUTH_TYPE_NTLM_V2,
                       transport_type: str = const.AD_TRANSPORT_STARTTLS,
                       ssl_verify_depth: int = const.DEFAULT_SSL_VERIFY_DEPTH,
                       ssl_verify_hostname: bool = True,
                       ssl_ca_certs: Optional[str] = None,
                       pagination: Optional[str] = PAGINATION_TYPE_CRITICAL,
                       page_size: int = DEFAULT_PAGE_SIZE,
                       max_result_size: Optional[int] = None,
                       timeout: int = 60,
                       call_id: Optional[int] = None):
        """
        * host: LDAP IP address we will perform actions against.
        * port: LDAP server port.
        * filter_text: Filter user search.
        * attributes: Retrieve only the listed attributes. If None,
          retrieve all attributes.
        * ntlm_{domain, workstation}: Windows authentication mechanism.
          Allows for bypassing primary bind if already within the domain.
        * auth_type: Bind method to use, e.g. NTLM, Plain, SSPI, etc.
        * transport_type: Method of transportation to use, e.g. Clear, TLS,
          LDAPS, etc.
        * timeout: If either establishing the connection or binding
          and searching take longer than this number of seconds
          the response will be an error.
        * page_size: As in RFC 2696.
        * max_result_size: If paging, stop requesting results after
          when at least this number of results have been received.
        """

        if attributes is None:
            attributes = set()

        try:
            attributes = set(k.lower() for k in attributes)
            if 'userpassword' in attributes:
                attributes.remove('userpassword')
        except Exception as e:
            log.err(e)
            raise drpc.CallBadArgError(['attributes'])

        page_size = parse_positive_int(page_size, 'page_size')
        if max_result_size is not None:
            max_result_size = parse_positive_int(max_result_size,
                                                 'max_result_size')

        if not (filter_text is None
                or isinstance(filter_text, six.string_types)):
            raise drpc.CallBadArgError(['filter_text'])

        bind_dn = self.service_account_username
        bind_pw = self.service_account_password

        log.msg((
            "Performing LDAP search: "
            "call_id={call_id} host={host} port={port} base_dn={base_dn} "
            "auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs} attributes={attributes}").format(
                call_id=call_id,
                host=host,
                port=port,
                base_dn=base_dn,
                auth_type=auth_type,
                transport_type=transport_type,
                ssl_verify_depth=ssl_verify_depth,
                ssl_verify_hostname=ssl_verify_hostname,
                attributes=attributes,
                ssl_ca_certs=ssl_ca_certs is not None))

        self._verify_ldap_config_args(bind_dn, bind_pw, auth_type,
                                      transport_type)

        cl, timeout_dc = yield self._get_client(
            host, port, transport_type, ssl_verify_depth, ssl_verify_hostname,
            ssl_ca_certs, timeout, self.debug, self.is_logging_insecure)

        try:
            try:
                yield cl.perform_bind(auth_type=auth_type,
                                      dn=bind_dn,
                                      username=bind_dn,
                                      password=bind_pw,
                                      domain=ntlm_domain,
                                      workstation=ntlm_workstation,
                                      permit_implicit=True)
            except Exception:
                if timeout_dc.active():
                    log.err(None, ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED)
                else:
                    log.err(None, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'bind',
                    })

            try:
                filter_bytes = None
                if filter_text is not None:
                    filter_bytes = filter_text.encode('utf-8')

                result = yield self._paged_search(
                    client=cl,
                    base_dn=base_dn,
                    filter_text=filter_bytes,
                    attributes=[a.encode('utf-8') for a in attributes],
                    pagination=pagination,
                    page_size=page_size,
                    max_result_size=max_result_size,
                )

                # any object that has a 'member' attribute is a potential
                # group object. if that attribute is an empty list then the
                # group is either empty or contains more values than the
                # server's configured maximum attribute length (usually 1500)
                # results.
                # try to check for members using range before giving up.
                try_ranged = [obj for obj in result if obj.get('member') == []]
                if try_ranged:
                    yield self.try_ranged_search(cl, base_dn, try_ranged)

            except Exception:
                if timeout_dc.active():
                    log.err(None, ldap_base.ERR_LDAP_SEARCH_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED)
                else:
                    log.err(None, ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        'during': 'search',
                    })
        finally:
            if timeout_dc.active():
                timeout_dc.cancel()
            try:
                cl.transport.abortConnection()
            except Exception:
                pass

        defer.returnValue({
            'results': result,
        })
コード例 #20
0
    def duo_preauth(self, request):
        """
        Get the user's duo auth status, and return it.  On API error,
        this builds an appropriate failure return.

        request -- The user's RADIUS request

        Returns the preauth JSON result.
        """
        try:
            if request.username in self.exempt_usernames:
                preauth_message = 'User exempted from 2FA'
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': preauth_message
                }
            else:
                preauth_res = yield self.client.preauth(
                    request.username, request.client_ip, self.failmode)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo preauth call failed')
            if duo_async.should_server_fail_open(self.failmode, e.fail_open):
                preauth_message = duo_async.get_fail_open_msg()
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': 'API call failed'
                }
            else:
                preauth_message = duo_async.FAILMODE_SECURE_MSG
                preauth_res = {
                    'result': duo_async.API_RESULT_DENY,
                    'status': 'API call failed'
                }
        else:
            if 'status' in preauth_res:
                preauth_message = 'Duo preauth returned \'%s\': \'%s\'' % (
                    preauth_res['result'], preauth_res['status'])
                self.log_request(request, preauth_message)
            else:
                preauth_message = 'Duo preauth returned \'%s\'' % preauth_res[
                    'result']
                self.log_request(request, preauth_message)

        if preauth_res['result'] == duo_async.API_RESULT_ALLOW:
            log.auth_standard(msg=preauth_message,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        elif preauth_res['result'] == duo_async.API_RESULT_DENY:
            log.auth_standard(msg=preauth_message,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        elif preauth_res['result'] == duo_async.API_RESULT_ENROLL:
            log.auth_standard(msg=log.AUTH_ENROLL_MSG,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)

        defer.returnValue(preauth_res)
コード例 #21
0
    def get_initial_response(self, request):
        # make sure username, password were provided
        if request.username is None:
            msg = 'No username provided'
            self.log_request(request, msg)
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage="Unknown",
                              status=log.AUTH_ERROR,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request, msg))

        self.log_request(request,
                         'login attempt for username %r' % request.username)

        if request.password is None:
            self.log_request(
                request, 'Only the PAP with a Shared Secret format is'
                ' supported. Is the system communicating with'
                ' the Authentication Proxy using CHAP or'
                ' MSCHAPv2 instead?')
            msg = 'No password provided'
            self.log_request(request, msg)
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_PRIMARY,
                              status=log.AUTH_ERROR,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request, msg))

        # perform primary authentication
        primary_res = yield self.primary_auth(request, request.password)
        if not primary_res.success:
            defer.returnValue(
                self.create_reject_packet(request, primary_res.msg))

        if request.username in self.exempt_usernames:
            msg = 'User exempted from 2FA'
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(
                self.create_accept_packet(
                    request, msg, radius_attrs=primary_res.radius_attrs))

        # get a txid from duo service
        try:
            init_res = yield self.client.proxy_init(request.username)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo proxy_init call failed')
            response_packet = self.response_for_api_error(
                request,
                primary_res,
                e,
                primary_res.radius_attrs,
            )
            defer.returnValue(response_packet)

        # Note: MUST NOT YIELD between calling _create_challenge_id
        # and calling create_challenge()
        challenge_id = self._create_challenge_id()

        # build script injection with the txid
        params = {
            'script_uri': self.script_uri,
            'proxy_txid': init_res['proxy_txid'],
            'api_host': self.client.host,
            'state': challenge_id
        }
        challenge_msg = self.script_inject % params
        if len(challenge_msg) > 253:
            raise ValueError(
                'response string is %d chars long, but cannot exceed 253 '
                'chars. If you specified a custom iframe_script_uri, you '
                'may need to shorten it by at least %d chars' %
                (len(challenge_msg), len(challenge_msg) - 253))

        self.log_request(request, 'Sending authentication challenge packet')
        state = {
            'primary_res': primary_res,
        }
        challenge_packet = self.create_challenge(request,
                                                 challenge_msg,
                                                 state=state,
                                                 challenge_id=challenge_id)
        defer.returnValue(challenge_packet)
コード例 #22
0
 def log_err(self, stuff, why):
     try:
         log.err(stuff, why)
     except Exception:
         pass
コード例 #23
0
    def perform_authentication(self,
                               servers,
                               username,
                               password,
                               service_account_username,
                               service_account_password,
                               base_dns=None,
                               ntlm_domain=None,
                               ntlm_workstation=None,
                               auth_type=const.AD_AUTH_TYPE_NTLM_V2,
                               transport_type=const.AD_TRANSPORT_STARTTLS,
                               ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH,
                               ssl_verify_hostname=True,
                               ssl_ca_certs=None,
                               timeout=60,
                               username_attributes=None,
                               call_id=None):
        """
        Authenticates against the provided servers in random order until
        a successful authentication occurs or the list of servers has been
        exhausted.

        See do_ldap_search for further argument documentation.

        Args:
            servers (list): List of dicts, each containing a 'hostname' and 'port'

        Returns:
            A dict containing:
                success (bool): True if the auth  was successful
                msg (str): The message associated with the result of the auth
                exceptions (list): Dict of CallError keyed on hostname
                    containing any errors that occurred during the
                    authentication process. Note that the presence of errors
                    does not indicate auth failure! For example, the exceptions
                    list will be non-empty if the first server could not be
                    reached but the second server serviced the auth.

        """
        # Assume user authentication never legitimately uses anonymous bind.
        if not password:
            defer.returnValue({
                'success': False,
                'msg': AUTH_FAILED,
                'exceptions': {},
            })

        exceptions = {}
        auth_successful = False
        user_full_dn = None
        # Try each server in random order
        for server in servers:
            host = server['hostname']
            port = server['port']
            try:
                auth_successful, user_full_dn = yield self.authenticate_against_server(
                    host=host,
                    port=port,
                    username=username,
                    password=password,
                    service_account_username=service_account_username,
                    service_account_password=service_account_password,
                    base_dns=base_dns,
                    ntlm_domain=ntlm_domain,
                    ntlm_workstation=ntlm_workstation,
                    auth_type=auth_type,
                    transport_type=transport_type,
                    ssl_verify_depth=ssl_verify_depth,
                    ssl_verify_hostname=ssl_verify_hostname,
                    ssl_ca_certs=ssl_ca_certs,
                    timeout=timeout,
                    username_attributes=username_attributes,
                    call_id=call_id,
                )
                # If we reach this line without an exception, the auth was
                # serviced successfully and we don't need to check any more
                # servers.
                if auth_successful:
                    log.sso_ldap(
                        msg="Successful authentication against server",
                        query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                        status=log.SSO_LDAP_QUERY_SUCCEEDED,
                        server=host,
                        port=port,
                        username=username,
                        proxy_key=self.proxy_key)
                else:
                    log.sso_ldap(msg="Failed authentication against server",
                                 query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                                 status=log.SSO_LDAP_QUERY_FAILED,
                                 server=host,
                                 port=port,
                                 username=username,
                                 proxy_key=self.proxy_key,
                                 reason='Invalid credentials')
                break
            except drpc.CallError as e:
                # serialize the errors in a similar format to what drpc does
                log.sso_ldap(msg="Failed authentication against server",
                             query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                             status=log.SSO_LDAP_QUERY_FAILED,
                             server=host,
                             port=port,
                             username=username,
                             proxy_key=self.proxy_key,
                             reason=e.error)
                exceptions[host] = {
                    'error': e.error,
                    'error_args': e.error_args,
                }

        if self.connection_failures_for_all_servers(servers, exceptions):
            raise drpc.CallError(ERR_LDAP_CANNOT_SERVICE_REQUEST, {
                'error':
                str('Failed to communicate with any domain controllers')
            })

        if self.too_many_users_failure_on_all_servers(servers, exceptions):
            error = drpc.CallError(
                ERR_LDAP_NON_UNIQUE_USERNAME_ATTR, {
                    'error':
                    'Each domain controller returned more than one user for the provided username attributes.'
                })
            log.err(
                error, '{msg} {username}'.format(
                    msg=ERR_LDAP_NON_UNIQUE_USERNAME_ATTR, username=username))
            raise error

        defer.returnValue({
            'success': auth_successful,
            'user_full_dn': user_full_dn,
            'msg': AUTH_SUCCEEDED if auth_successful else AUTH_FAILED,
            'exceptions': exceptions,
        })