Ejemplo n.º 1
0
def is_valid_ip(ip):
    """Checks the ip to see if it's valid in any of the 4 formats we allow
    Args:
        ip (str): Formats are single, range, cidr, and IP/netmask
    Returns:
        bool: True/False
    """
    if ip is None:
        log.msg("Invalid ip. Ip was None")
        return False
    elif "-" in ip:
        try:
            ip_range(ip)
        except Exception as e:
            log.msg("Invalid ip range: {0}. Exception: {1}".format(ip, e))
            return False
    elif "/" in ip:
        try:
            ip_cidr(ip)
        except Exception as e:
            log.msg("Invalid cidr ip: {0}. Exception: {1}".format(ip, e))
            return False
    else:
        if not is_valid_single_ip(ip):
            log.msg("Invalid single ip: {0}.".format(ip))
            return False

    log.msg("Valid ip check passed for: {0}.".format(ip))
    return True
Ejemplo n.º 2
0
    def __init__(self, config):
        super(Module, self).__init__()

        log.msg("CloudSSO Connector Module")

        log.config(
            config,
            (lambda k: k in (
                "service_account_password",
                "service_account_password_protected",
                "encryption_skey",
                "encryption_skey_protected",
                "signing_skey",
                "signing_skey_protected",
            )),
        )
        self.debug = config.get_bool("debug", False)

        self.duo_creds = DuoCreds(
            identity=config[const.DRPC_PROXY_KEY_IDENTIFIER],
            secret=config[const.DRPC_SIGNING_SKEY_IDENTIFIER].encode(),
        )
        self.host = config[const.DRPC_API_HOST_IDENTIFIER]
        self.encryption_skey = config[const.DRPC_ENCRYPTION_SKEY_IDENTIFIER]

        self.duo_client = self.make_duo_client(
            duo_creds=self.duo_creds,
            host=self.host,
            client_type=duo_async.CloudSSODuoClient,
        )

        self.identities[self.duo_creds.get_identity()] = self.duo_creds

        self.drpc_path = "/drpc/v1/join"
Ejemplo n.º 3
0
    def __init__(self, config, _primary_ator=None):
        super(Module, self).__init__()

        log.msg("Cloud Connector Module Configuration:")
        log.config(
            config,
            (
                lambda k: k
                in (
                    "skey",
                    "skey_protected",
                    "service_account_password",
                    "service_account_password_protected",
                )
            ),
        )
        self.debug = config.get_bool("debug", False)
        self.duo_creds = DuoCreds(
            config.get("ikey"),
            config.get_protected_str("skey_protected", "skey").encode(),
        )

        host = config.get_str("api_host", "api.duosecurity.com")
        port = config.get_int("api_port", 443)
        self.duo_client = self.make_duo_client(self.duo_creds, host, port=port)

        self.identities[self.duo_creds.get_identity()] = self.duo_creds

        self.drpc_path = "/auth/v2/proxy_join"
Ejemplo n.º 4
0
    def authenticate(self,
                     username,
                     password,
                     client_ip,
                     pass_through_attrs=None):
        if pass_through_attrs is None:
            pass_through_attrs = {}
        id = yield self.id_list.request()
        request_packet = packet.AuthPacket(code=packet.AccessRequest,
                                           id=id,
                                           secret=self.secret.encode(),
                                           dict=base.radius_dictionary())
        request_packet['NAS-IP-Address'] = self.nas_ip
        add_packet_attributes(packet=request_packet, attrs=pass_through_attrs)
        request = ClientRequest(request_packet, self.pw_codec)
        request.username = username
        if password is not None:
            request.password = password
        if client_ip:
            request.client_ip = client_ip

        log.msg('Sending request for user %r to %r with id %r' %
                (username, self.addrs[0], id))

        # Send request; wait for response
        self.requests[id] = request
        self._send_request(request)
        response_packet = yield request.deferred
        defer.returnValue(response_packet)
