diff --git a/server/backend/diagnostics.py b/server/backend/diagnostics.py new file mode 100644 index 0000000..31851dc --- /dev/null +++ b/server/backend/diagnostics.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +import os +import subprocess +import platform +import socket +import pkg_resources +import psutil + +__author__ = 'Eugeny N Ablesov' +__version__ = '1.0.17' + +def collect_accounts_info(): + """ This call collects generic information about + user accounts presented on system running TinyCheck. + + No personal information collected or provided by this call. + """ + accs = { } + users = psutil.users() + for user in users: + accs[user.name + '@' + user.host] = { + 'started': user.started, + 'term': user.terminal + } + alt_user = os.getenv('SUDO_USER', os.getenv('USER')) + usr = 'root' if os.path.expanduser('~') == '/root' else alt_user + pid = psutil.Process().pid + term = psutil.Process().terminal() if 'Linux' in platform.system() else 'win' + accs[usr + '@' + term] = { 'pid': pid } + return accs + +def collect_os_info(): + """ This call collects generic information about + operating system running TinyCheck. + + No personal information collected or provided by this call. + """ + os_info = { } + os_info['system'] = platform.system() + os_info['release'] = platform.release() + os_info['version'] = platform.version() + os_info['platform'] = platform.platform(aliased=True) + if 'Windows' in os_info['system']: + os_info['dist'] = platform.win32_ver() + if 'Linux' in os_info['system']: + os_info['dist'] = platform.libc_ver() + return os_info + +def collect_hardware_info(): + """ This call collects information about hardware running TinyCheck. + + No personal information collected or provided by this call. + """ + hw_info = { } + hw_info['arch'] = platform.architecture() + hw_info['machine'] = platform.machine() + hw_info['cpus'] = psutil.cpu_count(logical=False) + hw_info['cores'] = psutil.cpu_count() + hw_info['load'] = psutil.getloadavg() + disk_info = psutil.disk_usage('/') + hw_info['disk'] = { + 'total': disk_info.total, + 'used': disk_info.used, + 'free': disk_info.free + } + return hw_info + +def collect_network_info(): + """ This call collects information about + network configuration and state running TinyCheck. + + No personal information collected or provided by this call. + """ + net_info = { } + net_info['namei'] = socket.if_nameindex() + addrs = psutil.net_if_addrs() + state = psutil.net_io_counters(pernic=True) + for interface in addrs.keys(): + net_info[interface] = { } + int_info = state[interface] + props = [p for p in dir(int_info) + if not p.startswith("_") + and not p == "index" + and not p == "count"] + for prop in props: + net_info[interface][prop] = getattr(int_info, prop) + return net_info + +def collect_dependency_info(package_list): + """ This call collects information about + python packages required to run TinyCheck. + + No personal information collected or provided by this call. + """ + dependencies = { } + installed_packages = list(pkg_resources.working_set) + installed_packages_list = sorted(["%s==%s" + % (installed.key, installed.version) + for installed in installed_packages]) + for pkg in installed_packages_list: + [package_name, package_version] = pkg.split('==') + if package_name in package_list: + dependencies[package_name] = package_version + return dependencies + +def collect_db_tables_records_count(db_path, tables): + result = { } + for table in tables: + query = 'SELECT COUNT(*) FROM %s' % (table) + sqlite_call = subprocess.Popen(['sqlite3', db_path, query], stdout = subprocess.PIPE) + stout, sterr = sqlite_call.communicate() + val = stout.decode("utf-8") + recs = int(val) if val else 0 + result[table] = recs + return result + +def collect_internal_state(db_path, tables, to_check): + """ This call collects information about + installed TinyCheck instance and its internal state. + + No personal information collected or provided by this call. + """ + state_ = { } + available = os.path.isfile(db_path) + dbsize = 0 + state_['db'] = { + 'available': available, + 'size': dbsize + } + state_['db']['records'] = { } + if available: + state_['db']['size'] = os.stat(db_path).st_size + state_['db']['records'] = collect_db_tables_records_count(db_path, tables) + + services_ = { } + for alias in to_check: + status = subprocess.call(['systemctl', 'is-active', '--quiet', '%s' % (to_check[alias])]) + state = '' + if status != 0: + sysctl_call = subprocess.Popen( + ["systemctl", "status", "%s" % (to_check[alias]), + r"|", + "grep", + r"''"], + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + stout, sterr = sysctl_call.communicate() + state = stout.decode("utf-8") + errs = sterr.decode("utf-8") + if "could not be found" in errs: + state = 'Service not found' + services_[alias] = { + 'running': status == 0, + 'status': status, + 'state': state + } + state_['svc'] = services_ + return state_ + +def main(): + print("TinyCheck diagnostics script.\nVersion: %s" % (__version__)) + print("") + + db_path = '/usr/share/tinycheck/tinycheck.sqlite3' + tables = ['iocs', 'whitelist', 'misp'] + services = { } + services['frontend'] = 'tinycheck-frontend.service' + services['backend'] = 'tinycheck-backend.service' + services['kiosk'] = 'tinycheck-kiosk.service' + services['watchers'] = 'tinycheck-watchers.service' + + deps = [ + 'pymisp', 'sqlalchemy', 'ipwhois', + 'netaddr', 'flask', 'flask_httpauth', + 'pyjwt', 'psutil', 'pydig', 'pyudev', + 'pyyaml', 'wifi', 'qrcode', 'netifaces', + 'weasyprint', 'python-whois', 'six' ] + + diagnostics = { } + diagnostics['acc'] = collect_accounts_info() + diagnostics['os'] = collect_os_info() + diagnostics['hw'] = collect_hardware_info() + diagnostics['net'] = collect_network_info() + diagnostics['deps'] = collect_dependency_info(deps) + diagnostics['state'] = collect_internal_state(db_path, tables, services) + report = { 'diagnostics': diagnostics } + print(report) + print("") + +if __name__ == '__main__': + main()