def test_map(self):

        validator = validators.Map(a=1, b=2, c=3)
        self.assertEqual(validator.__call__('a'), 1)
        self.assertEqual(validator.__call__('b'), 2)
        self.assertEqual(validator.__call__('c'), 3)
        self.assertRaises(ValueError, validator.__call__, 'd')

        self.assertEqual(validator.__call__(None), None)
Example #2
0
class TestSearchCommand(SearchCommand):

    boolean = Option(
        doc='''
        **Syntax:** **boolean=***<value>*
        **Description:** A boolean value''',
        validate=validators.Boolean())

    required_boolean = Option(
        doc='''
        **Syntax:** **boolean=***<value>*
        **Description:** A boolean value''',
        require=True, validate=validators.Boolean())

    aliased_required_boolean = Option(
        doc='''
        **Syntax:** **boolean=***<value>*
        **Description:** A boolean value''',
        name='foo', require=True, validate=validators.Boolean())

    code = Option(
        doc='''
        **Syntax:** **code=***<value>*
        **Description:** A Python expression, if mode == "eval", or statement, if mode == "exec"''',
        validate=validators.Code())

    required_code = Option(
        doc='''
        **Syntax:** **code=***<value>*
        **Description:** A Python expression, if mode == "eval", or statement, if mode == "exec"''',
        require=True, validate=validators.Code())

    duration = Option(
        doc='''
        **Syntax:** **duration=***<value>*
        **Description:** A length of time''',
        validate=validators.Duration())

    required_duration = Option(
        doc='''
        **Syntax:** **duration=***<value>*
        **Description:** A length of time''',
        require=True, validate=validators.Duration())

    fieldname = Option(
        doc='''
        **Syntax:** **fieldname=***<value>*
        **Description:** Name of a field''',
        validate=validators.Fieldname())

    required_fieldname = Option(
        doc='''
        **Syntax:** **fieldname=***<value>*
        **Description:** Name of a field''',
        require=True, validate=validators.Fieldname())

    file = Option(
        doc='''
        **Syntax:** **file=***<value>*
        **Description:** Name of a file''',
        validate=validators.File())

    required_file = Option(
        doc='''
        **Syntax:** **file=***<value>*
        **Description:** Name of a file''',
        require=True, validate=validators.File())

    integer = Option(
        doc='''
        **Syntax:** **integer=***<value>*
        **Description:** An integer value''',
        validate=validators.Integer())

    required_integer = Option(
        doc='''
        **Syntax:** **integer=***<value>*
        **Description:** An integer value''',
        require=True, validate=validators.Integer())

    map = Option(
        doc='''
        **Syntax:** **map=***<value>*
        **Description:** A mapping from one value to another''',
        validate=validators.Map(foo=1, bar=2, test=3))

    required_map = Option(
        doc='''
        **Syntax:** **map=***<value>*
        **Description:** A mapping from one value to another''',
        require=True, validate=validators.Map(foo=1, bar=2, test=3))

    match = Option(
        doc='''
        **Syntax:** **match=***<value>*
        **Description:** A value that matches a regular expression pattern''',
        validate=validators.Match('social security number', r'\d{3}-\d{2}-\d{4}'))

    required_match = Option(
        doc='''
        **Syntax:** **required_match=***<value>*
        **Description:** A value that matches a regular expression pattern''',
        require=True, validate=validators.Match('social security number', r'\d{3}-\d{2}-\d{4}'))

    optionname = Option(
        doc='''
        **Syntax:** **optionname=***<value>*
        **Description:** The name of an option (used internally)''',
        validate=validators.OptionName())

    required_optionname = Option(
        doc='''
        **Syntax:** **optionname=***<value>*
        **Description:** The name of an option (used internally)''',
        require=True, validate=validators.OptionName())

    regularexpression = Option(
        doc='''
        **Syntax:** **regularexpression=***<value>*
        **Description:** Regular expression pattern to match''',
        validate=validators.RegularExpression())

    required_regularexpression = Option(
        doc='''
        **Syntax:** **regularexpression=***<value>*
        **Description:** Regular expression pattern to match''',
        require=True, validate=validators.RegularExpression())

    set = Option(
        doc='''
        **Syntax:** **set=***<value>*
        **Description:** A member of a set''',
        validate=validators.Set('foo', 'bar', 'test'))

    required_set = Option(
        doc='''
        **Syntax:** **set=***<value>*
        **Description:** A member of a set''',
        require=True, validate=validators.Set('foo', 'bar', 'test'))

    class ConfigurationSettings(SearchCommand.ConfigurationSettings):
        @classmethod
        def fix_up(cls, command_class):
            pass