Ejemplo n.º 5
0
    def __init__(self, config, primary_ator, server_section_name):
        log.msg(
            'RADIUS PEAP/EAP-GTC Automatic Factor Server Module Configuration:'
        )
        log.config(
            config, lambda x: x.startswith('radius_secret') or x in
            ('skey', 'skey_protected'))

        self.protocol = DuoEAPRadiusServer(
            secrets=parse_radius_secrets(config),
            primary_ator=primary_ator,
            duo_client=self.make_duo_client(config),
            failmode=config.get_enum('failmode',
                                     duo_async.FAILMODES,
                                     duo_async.FAILMODE_SAFE,
                                     transform=str.lower),
            factors=util.parse_factor_list(config.get_str('factors', 'auto')),
            delim=config.get_str('delimiter', ','),
            delimited_password_length=config.get_int(
                'delimited_password_length', 0),
            allow_concat=config.get_bool('allow_concat', False),
            debug=config.get_bool('debug', False),
            pass_through_attr_names=config.get_str('pass_through_attr_names',
                                                   ''),
            prompt=config.get_str('prompt', 'Enter your password: '******'pkey'),
            cert_file=config.get_str('certs'),
            cipher_list=config.get_str('cipher_list', ''),
            minimum_tls_version=config.get_str('minimum_tls_version', ''),
            pw_codec=config.get_str('pw_codec', 'utf-8'),
            client_ip_attr=parse_client_ip_attribute(config),
            server_section_name=server_section_name,
            pass_through_all=config.get_bool('pass_through_all', False),
        )
Ejemplo n.º 6
0
    def do_ldap_authentication(
        self,
        servers,
        username,
        password,
        rikey,
        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,
    ):
        creds = self.get_creds_for_ldap_idp(rikey, auth_type)
        service_account_username = creds.username
        service_account_password = creds.password

        self._verify_ldap_config_args(
            service_account_username,
            service_account_password,
            auth_type,
            transport_type,
        )

        # Decrypt the end user password using our symmetric key
        try:
            password = self.decrypt_password(password)
        except InvalidToken as e:
            msg = "Unable to decrypt the password for user: {}. Please check that your CloudSSO enrollment code is correct. Failing the authentication".format(
                username)
            log.msg(msg)
            raise drpc.CallError(ERR_LDAP_PW_DECRYPT_FAILED, {
                "error": str(e),
            })

        res = yield self.perform_authentication(
            servers,
            username,
            password,
            service_account_username,
            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,
        )
        defer.returnValue(res)
Ejemplo n.º 7
0
    def __init__(self, config, primary_client, server_section_name):
        log.msg("RADIUS IFrame Server Module Configuration:")
        log.config(
            config,
            lambda x: x.startswith("radius_secret") or x in
            ("skey", "skey_protected"),
        )

        failmode = config.get_enum(
            "failmode",
            duo_async.FAILMODES,
            duo_async.FAILMODE_SAFE,
            transform=str.lower,
        )
        secrets = parse_radius_secrets(config)
        type = config.get_enum("type", JS_TYPES)

        if type != JS_TYPE_CITRIX:
            api_timeout = 15
        else:
            # citrix devices don't retransmit correctly,
            # and can't do timeouts > 10 seconds
            api_timeout = 8
        duo_client = self.make_duo_client(config, default_timeout=api_timeout)

        # script injection snippet, js file
        script_file_default = SCRIPT_FILES[type]
        if duo_client.port == 443:
            script_uri_default = "https://%s/frame/hosted/%s" % (
                duo_client.host,
                script_file_default,
            )
        else:
            script_uri_default = "https://%s:%d/frame/hosted/%s" % (
                duo_client.host,
                duo_client.port,
                script_file_default,
            )
        script_uri = config.get_str("iframe_script_uri", script_uri_default)
        script_inject_default = SCRIPT_INJECT[type]
        script_inject = config.get_str("script_inject", script_inject_default)

        self.protocol = DuoIFrameRadiusServer(
            type,
            script_uri,
            script_inject,
            failmode,
            duo_client=duo_client,
            exempt_usernames=parse_exempt_usernames(config),
            secrets=secrets,
            primary_ator=primary_client,
            pass_through_attr_names=config.get_str("pass_through_attr_names",
                                                   ""),
            pass_through_all=config.get_bool("pass_through_all", False),
            pw_codec=config.get_str("pw_codec", "utf-8"),
            client_ip_attr=parse_client_ip_attribute(config),
            server_section_name=server_section_name,
            server_section_ikey=config.get_str("ikey", ""),
        )
