You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
5.8 KiB
161 lines
5.8 KiB
#!/usr/bin/env python
|
|
# coding=utf-8
|
|
#
|
|
# Copyright (C) 2009-2021 Splunk Inc. All Rights Reserved.
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
import default
|
|
|
|
from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators
|
|
from collections import OrderedDict
|
|
from itertools import chain
|
|
from base64 import b64encode
|
|
from json import JSONEncoder
|
|
from time import time
|
|
import ldap3
|
|
import app
|
|
import datetime
|
|
from app.six.moves import map
|
|
from app.six import iteritems, b, binary_type
|
|
|
|
@Configuration(retainsevents=True)
|
|
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.BASE,
|
|
one=ldap3.LEVEL,
|
|
sub=ldap3.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
|
|
GeneratingCommand.flush
|
|
if self.limit and serial_number == self.limit:
|
|
break
|
|
pass
|
|
|
|
pass
|
|
|
|
except ldap3.core.exceptions.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 iteritems(attributes):
|
|
if isinstance(value, binary_type):
|
|
attributes[name] = b64encode(value).decode('utf-8')
|
|
elif isinstance(value, datetime.datetime):
|
|
attributes[name] = str(value)
|
|
elif isinstance(value, list):
|
|
for i in range(len(value)):
|
|
if isinstance(value[i], binary_type):
|
|
value[i] = b64encode(value[i]).decode('utf-8')
|
|
elif isinstance(value[i], datetime.datetime):
|
|
value[i] = str(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)),
|
|
map(lambda name: (name, attributes.get(name, '')), attribute_names)))
|
|
|
|
return record
|
|
|
|
dispatch(LdapSearchCommand, module_name=__name__)
|