Example #3
0
class LdapFilterCommand(StreamingCommand):
    """  Filters and augments events with information from Active Directory.

    This command follows a search or similar command in the pipeline so that you can feed it events:

        .. code-block:: text
        eventtype=msad-user-logons
        | ldapfilter domain=$dest_nt_domain$ search="(objectClass=$src_user$)" attrs="telephoneNumber,displayName"

    """
    # region Command options

    search = Option(
        doc=''' Specifies an RFC 2254 compliant search string.
        ''',
        require=True)

    domain = Option(
        doc=''' Specifies the Active Directory domain to search.
        ''',
        default='default')

    attrs = Option(
        doc=''' Specifies a comma separated list of attributes to return as fields.
        **Default:** '*', specifying that all attributes should be returned as fields.
        ''',
        default=[ldap3.ALL_ATTRIBUTES], validate=validators.List())

    basedn = Option(
        doc=''' Specifies the starting point for the search.
        Default: The value of `basedn` as specified in the configuration stanza for `domain`.
        ''')

    scope = Option(
        doc=''' Specifies the scope of the search to be one of `base`, `one`, or `sub`.
        **Default:** sub.
        ''',
        default='sub', validate=validators.Map(
            base=ldap3.SEARCH_SCOPE_BASE_OBJECT,
            one=ldap3.SEARCH_SCOPE_SINGLE_LEVEL,
            sub=ldap3.SEARCH_SCOPE_WHOLE_SUBTREE
        ))

    decode = Option(
        doc=''' True, if Active Directory formatting rules should be applied to attribute types.
        **Default:** The value of decode as specified in the configuration stanza for domain.
        ''',
        default=True, validate=validators.Boolean())

    limit = Option(
        doc=''' Specifies an upper bound on the number of matching entries returned by the search.
        **Default:** 0, specifying that there is no upper bound on the number of entries returned by the search.
        ''',
        default=0, validate=validators.Integer(minimum=0))

    debug = Option(
        doc=''' True, if the logging_level should be set to DEBUG; otherwise False.
        **Default:** The current value of logging_level.
        ''',
        default=False, validate=validators.Boolean())

    # endregion

    # region Command implementation

    def stream(self, records):
        """
        :param records: An iterable stream of events from the command pipeline.
        :return: `None`.

        """
        configuration = app.Configuration(self, is_expanded=True)
        expanded_domain = app.ExpandedString(self.domain)
        expanded_search_filter = app.ExpandedString(self.search, converter=app.escape_assertion_value)

        try:
            with configuration.open_connection_pool(self.attrs) as connection_pool:

                for record in records:

                    domain = expanded_domain.get_value(record)

                    if domain is None:
                        continue

                    search_filter = expanded_search_filter.get_value(record)

                    if len(search_filter) == 0:
                        continue

                    connection = connection_pool.select(domain)

                    if not connection:
                        self.logger.warning('search="%s": domain="%s" is not configured', search_filter, domain)
                        continue

                    search_base = app.ExpandedString(self.basedn).get_value(record)  # must be instantiated here

                    entry_generator = connection.extend.standard.paged_search(
                        search_base=search_base, search_filter=search_filter, search_scope=self.scope,
                        attributes=connection_pool.attributes, paged_size=configuration.paged_size)

                    for entry in entry_generator:
                        attributes = app.get_attributes(self, entry)
                        if not attributes:
                            continue
                        for name in connection_pool.attributes:
                            record[name] = attributes.get(name, '')
                        yield record

                    pass

        except ldap3.LDAPException as error:
            self.error_exit(error, app.get_ldap_error_message(error, configuration))

        return