Ejemplo n.º 8
0
 def restart_looping_call(self, reason):
     if self._check_connection_lc.running:
         # restart looping call which should trigger a reconnection
         log.msg("Connection lost to SSO: {0}".format(reason))
         self._check_connection_lc.stop()
         yield self._check_connection()
         delay = random.SystemRandom().uniform(1, 60)
         self.reactor.callLater(delay, self._check_connection_lc.start,
                                self.reconnect_interval, True)
Ejemplo n.º 9
0
 def log_msg(self, summary, **kwargs):
     try:
         kwargs = dict(**kwargs)
         log.msg(
             "Summary: {summary}. Extra data: {extra_data}",
             summary=summary,
             extra_data=kwargs,
         )
     except Exception:
         pass
Ejemplo n.º 10
0
 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))
Ejemplo n.º 11
0
    def __init__(self, config):
        log.msg("HTTP Proxy Module Configuration:")
        log.config(config)
        self.port = config.get_int("port", const.DEFAULT_HTTP_PORT)
        host = config.get_str("api_host")
        self.interface = config.get_str("interface", "")
        client_ips = get_allowed_ip_networks(config)

        self.factory = twisted.web.http.HTTPFactory()
        self.factory.protocol = functools.partial(
            ConnectProxy, host=host, client_ips=client_ips
        )
        self.listener = None
        self._bind_if_necessary()
Ejemplo n.º 12
0
    def handle_datagram_received(self, datagram, host, port):
        if self.debug:
            log.msg("Packet dump - received from %s:" % host)
            log.msg(repr(datagram))

        server = self.filter(host)
        log.msg("Sending request from %s to %s" %
                (host, self.server_names[server]))
        request = yield server.protocol._handle_request(datagram, (host, port))
        if request.response:
            self.transport.write(request.response, request.source)
            if self.debug:
                log.msg("Packet dump - sent to %s:" % (request.source[0]))
                log.msg(repr(request.response))
Ejemplo n.º 13
0
def is_writable(path: str) -> bool:
    """
    Does not create a file but checks to see if one exists and if it's writable
    args:
        path (str): path to the file to check
    """

    try:
        if os.path.isfile(path):
            with open(path, "a"):
                return True
        else:
            return False
    except Exception as e:
        log.msg(e)
        return False
Ejemplo n.º 14
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,
        }
Ejemplo n.º 15
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.failure(ERR_COUNTER_FAILURE)
            raise drpc.CallError(ERR_COUNTER_FAILURE, {
                "during": "get_proxy_counter",
                "error": str(e)
            })

        return {
            "counter": counter,
        }
Ejemplo n.º 16
0
def _find_usable_path(storage_paths: List[str]) -> str:
    """
    Returns the file path of the first writable storage location. The file is guarenteed
    to exist and be writable if it is returned from this function. If an exception is thrown
    there may be no storage location available
    """

    for path in storage_paths:
        if is_writable(path):
            return path

    for path in storage_paths:
        try:
            make_storage(path)
            return path
        except Exception as e:
            log.msg(e)

    raise SecretStorageFileError(
        "No writable storage locations found. Make sure you have the privilege to write."
    )
