mirror of https://github.com/MISP/misp-dashboard
				
				
				
			new: Started dev of diagnostic tool - WiP
							parent
							
								
									1c06b19840
								
							
						
					
					
						commit
						a50c5c6fdb
					
				| 
						 | 
				
			
			@ -0,0 +1,220 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import stat
 | 
			
		||||
import time
 | 
			
		||||
import functools
 | 
			
		||||
import configparser
 | 
			
		||||
import diagnostic_util
 | 
			
		||||
import redis
 | 
			
		||||
import zmq
 | 
			
		||||
from halo import Halo
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
Steps:
 | 
			
		||||
- check if virtualenv exists
 | 
			
		||||
- check if configuration is update-to-date
 | 
			
		||||
 - check file permission
 | 
			
		||||
 - check if redis is running and responding
 | 
			
		||||
 - check if able to connect to zmq
 | 
			
		||||
 - check zmq_dispatcher processing queue
 | 
			
		||||
    - check queue status: being filled up / being filled down
 | 
			
		||||
- check if subscriber responding
 | 
			
		||||
- check if dispatcher responding
 | 
			
		||||
- check if server listening
 | 
			
		||||
- check log static endpoint
 | 
			
		||||
- check log dynamic endpoint
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
configuration_file = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def humanize(name, isResult=False):
 | 
			
		||||
    words = name.split('_')
 | 
			
		||||
    if isResult:
 | 
			
		||||
        words = words[1:]
 | 
			
		||||
        words[0] = words[0][0].upper() + words[0][1:]
 | 
			
		||||
    else:
 | 
			
		||||
        words[0] = words[0][0].upper() + words[0][1:] + 'ing'
 | 
			
		||||
    return ' '.join(words)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_spinner(_func=None, name='dots'):
 | 
			
		||||
    def decorator_add_spinner(func):
 | 
			
		||||
        @functools.wraps(func)
 | 
			
		||||
        def wrapper_add_spinner(*args, **kwargs):
 | 
			
		||||
            human_func_name = humanize(func.__name__)
 | 
			
		||||
            human_func_result = humanize(func.__name__, isResult=True)
 | 
			
		||||
            flag_skip = False
 | 
			
		||||
 | 
			
		||||
            with Halo(text=human_func_name, spinner=name) as spinner:
 | 
			
		||||
                result = func(spinner, *args, **kwargs)
 | 
			
		||||
                if isinstance(result, tuple):
 | 
			
		||||
                    status, output = result
 | 
			
		||||
                elif isinstance(result, list):
 | 
			
		||||
                    status = result[0]
 | 
			
		||||
                    output = result[1]
 | 
			
		||||
                elif isinstance(result, bool):
 | 
			
		||||
                    status = result
 | 
			
		||||
                    output = None
 | 
			
		||||
                else:
 | 
			
		||||
                    status = False
 | 
			
		||||
                    flag_skip = True
 | 
			
		||||
                    spinner.fail(f'{human_func_name} -  Function return unexpected result: {str(result)}')
 | 
			
		||||
 | 
			
		||||
                if not flag_skip:
 | 
			
		||||
                    text = human_func_result
 | 
			
		||||
                    if output is not None and len(output) > 0:
 | 
			
		||||
                        text += f': {output}'
 | 
			
		||||
 | 
			
		||||
                    if isinstance(status, bool) and status:
 | 
			
		||||
                        spinner.succeed(text)
 | 
			
		||||
                    elif isinstance(status, bool) and not status:
 | 
			
		||||
                        spinner.fail(text)
 | 
			
		||||
                    else:
 | 
			
		||||
                        spinner.warn(text)
 | 
			
		||||
                return status
 | 
			
		||||
        return wrapper_add_spinner
 | 
			
		||||
 | 
			
		||||
    if _func is None:
 | 
			
		||||
        return decorator_add_spinner
 | 
			
		||||
    else:
 | 
			
		||||
        return decorator_add_spinner(_func)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_virtual_environment(spinner):
 | 
			
		||||
    result = os.environ.get('VIRTUAL_ENV')
 | 
			
		||||
    if result is None:
 | 
			
		||||
        return (False, 'This diagnostic tool should be started inside a virtual environment.')
 | 
			
		||||
    else:
 | 
			
		||||
        return (True, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_configuration(spinner):
 | 
			
		||||
    global configuration_file
 | 
			
		||||
    configfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config/config.cfg')
 | 
			
		||||
    cfg = configparser.ConfigParser()
 | 
			
		||||
    cfg.read(configfile)
 | 
			
		||||
    configuration_file = cfg
 | 
			
		||||
    cfg = {s: dict(cfg.items(s)) for s in cfg.sections()}
 | 
			
		||||
    configfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config/config.cfg.default')
 | 
			
		||||
    cfg_default = configparser.ConfigParser()
 | 
			
		||||
    cfg_default.read(configfile)
 | 
			
		||||
    cfg_default = {s: dict(cfg_default.items(s)) for s in cfg_default.sections()}
 | 
			
		||||
 | 
			
		||||
    # Check if all fields from config.default exists in config
 | 
			
		||||
    result, faulties = diagnostic_util.dict_compare(cfg_default, cfg)
 | 
			
		||||
    faulties = [item for sublist in faulties for item in sublist]
 | 
			
		||||
    if result:
 | 
			
		||||
        return (True, '')
 | 
			
		||||
    else:
 | 
			
		||||
        return (False, f'''Configuration incomplete.
 | 
			
		||||
\tUpdate your configuration file `config.cfg`.\n\t➥ Faulty fields: {", ".join(faulties)}''')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner(name='dot')
 | 
			
		||||
def check_file_permission(spinner):
 | 
			
		||||
    max_mind_database_path = configuration_file.get('RedisMap', 'pathmaxminddb')
 | 
			
		||||
    st = os.stat(max_mind_database_path)
 | 
			
		||||
    all_read_perm = bool(st.st_mode & stat.S_IROTH)  # FIXME: permission may be changed
 | 
			
		||||
    if all_read_perm:
 | 
			
		||||
        return (True, '')
 | 
			
		||||
    else:
 | 
			
		||||
        return (False, 'Maxmin GeoDB might have incorrect read file permission')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_redis(spinner):
 | 
			
		||||
    redis_server = redis.StrictRedis(
 | 
			
		||||
        host=configuration_file.get('RedisGlobal', 'host'),
 | 
			
		||||
        port=configuration_file.getint('RedisGlobal', 'port'),
 | 
			
		||||
        db=configuration_file.getint('RedisLog', 'db'))
 | 
			
		||||
    if redis_server.ping():
 | 
			
		||||
        return (True, '')
 | 
			
		||||
    else:
 | 
			
		||||
        return (False, '''Can\'t reach Redis server.
 | 
			
		||||
\t➥ Make sure it is running and adapt your configuration accordingly''')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_zmq(spinner):
 | 
			
		||||
    context = zmq.Context()
 | 
			
		||||
    socket = context.socket(zmq.SUB)
 | 
			
		||||
    socket.connect(configuration_file.get('RedisGlobal', 'zmq_url'))
 | 
			
		||||
    socket.setsockopt_string(zmq.SUBSCRIBE, '')
 | 
			
		||||
    poller = zmq.Poller()
 | 
			
		||||
 | 
			
		||||
    poller.register(socket, zmq.POLLIN)
 | 
			
		||||
    socks = dict(poller.poll(timeout=15000))
 | 
			
		||||
    if len(socks) > 0:
 | 
			
		||||
        if socket in socks and socks[socket] == zmq.POLLIN:
 | 
			
		||||
            rcv_string = socket.recv()
 | 
			
		||||
            if rcv_string.startswith(b'misp_json'):
 | 
			
		||||
                return (True, '')
 | 
			
		||||
    else:
 | 
			
		||||
        return (False, '''Can\'t connect to the ZMQ stream.
 | 
			
		||||
\t➥ Make sure the MISP ZMQ is running: `/servers/serverSettings/diagnostics`
 | 
			
		||||
\t➥ Make sure your network infrastucture allows you to connect to the ZMQ''')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_buffer_queue(spinner):
 | 
			
		||||
    redis_server = redis.StrictRedis(
 | 
			
		||||
            host=configuration_file.get('RedisGlobal', 'host'),
 | 
			
		||||
            port=configuration_file.getint('RedisGlobal', 'port'),
 | 
			
		||||
            db=configuration_file.getint('RedisLIST', 'db'))
 | 
			
		||||
    elements_in_list = redis_server.llen(configuration_file.get('RedisLIST', 'listName'))
 | 
			
		||||
    if elements_in_list > 100:
 | 
			
		||||
        return ('warning', f'Currently {elements_in_list} in the buffer')
 | 
			
		||||
    else:
 | 
			
		||||
        return (True, f'Currently {elements_in_list} in the buffer')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_subscriber_status(spinner):
 | 
			
		||||
    return (False, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_dispatcher_status(spinner):
 | 
			
		||||
    return (False, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_server_listening(spinner):
 | 
			
		||||
    return (False, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_static_endpoint(spinner):
 | 
			
		||||
    return (False, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@add_spinner
 | 
			
		||||
def check_dynamic_enpoint(spinner):
 | 
			
		||||
    return (False, '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def start_diagnostic():
 | 
			
		||||
    if not (check_virtual_environment() and check_configuration()):
 | 
			
		||||
        return
 | 
			
		||||
    check_file_permission()
 | 
			
		||||
    check_redis()
 | 
			
		||||
    check_zmq()
 | 
			
		||||
    check_buffer_queue()
 | 
			
		||||
    check_subscriber_status()
 | 
			
		||||
    check_dispatcher_status()
 | 
			
		||||
    check_server_listening()
 | 
			
		||||
    check_static_endpoint()
 | 
			
		||||
    check_dynamic_enpoint()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    start_diagnostic()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
def dict_compare(dict1, dict2):
 | 
			
		||||
    dict1_keys = set(dict1.keys())
 | 
			
		||||
    dict2_keys = set(dict2.keys())
 | 
			
		||||
    intersection = dict1_keys.difference(dict2_keys)
 | 
			
		||||
    if len(intersection) > 0:
 | 
			
		||||
        return (False, list(intersection))
 | 
			
		||||
 | 
			
		||||
    flag_no_error = True
 | 
			
		||||
    faulties = []
 | 
			
		||||
    for k, v in dict1.items():
 | 
			
		||||
        if (isinstance(v, dict)):
 | 
			
		||||
            status, faulty = dict_compare(v, dict2[k])
 | 
			
		||||
            flag_no_error = flag_no_error and status
 | 
			
		||||
            faulties.append(faulty)
 | 
			
		||||
        else:
 | 
			
		||||
            (True, [])
 | 
			
		||||
    if flag_no_error:
 | 
			
		||||
        return (True, [])
 | 
			
		||||
    else:
 | 
			
		||||
        return (False, faulties)
 | 
			
		||||
		Loading…
	
		Reference in New Issue