Example #4
0
class LdapSearchCommand(GeneratingCommand):
    """ Retrieves results from the specified search in a configured domain and generates events.

    This command must be placed at the beginning of a search pipeline:

        .. code-block:: text
        | ldapsearch domain=splunk.com search="(objectCategory=User)" attrs="distinguishedName"

    """

    search = Option(doc=''' Specifies an RFC 2254 compliant search string.
        ''',
                    require=True)

    attrs = Option(
        doc=
        ''' Specifies a comma separated list of attributes to be returned as fields.
        **Default:** '*', specifying that all attributes should be returned as fields.
        ''',
        default=[ldap3.ALL_ATTRIBUTES],
        validate=validators.List())

    basedn = Option(doc=''' Specifies the starting point for the search.
        Default: The value of basedn as specified in the configuration stanza for domain.
        ''')

    domain = Option(
        doc=
        ''' Specifies the LDAP or Active Directory domain directory to search.
        ''',
        default='default')

    scope = Option(
        doc=''' Specifies the scope of the search to be one of base, one, or sub.
        **Default:** sub.
        ''',
        default='sub',
        validate=validators.Map(base=ldap3.SEARCH_SCOPE_BASE_OBJECT,
                                one=ldap3.SEARCH_SCOPE_SINGLE_LEVEL,
                                sub=ldap3.SEARCH_SCOPE_WHOLE_SUBTREE))

    debug = Option(
        doc=
        ''' True, if the logging_level should be set to DEBUG; otherwise False.
        **Default:** The current value of logging_level.
        ''',
        default=False,
        validate=validators.Boolean())

    decode = Option(
        doc=
        ''' True, if Active Directory formatting rules should be applied to attribute types.
        **Default:** The value of decode as specified in the configuration stanza for domain.
        ''',
        default=True,
        validate=validators.Boolean())

    limit = Option(
        doc=
        ''' Specifies an upper bound on the number of matching entries returned by the search.
        **Default:** 0, specifying that there is no upper bound on the number of entries returned by the search.
        ''',
        default=0,
        validate=validators.Integer(minimum=0))

    def generate(self):
        """
        :return: `None`.

        """
        configuration = app.Configuration(self)

        try:
            with ldap3.Connection(
                    configuration.server,
                    read_only=True,
                    raise_exceptions=True,
                    user=configuration.credentials.username,
                    password=configuration.credentials.password) as connection:

                attribute_names = app.get_normalized_attribute_names(
                    self.attrs, connection, configuration)

                entry_generator = connection.extend.standard.paged_search(
                    search_base=self.basedn,
                    search_filter=self.search,
                    search_scope=self.scope,
                    attributes=self.attrs,
                    paged_size=configuration.paged_size)

                encoder = JSONEncoder(ensure_ascii=False,
                                      separators=(',', ':'))
                time_stamp = time()
                serial_number = 0

                for entry in entry_generator:
                    attributes = app.get_attributes(self, entry)
                    if attributes:
                        dn = entry['dn']
                        yield LdapSearchCommand._record(
                            serial_number, time_stamp, connection.server.host,
                            dn, attributes, attribute_names, encoder)
                        serial_number += 1
                    if self.limit and serial_number == self.limit:
                        break
                    pass

                pass

        except ldap3.LDAPException as error:
            self.error_exit(error,
                            app.get_ldap_error_message(error, configuration))

        return

    @staticmethod
    def _record(serial_number, time_stamp, host, dn, attributes,
                attribute_names, encoder):

        # Base-64 encode binary values (they're stored as str values--byte strings--not unicode values)

        for name, value in attributes.iteritems():
            if isinstance(value, str):
                attributes[name] = b64encode(value)
            elif isinstance(value, list):
                for i in range(len(value)):
                    if isinstance(value[i], str):
                        value[i] = b64encode(value[i])

        raw = encoder.encode(attributes)

        # Formulate record

        if serial_number > 0:
            attributes['_serial'] = serial_number
            attributes['_time'] = time_stamp
            attributes['_raw'] = raw
            attributes['host'] = host
            attributes['dn'] = dn
            return attributes

        record = OrderedDict(
            chain((('_serial', serial_number), ('_time', time_stamp),
                   ('_raw', raw), ('host', host), ('dn', dn)),
                  imap(lambda name: (name, attributes.get(name, '')),
                       attribute_names)))

        return record