Ejemplo n.º 17
0
    def radius_proxy(self, orig_request):
        """Send the given RADIUS packet and return the response received.
        """
        # Create a new request packet with a different ID but the same
        # type and attributes.
        request_packet = copy.deepcopy(orig_request.packet)
        request_packet.id = yield self.id_list.request()
        request_packet.secret = self.secret.encode()
        request = ClientRequest(request_packet, self.pw_codec)

        # Send onward; wait for reply.
        log.msg('Sending proxied request for id %r to %r with id %r' %
                (orig_request.id, self.addrs[0], request_packet.id))
        self.requests[request_packet.id] = request
        self._send_request(request)

        # Make response match principal's expectations.
        response_packet = yield request.deferred
        response_packet.id = orig_request.id
        response_packet.secret = orig_request.secret
        defer.returnValue(response_packet)
Ejemplo n.º 18
0
    def handle_response(self, datagram, source):
        response_packet = packet.AuthPacket(packet=datagram,
                                            dict=base.radius_dictionary())
        response_packet.source = source

        # make sure it's from the correct host (or properly ip-spoofed :)
        if (source[0], int(source[1])) not in self.addrs:
            raise packet.PacketError(
                'response packet from unknown address: %s:%s' % source, )

        # look up id
        try:
            request = self.requests[response_packet.id]
        except KeyError:
            raise packet.PacketError('unrecognized id in response packet: %s' %
                                     response_packet.id)

        # verify reply authentication
        if not request.packet.VerifyReply(response_packet, datagram):
            raise packet.PacketError(
                'response packet has invalid authenticator')
        response_packet.secret = self.secret.encode()
        # Validate Message-Authenticator, if any
        if response_packet.message_authenticator:
            if not response_packet.verify_message_authenticator(
                    original_authenticator=request.packet.authenticator):
                raise packet.PacketError(
                    'Invalid Message-Authenticator from {0}'.format(source[0]))
        response_packet.authenticator = request.packet.authenticator

        # clear request state
        self._request_done(request)

        log.msg(
            'Got response for id %r from %r; code %r' %
            (response_packet.id, response_packet.source, response_packet.code))

        # callback
        request.deferred.callback(response_packet)
Ejemplo n.º 19
0
    def __init__(self, config, _primary_ator=None):
        super(Module, self).__init__()

        log.msg('Cloud Connector Module Configuration:')
        log.config(config, (lambda k: k in (
            'skey',
            'skey_protected',
            'service_account_password',
            'service_account_password_protected',
        )))
        self.debug = config.get_bool('debug', False)
        self.duo_creds = DuoCreds(
            config.get('ikey'),
            config.get_protected_str('skey_protected', 'skey').encode(),
        )

        host = config.get_str('api_host', 'api.duosecurity.com')
        port = config.get_int('api_port', 443)
        self.duo_client = self.make_duo_client(self.duo_creds, host, port=port)

        self.identities[self.duo_creds.get_identity()] = self.duo_creds

        self.drpc_path = '/auth/v2/proxy_join'
Ejemplo n.º 20
0
    def proxy_join(self, server_module, drpc_path):
        """
        Connects a proxy to the Duo service and establishes a long lived connection.
        Args:
            server_module (module): A module that implements the methods from the DRPC plugin interface
            drpc_path (str): url path to the join endpoint
        """
        try:
            yield self.ping()  # Update self.time_offset. Needed for sigV2+.

            # Open a request normally (using configured HTTPS proxy,
            # etc.), but upgrade protocol to DRPC instead of calling API.
            method = "POST"
            _status, rpc_protocol, _headers = yield self._request(
                method=method,
                # URL used only for scheme, host, and port.
                url=("https://{0}:{1:d}/".format(self.host, self.port)),
                body="",  # overridden in protocol.
                headers={},  # overridden in protocol to sign with v2.
                timeout=self.timeout_default,
                protocol_class=functools.partial(
                    self.http_client,
                    duo_client=self,
                    server_module=server_module,
                    drpc_path=drpc_path,
                ),
                disconnect=False,
            )
            defer.returnValue(rpc_protocol)
        except ConnectionRefusedError as e:
            log.msg(
                "Encountered exception while attempting to join. Error: {}".format(e)
            )
            # Translate exception into DuoAPIError so that the interface is the same
            # for all callers.
            raise DuoAPIError(str(e))
    def enable_primary_only(minutes):
        """Enable primary only mode by adding a callback that stops
        the Twisted reactor after a number of minutes

        Args:
            minutes (int): The number of minutes to run in primary only mode before the Authentication Proxy is stopped
        """
        if minutes > 240:
            minutes = 240
        try:

            def primary_only_callback():
                log.msg("Primary only mode has expired. Stopping proxy")
                reactor.stop()

            reactor.callLater(minutes * 60, primary_only_callback)
            log.msg(
                "Running in PRIMARY ONLY mode. Stopping in {0} minutes".format(minutes)
            )
            PrimaryOnlyManager.__instance._PRIMARY_ONLY_ENABLED = True
        except Exception as e:
            log.msg(e)
            print(e)
            sys.exit(2)
