Моя веб панель управления ubuntu серверами. Часть 1.

2 января 2020 г. Теги:

Управление серверами с командной строки является лучшим методом для администратора, а вот для пользователя который не владеет этой магией лучше пользоваться визуальным интерфейсом. Я обслуживаю несколько ubuntu серверов компании в которой раньше работал и бывают моменты когда у меня нет возможности добраться до сервера или необходимы небольшие правки конфигурации, в такие моменты необходим пользовательский интерфейс.

Конечно у нас были решения в реализации данных решений, но вот до комплексного подхода ни как не доходили руки. В период новогодних праздников, в период затишья, мне пришла в голову мысль решить эту проблему. Первое необходимо поставить задачу и распланировать, разбить на модули весь проект.

В первую очередь решить проблему с предоставлением доступа в локальную сеть устройств. Все устройства "просящие" доступ в сеть необходимо разграничить в доступе. Конечно многие скажут, что есть уже прототипы реализующие данный алгоритм. Например вот ресурс: Авторизация Wi-Fi. Во первых практически все ресурсы предоставляющие эту возможность являются платными, а во вторых мне самому интересно изобрести велосипед.
Так, что я начал с составления составления справочников отделов и сотрудников и сопоставлений с ними справочника имеющихся "зарегистрированных" сетевых устройств. Реализации сканирования локальной сети в поисках подключенных устройств. Составления конфигурационного файла isc-dhcp-server.
Итак начну описание процесса.
Основу будет составлять Django 3.0.1.
Работу разделяю на приложения отвечающие каждая за свой функционал.
Весь функционал связанный с сетевыми устройствами выношу в приложение localset. Для организации интерфейса вся логика записывается в отдельные фасады. (Про каждый буду описывать по мере написания функционала). Так для управления DHCP сервера описываю фасад ControlDhcpServer

import re

from .models import RegisteredHost
from .services.parser_dhcp_conf import ParserDhcpConf
from .services.connect_server import ssh_conect, ssh_conect_dhcp
from apps.services_functions.string_functions import get_val_if_key

''' Фасад реализующий управление DHCP сервером.
    ControlDhcpServer - класс управляющий DHCP сервером

    Для ускорения работы все значения конфигурации DHCP сервера
    хранятся в базе данных и управляются классом прослойкой
    SettingsDhcpInBase - класс прослока

'''


