ldap-access-log-humanizer/ldap_access_log_humanizer/connection.py

141 строка
5.0 KiB
Python

import re
from ldap_access_log_humanizer.file_descriptor import FileDescriptor
from ldap_access_log_humanizer.operation import Operation
from ldap_access_log_humanizer.custom_logger import CustomLogger
class Connection:
def __init__(self, conn_id, args_dict):
self.conn_id = conn_id
self.time = ""
self.server = ""
self.process = ""
self.operations = {}
self.tls_status = False
self.file_descriptors = []
self.logger = CustomLogger(args_dict)
self.authenticated_status = False
self.user = ""
def dict(self):
return {
"conn_id": self.conn_id,
"time": self.time,
"client": self.client(),
"server": self.server,
"tls": self.tls(),
"authenticated": self.authenticated(),
"user": self.user
}
def reconstitute(self, event_dict):
combined_dict = {}
combined_dict.update(self.dict())
combined_dict.update(event_dict)
return combined_dict
def authenticated(self):
mail_regex = r'.*mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)'
uid_regex = r'.*uid=([a-zA-Z0-9._-]+)'
# requirements: single operation where we have a BIND verb followed by an LDAP_SUCCESS
for operation in self.operations.values():
for request in operation.requests:
if request.get("verb") == "BIND":
# Set user (even if they aren't authenticated, so we can track attempts)
for detail in request.get("details"):
mail_match_object = re.match(mail_regex, detail)
if mail_match_object:
self.user = mail_match_object.group(1)
uid_match_object = re.match(uid_regex, detail)
if uid_match_object:
self.user = uid_match_object.group(1)
# Set status if that user was successful
if operation.response_verb == "RESULT" and operation.error == "LDAP_SUCCESS":
self.authenticated_status = True
return self.authenticated_status
def tls(self):
for file_descriptor in self.file_descriptors:
if file_descriptor.verb == "TLS" and file_descriptor.details.startswith("established"):
self.tls_status = True
return self.tls_status
def closed(self):
for file_descriptor in self.file_descriptors:
if file_descriptor.verb == "closed":
return True
return False
def client(self):
for file_descriptor in self.file_descriptors:
if file_descriptor.verb == "ACCEPT":
pattern = r'from IP=(\d+\.\d+\.\d+\.\d+):'
match = re.search(pattern, file_descriptor.details)
if match:
return match.group(1)
return ""
def add_operation(self, rest):
# Expecting something like:
# op=1 BIND dn="uid=bind-generateusers,ou=logins,dc=example" mech=SIMPLE ssf=0
#
pattern = r'^op=(\d+) (.*)$'
match = re.search(pattern, rest)
if match:
op_id = match.group(1)
operation = self.operations.get(int(op_id))
# if an existing operation, update it's context
if operation:
operation.add_event(match.group(2))
# if a new operation, add it to our operations list
else:
operation = Operation(int(op_id))
operation.add_event(match.group(2))
self.operations[int(op_id)] = operation
if operation.loggable():
self.logger.log(self.reconstitute(operation.dict()))
else:
raise Exception('Malformed operation: {}'.format(rest))
def add_file_descriptor(self, rest):
# Expecting something like:
# fd=34 ACCEPT from IP=192.168.1.1:56822 (IP=0.0.0.0:389)
#
pattern = r'^fd=(\d+) (.*)$'
match = re.search(pattern, rest)
if match:
file_descriptor = FileDescriptor(int(match.group(1)))
file_descriptor.add_event(match.group(2))
self.file_descriptors.append(file_descriptor)
if file_descriptor.loggable():
self.logger.log(self.reconstitute(file_descriptor.dict()))
else:
raise Exception('Malformed file file_descriptor: {}'.format(rest))
# Something happened, this method's job is to update the context
def add_event(self, event):
self.time = event['time']
self.server = event['server']
self.process = event['process']
self.add_rest(event['rest'])
def add_rest(self, rest):
if rest.startswith('op'):
self.add_operation(rest)
elif rest.startswith('fd'):
self.add_file_descriptor(rest)
else:
raise Exception('Unsupported option: {}'.format(rest))