Ejemplo n.º 22
0
 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)
Ejemplo n.º 23
0
 def perform_join(self):
     try:
         server_protocol = yield self.duo_client.proxy_join(
             server_module=self, drpc_path=self.drpc_path)
         defer.returnValue(server_protocol)
     except duo_async.DuoAPIRotateRequiredError:
         server_protocol = yield self.rotate_and_rejoin()
         defer.returnValue(server_protocol)
     except duo_async.DuoAPIProxyNotFoundError:
         log.msg("Error connecting to service: Auth Proxy not found")
         self.stopService()
     except duo_async.DuoAPIBadSignatureError:
         log.msg(
             "There was a problem with the Duo API credentials.  If this persists, you may need to reconnect your Authentication Proxy."
         )
         self.stopService()
     except Exception as e:
         log.msg("Exception e: {}".format(e))
         raise e
Ejemplo n.º 24
0
    def _send_request(self, request, addrs=None, retry=0):
        if not addrs:
            # cycle through configured addresses
            addrs = self.addrs
        if retry > self.retries:
            log.msg('Request timeout for (outgoing) id %r to %r' %
                    (request.id, addrs[0]))
            self._request_done(request)
            request.deferred.errback(
                AuthError('RADIUS auth request timed out'))
            return

        # send request to first address
        raw = request.packet.RequestPacket()
        self.transport.write(raw, addrs[0])
        if self.debug:
            log.msg("Packet dump - sent to %s:" % (addrs[0][0]))
            log.msg(repr(raw))

        # set retry timer to re-send with next address
        request.retry_forward_dc = reactor.callLater(self.retry_wait,
                                                     self._send_request,
                                                     request, addrs[1:],
                                                     retry + 1)
Ejemplo n.º 25
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.failure(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.failure(ERR_LDAP_HOSTNAME_RESOLUTION_FAILED)
                raise drpc_exceptions.CallError(
                    ERR_LDAP_HOSTNAME_RESOLUTION_FAILED, {
                        "error": str(e),
                    })
            else:
                log.failure(ERR_LDAP_CONNECTION_FAILED)
                raise drpc_exceptions.CallError(ERR_LDAP_CONNECTION_FAILED, {
                    "error": str(e),
                })
        except Exception as e:
            log.failure(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))
Ejemplo n.º 26
0
 def timeout_cb():
     log.msg("LDAP operation timed out")
     client.transport.abortConnection()
Ejemplo n.º 27
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:
            log.failure("Error parsing provided attributes dict")
            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}",
            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.failure(ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED)
                else:
                    log.failure(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 ldap.client.ConnectionNotProperlyBoundError:
                log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED_BAD_BIND)
                raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED_BAD_BIND)
            except Exception:
                if timeout_dc.active():
                    log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED)
                else:
                    log.failure(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,
        })
