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.

155 lines
6.0 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
import app
import ldap3
from base64 import b64encode
import datetime
from app.six import binary_type
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators
@Configuration()
class LdapFetchCommand(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
| ldapsearch domain=splunk.com search="(objectClass=groups)"
| ldapfetch domain=splunk.com dn=memberOf attributes="cn,description"
"""
# region Command options
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())
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())
dn = Option(
doc=''' Specifies the name of the field holding the distinguished name to fetch.
''',
default='distinguishedName')
domain = Option(
doc=''' Specifies the Active Directory domain to search.
''',
default='default')
# 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)
search_scope = ldap3.BASE
search_filter = '(objectClass=*)'
try:
with configuration.open_connection_pool(self.attrs) as connection_pool:
attribute_names = connection_pool.attributes
for record in records:
dn = record.get(self.dn)
if not dn: # got a falsey value
self.logger.warning('Received empty value for the dn, adding the event without the attributes')
self._augment_record(record, dn, None, attribute_names)
yield record
continue
domain = expanded_domain.get_value(record)
if domain is None:
self.logger.warning('Received empty value for the domain, adding the event without the attributes')
self._augment_record(record, dn, None, attribute_names)
yield record
continue
connection = connection_pool.select(domain)
if not connection:
self.logger.warning('dn="%s": domain="%s" is not configured', self.dn, domain)
self._augment_record(record, dn, None, attribute_names)
yield record
continue
for search_base in dn if isinstance(dn, list) else (dn,):
if search_base:
try:
connection.search(search_base, search_filter, search_scope, attributes=attribute_names)
except ldap3.core.exceptions.LDAPNoSuchObjectResult:
self.logger.warning(
'dn="%s" domain="%s": distinguishedName="%s" does not exist', self.dn, domain,
search_base)
self._augment_record(record, dn, None, attribute_names)
else:
response = connection.response[0]
attributes = app.get_attributes(self, connection.response[0])
if attributes:
self._augment_record(record, response['dn'], attributes, attribute_names)
else:
self.logger.warning('Received empty value for the search_base, adding the event without the attributes')
self._augment_record(record, dn, None, attribute_names)
yield record
pass
except ldap3.core.exceptions.LDAPException as error:
self.error_exit(error, app.get_ldap_error_message(error, configuration))
return
def _augment_record(self, record, dn, attributes, attribute_names):
"""
:param record:
:param dn:
:param attributes:
:return:
"""
record[self.dn] = dn
for name in attribute_names:
value = None
if attributes:
value = attributes.get(name)
if isinstance(value, binary_type):
value = b64encode(value).decode('utf-8')
elif isinstance(value, datetime.datetime):
value = 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])
record[name] = value
return
# endregion
dispatch(LdapFetchCommand, module_name=__name__)