class ControlDhcpServer():
    ''' Класс управляющий DHCP сервером
    '''
    _active_server = None
    _registers_mac = set()  # список зарегистрированных mac адресов;
    _dhcp_clients = []  # список зарегистрированных соответсвий ip-mac
    _unknow_cliets = set()  # список не зарегистрированных  mac адресов;

    def __init__(self, host=None, user=None, passwd=None):
        if not host or not user or not passwd:
            self._active_server = ssh_conect_dhcp()
        else:
            self._active_server = ssh_conect(host, user, passwd)

    def leases(self):
        ''' Производится разбор лога DHCP сервера
            на данный момент времени
            и заполняются:
            _registers_mac - список зарегистрированных mac адресов;
            _dhcp_clients - список зарегистрированных соответсвий ip-mac;
            unknow_cliets - список не зарегистрированных  mac адресов;
        '''
        stdin, stdout, stderr = self._active_server.exec_command(
            "cat /var/log/dhcpd.log")
        log = stdout.read().decode('utf-8')
        for line in log.splitlines():
            self.find_unknow_client_to_log(line)
            self.find_pack_ip_client_to_log(line)

    def clear_log(self):
        self._active_server.exec_command("cat /dev/null > /var/log/dhcpd.log")

    def find_unknow_client_to_log(self, line):
        ''' Проверка на наличие в переданной сторке line
            наличие записи "DHCPDISCOVER.*unknown"
            Если найдена, то добавляем найденным mac адресов в
            список не зарегистрированных mac адресов
        '''
        tmp_r = re.findall(r'DHCPDISCOVER.*unknown', line)
        if tmp_r:
            self._unknow_cliets.update(re.findall(
                r'(?<!:)\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b(?!:)', line))

    def find_pack_ip_client_to_log(self, line):
        ''' Проверка на наличие в переданной сторке line
            наличие записи "DHCPACK"
            Если найдена, то добавляем найденным mac адресов в
            список зарегистрированных mac адресов и
            список зарегистрированных соответсвий ip-mac
        '''
        tmp_r = re.findall(r'DHCPACK', line)
        if tmp_r:
            mac = re.findall(
                r'(?<!:)\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b(?!:)', line)
            ip = re.findall(
                r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', line)
            if mac and not mac[0] in self._registers_mac:
                self._registers_mac.update(mac)
                self._dhcp_clients.append(
                    {'mac': mac[0] if mac else '',
                     'ip': ip[0] if ip else ''})

    @property
    def hosts_in_dhcp_server(self):
        return ParserDhcpConf().execute()

    @property
    def dhcp_clients(self):
        self.leases()
        return self._dhcp_clients

    @property
    def registers_mac(self):
        self.leases()
        return self._registers_mac

    @property
    def unknow_cliets(self):
        self.leases()
        return self._unknow_cliets


class SettingsDhcpInBase():
    ''' класс прослока между значениями хранящимися
        в конфигурациях DHCP сервера и значениями в базе данных.

    '''
    model = RegisteredHost

    def __init__(self, host=None, user=None, passwd=None):
        self.control_dhcp_derver = ControlDhcpServer(host, user, passwd)

    def dhcp_clients_from_config_to_base(self):
        dhcp_clients = self.control_dhcp_derver.hosts_in_dhcp_server
        for dhcp_client in dhcp_clients:
            # создается словарь обновляемых полей
            defaults = dict(zip(
                ['hostname', 'routes', 'subnet', 'dns'],
                map(lambda x: get_val_if_key(dhcp_client, x),
                    [ParserDhcpConf.TAG_HOST_NAME,
                     ParserDhcpConf.TAG_ROUTER,
                     ParserDhcpConf.TAG_SUBNET,
                     ParserDhcpConf.TAG_DNS])))
            self.model.objects.update_or_create(
                mac=dhcp_client[ParserDhcpConf.TAG_MAC],
                ip=dhcp_client[ParserDhcpConf.TAG_ADDR],
                defaults=defaults)

Разбор логов и конфигурационных файлов производится с помощью отдельного класса ParserDhcpConf

import re
from .connect_server import ssh_conect_dhcp


class ParserDhcpConf(object):
    ''' Класс преобразование конфигурационного файла настройки хостов
        DHCP сервера в список с словарями
    '''

    # маркеры начала, конца записи хоста
    # и опиций хоста в конфиге dhcp пользователей
    (TAG_START_ITEM, TAG_END_ITEM,
     TAG_MAC, TAG_ADDR, TAG_HOST_NAME,
     TAG_SUBNET, TAG_ROUTER, TAG_DNS) = (
        '^host .*{', '}',
        'hardware ethernet', 'fixed-address', 'ddns-hostname',
        'subnet-mask', 'routers', 'domain-name-servers',
    )

    # маркеры и регулярыне значения опиций хоста в конфиге dhcp пользователей
    tags = {
        TAG_START_ITEM: TAG_START_ITEM,
        TAG_END_ITEM: TAG_END_ITEM,
        TAG_MAC:
            r'((?<!:)\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b(?!:))',
        TAG_ADDR:
            r'fixed-address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3});',
        TAG_HOST_NAME: r'ddns-hostname (.*);',
        TAG_SUBNET: r'subnet-mask (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3});',
        TAG_ROUTER: r'routers (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3});',
        TAG_DNS:
            r'domain-name-servers (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3});',
    }

    hosts = []

    def get_value(self, tag, line, dict_item):
        '''Фуннкция возвращает переданный словарь(dict_item)
            вставив в него значение со сотроки(line),
            полученое при помощи переданого маркера(tag).
        '''
        result = re.search(self.tags[tag], line)
        if result and result.groups():
            dict_item[tag] = result.group(1)
            return dict_item

    def what_active_tag_in_line(self, line):
        ''' Функция возвращает найденный маркер в текущей строке'''
        for key, tag in self.tags.items():
            pattern = re.compile(f'{key}')
            result = pattern.search(line)
            if result:
                return key

    def execute(self):
        ssh = ssh_conect_dhcp()
        stdin, stdout, stderr = ssh.exec_command("cat /etc/dhcp/inet.users")
        log = stdout.read().decode('utf-8')
        for line in log.splitlines():
            active_tag = self.what_active_tag_in_line(line)
            if not active_tag:
                continue  # при отсутствии необходимого маркера переходим далее
            if active_tag == self.TAG_START_ITEM:
                new_item = {}  # начало нового хоста в конфиге -> новый словарь
                continue
            elif active_tag == self.TAG_END_ITEM and new_item:
                self.hosts.append(new_item)  # конец опции по хосту в конфиге
                continue  # заносим словарь в список
            self.get_value(active_tag, line, new_item)
        return self.hosts

Соединение с серверами производятся через модуль paramiko по SSH протоколу

import paramiko
from django.conf import settings


def ssh_conect(server, username, password):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(server, username=username, password=password)
    return ssh


def ssh_conect_dhcp():
    return ssh_conect(
        settings.DHCP_SERVER, settings.DHCP_USER, settings.DHCP_PASSWORD)


def ssh_conect_firewall():
    return ssh_conect(
        settings.FW_SERVER, settings.FW_USER, settings.FW_PASSWORD)

Для работы с сетевых устройств в локальной сети буду использовать python-nmap, с помощью которого произвожу сканирования сети для нахождения всех текущих сетевых устройств. Данный процесс перевожу под управления Celery. Для чего в директории приложения создаю файл celeryp.py в который прописываю :

import os
from celery import Celery
from decouple import config

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'root.settings')