Ejemplo n.º 28
0
    def add_message(self, msg: bytes):
        """Add a message to the EAP session.
        Returns False on success, returns errback on error, or gtc_received
        on receiving a token card response.
        """
        code, identifier, length, eap_type = struct.unpack('>BBHB', msg[:5])
        if not self.inner and self.id != 0 and identifier != self.id:
            # Silently discard response if ID does not match
            log.msg("Response ID %s does not match request ID %s" %
                    (identifier, self.id))
            defer.returnValue(False)
        if len(msg) != length:
            message = 'Malformed EAP packet: length ({0}) does not match actual length ({1})'.format(
                length, len(msg))
            log.auth_standard(
                msg=message,
                username=self.current_request.username,
                auth_stage=log.AUTH_UNKNOWN,
                status=log.AUTH_ERROR,
                client_ip=self.current_request.client_ip,
                server_section=self.server.server_section_name,
                server_section_ikey=self.server.server_section_ikey)
            error = yield self.errback(self, message)
            defer.returnValue(error)

        if code == EAP_CODE_REQUEST:
            # EAP request, we are talking to RADIUS, this should not happen
            message = "EAP request received."
            log.auth_standard(
                msg=message,
                username=self.current_request.username,
                auth_stage=log.AUTH_UNKNOWN,
                status=log.AUTH_ERROR,
                client_ip=self.current_request.client_ip,
                server_section=self.server.server_section_name,
                server_section_ikey=self.server.server_section_ikey)
            error = yield self.errback(self, message)
            defer.returnValue(error)
        elif code == EAP_CODE_RESPONSE:
            # EAP response, we are talking to NetMotion
            self.id += 1
            if eap_type == EAP_TYPE_IDENTITY:  # Identity
                self.state = EAP_SESSION_REQUEST_GTC
            elif eap_type == EAP_TYPE_NAK:  # Legacy Nak
                # Data element (element 5) will indicate the desired type
                desired_type = msg[5]
                if desired_type == EAP_TYPE_PEAP:  # PEAP request
                    self.state = EAP_SESSION_PEAP_REQUEST
                else:
                    message = "Unknown Nak request %s." % desired_type
                    log.auth_standard(
                        msg=message,
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = yield self.errback(self, message)
                    defer.returnValue(error)
            elif eap_type == EAP_TYPE_TLV:
                if msg[5:7] == MS_RESULT_HEADER and len(
                        msg) == 11:  # MS-Result-TLV
                    if msg[-1] == 1 and self.state == EAP_SESSION_ACCEPT:  # Success
                        self.state = EAP_SESSION_SUCCESS
                    else:  # Failure
                        self.state = EAP_SESSION_FAILURE
                else:
                    # No support for cryptobinding or SoH TLV
                    # At least exit with some relevant info
                    message = "Unsupported TLV: {0!r}.".format(msg)
                    log.auth_standard(
                        msg=message,
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = yield self.errback(self, message)
                    defer.returnValue(error)

            elif eap_type == EAP_TYPE_PEAP:  # PEAP
                tls_flags = msg[5]
                if tls_flags & 0x80:  # Length included
                    self.tls_len = struct.unpack('>I', msg[6:10])
                    tls_msg: bytes = msg[10:]
                else:
                    tls_msg = msg[6:]

                if self.state == EAP_SESSION_PEAP_MORE:
                    self.tls_msg += tls_msg
                else:
                    self.tls_msg = tls_msg

                if tls_flags & 0x40:  # More fragments
                    self.state = EAP_SESSION_PEAP_MORE
                else:
                    self.state = EAP_SESSION_PEAP
                    if self.tls_msg:
                        if hasattr(self, 'ssl_con'):
                            add_response = yield self.add_incoming(tls_msg)
                            if add_response:
                                # Errback or gtc callback occured, pass it through
                                defer.returnValue(add_response)
                        else:
                            # We reset the EAP session but the peer is trying to continue
                            self.state = EAP_SESSION_PEAP_REQUEST
                    else:
                        # Empty PEAP message, probably handshake over
                        self.state = EAP_SESSION_PEAP_ID
        defer.returnValue(False)
 def log_msg(self, summary, **kwargs):
     try:
         kwargs = dict(**kwargs)
         log.msg(summary, kwargs)
     except Exception:
         pass
Ejemplo n.º 30
0
 def log_request(self, request, msg):
     log.msg("({}, {}, {}): {}".format(request.source, request.username,
                                       request.id, msg))