app = Celery(config('CELERY_NAME_APP'))

app.config_from_object('django.conf:settings')
app.autodiscover_tasks()

В файл tasks.py приложения localset записываю задачи по выявлению on-line устройств в сети (периодичность: каждые 5 минут) и очистки лог файл DHCP сервера( выполняется в полночь).

from .facade import ControlDhcpServer
from .services.local_service import scan_ip
from celery.decorators import periodic_task
from celery.task.schedules import crontab


@periodic_task(run_every=crontab(minute='*/5'))
def scan_network():
    scan_ip()


@periodic_task(run_every=crontab(minute=0, hour=0))
def clear_dhcp_log():
    ControlDhcpServer().clear_log()

Так как сканирование сети через nmap является довольно медленным процессом, то для ускорение работы результаты будут заносится в модель Host

''' Функции сервиса сети
'''
import nmap

from ..models import Host


def scan_ip():
    '''Сканирование сети'''
    active_ip()


def active_ip():
    Host.objects.all().update(active=False)
    nm = nmap.PortScanner()
    active_scan = nm.scan(hosts='172.16.0.0/22', arguments='-sP -T4')
    for ip, value in active_scan['scan'].items():
        if 'mac' in value['addresses']:
            mac = value['addresses']['mac']
            vendor = value['vendor'][mac] if mac in value['vendor'] else ''
            Host.objects.update_or_create(
                ip=ip,
                defaults={'mac': mac,
                          'vendor': vendor,
                          'active': True}
            )

Моя веб панель управления ubuntu серверами. Часть 2.

© 2019. Tumanov Nikolay.
Все права защищены. При использовании материалов сайта ссылка обязательна.
Условия использования персональных данных

Разработка сайта - Tumanov Nikolay