Narzędzia Net Admina: Nie masz SYSLOG-a – napisz go sobie sam

Tym artykułem rozpoczynam cykl „Net Admin Tools – nie masz narzędzi, to stwórz je sobie sam”.

W cyklu tym planuję pokazać, jak w niezwykle prosty sposób i na dodatek w praktycznie błyskawicznym tempie napisać w języku Python kod serwera i klienta Syslog-a, serwera i klienta TFTP, jak skryptowo oprogramować tunelowanie SSH, by zabezpieczyć nieszyfrowaną transmisję danych przez sieć, czy też jak napisać własnego Netcata.

Log managementTematem na dziś jest Syslog – rzecz bez wątpienia niezbędna każdemu administratorowi sieci do przetwarzania logów generowanych przez urządzenia infrastruktury sieciowej i urządzania systemów zabezpieczeń sieci.

To prawda, że nic nie stoi na przeszkodzie, by skorzystać z gotowych narzędzi i na komputerze z Linuxem lub z innym systemem z rodziny Unix uruchomić systemowego Syslog-a. Dla systemów Windows też istnieją gotowe programy tego rodzaju. Jednak możemy po pierwsze nie mieć takowych narzędzi pod ręką, a po drugie – omija nas doskonała okazja, by nauczyć się sprawnie programować interfejsy gniazd sieciowych (network sockets). Wreszcie, mając własne narzędzia można logowanie uczynić bardziej przyjazne dla dalszego przetwarzania, czy to skryptowego, czy też w narzędziach klasy SIEM.

Z ciekawostek dodam w tym miejscu, że z braku innych możliwości zdarzyło mi się kiedyś napisać i uruchomić serwer Syslog-a na… tablecie🙂.

Elementem najistotniejszym i jednocześnie tematem przewodnim artykułu jest oczywiście kod serwera. Klienta natomiast napisałem przede wszystkim, by można było w jakiś rozsądny sposób przetestować serwer, ale też dlatego, że funkcje generowania logów w odpowiedzi na różne zdarzenia w aplikacji okazują się być bardzo przydatne niezależnie od tego gdzie finalnie są przekierowywane logi – do serwera, do pliku czy na ekran konsoli. Zatem kod klienta można wykorzystywać w celu implementacji w aplikacjach funkcji w miarę eleganckiego logowania zdarzeń.

Zachęcam do dalszego rozwoju oprogramowania i implementacji w nim dodatkowych funkcjonalności. Do oprogramowania pozostaje na przykład cała bogata sfera działań do podjęcia w odpowiedzi na wystąpienie konkretnego rodzaju zdarzeń, filtrowanie logów, logowanie do różnych plików itd.

Kod w obecnej postaci pozwala na logowania na ekran konsoli i do wskazanego pliku. Z braku wskazania konkretnej nazwy loguje domyślnie do pliku syslog.log.

Zachęcam do lektury tego i dalszych artykułów cyklu Net Admin Tools.

Interfejs skryptu serwera prezentuje się następująco:

$ skrypty> syslog_server.py
Usage:
        ███████╗██╗   ██╗███████╗██╗      ██████╗  ██████╗
        ██╔════╝╚██╗ ██╔╝██╔════╝██║     ██╔═══██╗██╔════╝
        ███████╗ ╚████╔╝ ███████╗██║     ██║   ██║██║  ███╗
        ╚════██║  ╚██╔╝  ╚════██║██║     ██║   ██║██║   ██║
        ███████║   ██║   ███████║███████╗╚██████╔╝╚██████╔╝
        ╚══════╝   ╚═╝   ╚══════╝╚══════╝ ╚═════╝  ╚═════╝
        =================== SERVER =======================
        
Options:
  -h, --help            show this help message and exit
  -i IP_ADDR, --ip=IP_ADDR
                        ip address to bind to
                        (default: 0.0.0.0)
  -p DPORT, --port=DPORT
                        local port to use
                        (default: 514)
  -f LOGFILE, --file=LOGFILE
                        file to save the logs to
                        (default: syslog.log)
-------------------------
Starting SYSLOG server...
SYSLOG started. Press Ctrl+C to shut it down

Interfejs klienta wygląda tak:

$ skrypty> syslog_client.py -h
Usage:
        ███████╗██╗   ██╗███████╗██╗      ██████╗  ██████╗
        ██╔════╝╚██╗ ██╔╝██╔════╝██║     ██╔═══██╗██╔════╝
        ███████╗ ╚████╔╝ ███████╗██║     ██║   ██║██║  ███╗
        ╚════██║  ╚██╔╝  ╚════██║██║     ██║   ██║██║   ██║
        ███████║   ██║   ███████║███████╗╚██████╔╝╚██████╔╝
        ╚══════╝   ╚═╝   ╚══════╝╚══════╝ ╚═════╝  ╚═════╝
        -------------------- CLIENT -----------------------

Options:
  -h, --help            show this help message and exit
  -i IP_ADDR, --ip=IP_ADDR
                        ip address of server
                        (default: localhost)
  -l LEVEL, --level=LEVEL
                        level of logging
                        (EMERG(0), ALERT(1), CRIT(2), ERR(3),
                        WARNING(4), NOTICE(5), INFO(6), DEBUG(7))
  -f FACL, --facility=FACL
                        logging facility
                        (KERN(0), USER(1), MAIL(2), DAEMON(3), AUTH(4),
                        SYSLOG(5), LPR(6), NEWS(7), UUCP(8), CRON(9),
                        AUTHPRIV(10), FTP(11), LOCAL0(16), LOCAL1(17),
                        LOCAL2(18), LOCAL3(19), LOCAL4(20), LOCAL5(21),
                        LOCAL6(22), LOCAL7(23)
  -m MESG, --message=MESG
                        message to send

Skrypty w akcji prezentują się następująco:

Wygenerowanie logów przez klienta:
$ skrypty> syslog_client.py -i localhost -l ALERT -f LOCAL0 -m "Pozdrowienia od Edłarda Ąckiego..."
|FAC-16|SEV-1|Mon Sep 14 21:47:12 2015|: Pozdrowienia od Edłarda Ąckiego...

Logi na serwerze:
2015-09-14 21:47:12,786-SYSLOG-WARNING: |FAC-16|SEV-1|Mon Sep 14 21:47:12 2015|: Pozdrowienia od Edłarda Ąckiego...
2015-09-14 21:47:12,864-SYSLOG-INFO: |FAC-16|SEV-1|Mon Sep 14 21:47:12 2015|: Pozdrowienia od Edłarda Ąckiego...

Proszę zwrócić uwagę na poprawną obsługę polskich znaków diakrytycznych.

Kod serwera:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import logging
import SocketServer
from optparse import OptionParser
 
class SyslogUDPHandler(SocketServer.BaseRequestHandler):    
    def handle(self):
        try:
            data = self.request[0].strip()
            message = data.decode('cp1250')
            socket = self.request[1]
            main_logger.warning(message)
            main_logger.info(message)
        except:
            print "Problem within logging handler function"
# end of class SyslogUDPHandler
        
if __name__ == "__main__":
    LOG_FILE = 'syslog.log'
    HOST, PORT = "0.0.0.0", 514
        
    usage = u"""
    ███████╗██╗   ██╗███████╗██╗      ██████╗  ██████╗ 
    ██╔════╝╚██╗ ██╔╝██╔════╝██║     ██╔═══██╗██╔════╝ 
    ███████╗ ╚████╔╝ ███████╗██║     ██║   ██║██║  ███╗
    ╚════██║  ╚██╔╝  ╚════██║██║     ██║   ██║██║   ██║
    ███████║   ██║   ███████║███████╗╚██████╔╝╚██████╔╝
    ╚══════╝   ╚═╝   ╚══════╝╚══════╝ ╚═════╝  ╚═════╝ 
    =================== SERVER =======================
    """
    parser = OptionParser(usage=usage)
    
    parser.add_option('-i', '--ip', type='string',
                      help='ip address to bind to \
                      (default: 0.0.0.0)',
                      dest='ip_addr', default="")
    parser.add_option('-p', '--port', type='int',
                      help='local port to use \
                      (default: 514)',
                      dest='dport', default=514)
    parser.add_option('-f', '--file', type='string',
                      help='file to save the logs to \
                      (default: syslog.log)',
                      dest='logfile', default='syslog.log')
    
    options, args = parser.parse_args()

    if options.ip_addr:
        HOST = options.ip_addr
    
    if options.dport:
        PORT = options.dport

    if options.logfile:
        LOG_FILE = options.logfile
    
    parser.print_help()
    
    # Setting logging facility

    main_logger = logging.getLogger("SYSLOG")
    main_logger.setLevel(logging.INFO)

    console_handler = logging.StreamHandler()
    file_handler = logging.FileHandler(LOG_FILE)

    console_handler.setLevel(logging.INFO)
    file_handler.setLevel(logging.DEBUG)

    formatter = logging.Formatter('%(asctime)s-%(name)s-%(levelname)s: %(message)s')
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    main_logger.addHandler(console_handler)
    main_logger.addHandler(file_handler)
    
    logging.basicConfig(
        filename=LOG_FILE,
        filemode='a',
        level=logging.DEBUG, 
        format='%(asctime)s-%(name)s-%(levelname)s: %(message)s',
        datefmt='%H:%M:%S'
    )
    
    try:
        sys.stderr.write('-------------------------\n')
        sys.stderr.write('Starting SYSLOG server...\n')
        server = SocketServer.UDPServer((HOST,PORT), SyslogUDPHandler)
        sys.stderr.write('SYSLOG started. Press Ctrl+C to shut it down\n')
        server.serve_forever(poll_interval=0.5)
    except (IOError, SystemExit):
        raise
    except KeyboardInterrupt:
        print ("Ctrl+C Pressed. Shutting down.")
        

Kod klienta:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
import time
from optparse import OptionParser

class Facility:
    "Syslog facilities"
    KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, \
    LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP = range(12)

    LOCAL0, LOCAL1, LOCAL2, LOCAL3, \
    LOCAL4, LOCAL5, LOCAL6, LOCAL7 = range(16, 24)

class Level:
    "Syslog levels"
    EMERG, ALERT, CRIT, ERR, \
    WARNING, NOTICE, INFO, DEBUG = range(8)

class Syslog:
    def __init__(self, host="localhost", port=514, facility=Facility.DAEMON):
        self.host = host
        self.port = port
        self.facility = facility
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def send(self, message, level):
        "Send a syslog message to remote host using UDP."
        t = time.localtime()
        data = "|FAC-%s|SEV-%s|%s|: %s" % (self.facility, level, time.asctime(t), message)
        print data.decode('cp1250')
        self.socket.sendto(data, (self.host, self.port))
# end of class Syslog

if __name__ == "__main__":
    HOST = "127.0.0.1"
    FACILITY = 3; LEVEL = 3
    usage = u"""
    ███████╗██╗   ██╗███████╗██╗      ██████╗  ██████╗ 
    ██╔════╝╚██╗ ██╔╝██╔════╝██║     ██╔═══██╗██╔════╝ 
    ███████╗ ╚████╔╝ ███████╗██║     ██║   ██║██║  ███╗
    ╚════██║  ╚██╔╝  ╚════██║██║     ██║   ██║██║   ██║
    ███████║   ██║   ███████║███████╗╚██████╔╝╚██████╔╝
    ╚══════╝   ╚═╝   ╚══════╝╚══════╝ ╚═════╝  ╚═════╝ 
    -------------------- CLIENT -----------------------    
    """
    parser = OptionParser(usage=usage)
    
    parser.add_option('-i', '--ip', type='string',
                      help='ip address of server \
                      (default: localhost)',
                      dest='ip_addr', default="")
    
    parser.add_option('-l', '--level', type='string',
                      help='level of logging \
                      (EMERG(0), ALERT(1), CRIT(2), ERR(3), \
                       WARNING(4), NOTICE(5), INFO(6), DEBUG(7))',
                      dest='level', default=4)                      
    
    parser.add_option('-f', '--facility', type='string',
                      help='logging facility \
                      (KERN(0), USER(1), MAIL(2), DAEMON(3), AUTH(4), \
                       SYSLOG(5), LPR(6), NEWS(7), UUCP(8), CRON(9), \
                       AUTHPRIV(10), FTP(11), LOCAL0(16), LOCAL1(17), \
                       LOCAL2(18), LOCAL3(19), LOCAL4(20), LOCAL5(21), \
                       LOCAL6(22), LOCAL7(23)',
                      dest='facl', default='5')    
                                        
    parser.add_option('-m', '--message', type='string',
                      help='message to send',
                      dest='mesg', default="")                  
    
    options, args = parser.parse_args()

    if options.ip_addr:
        HOST = options.ip_addr
                    
    if options.facl in ('0', 'KERN', 'KERN(0)'):
        FACILITY=Facility.KERN
    elif options.facl in ('1', 'USER', 'USER(1)'):
        FACILITY=Facility.USER
    elif options.facl in ('2', 'MAIL', 'MAIL(2)'):
        FACILITY=Facility.MAIL
    elif options.facl in ('3', 'DAEMON', 'DAEMON(3)'):
        FACILITY=Facility.DAEMON
    elif options.facl in ('4', 'AUTH', 'AUTH(4)'):
        FACILITY=Facility.AUTH
    elif options.facl in ('5', 'SYSLOG', 'SYSLOG(5)'):
        FACILITY=Facility.SYSLOG    
    elif options.facl in ('6', 'LPR', 'LPR(6)'):
        FACILITY=Facility.LPR    
    elif options.facl in ('7', 'NEWS', 'NEWS(7)'):
        FACILITY=Facility.NEWS
    elif options.facl in ('8', 'UUCP', 'UUCP(8)'):
        FACILITY=Facility.UUCP
    elif options.facl in ('9', 'CRON', 'CRON(9)'):
        FACILITY=Facility.CRON
    elif options.facl in ('10', 'AUTHPRIV', 'AUTHPRIV(10)'):
        FACILITY=Facility.AUTHPRIV
    elif options.facl in ('11', 'FTP', 'FTP(11)'):
        FACILITY=Facility.FTP    
    elif options.facl in ('16', 'LOCAL0', 'LOCAL0(16)'):
        FACILITY=Facility.LOCAL0    
    elif options.facl in ('17', 'LOCAL1', 'LOCAL1(17)'):
        FACILITY=Facility.LOCAL1    
    elif options.facl in ('18', 'LOCAL2', 'LOCAL2(18)'):
        FACILITY=Facility.LOCAL2    
    elif FACILITY in ('19', 'LOCAL3', 'LOCAL3(19)'):
        FACILITY=Facility.LOCAL3    
    elif options.facl in ('20', 'LOCAL4', 'LOCAL4(20)'):
        FACILITY=Facility.LOCAL4    
    elif options.facl in ('21', 'LOCAL5', 'LOCAL5(21)'):
        FACILITY=Facility.LOCAL5    
    elif options.facl in ('22', 'LOCAL6', 'LOCAL6(22)'):
        FACILITY=Facility.LOCAL6            
    elif options.facl in ('23', 'LOCAL7', 'LOCAL7(23)'):
        FACILITY=Facility.LOCAL7
    else:
        FACILITY=Facility.SYSLOG
    
    if options.level in ('0', 'EMERG', 'EMERG(0)'):
        LEVEL = Level.EMERG
    elif options.level in ('1', 'ALERT', 'ALERT(1)'):
        LEVEL = Level.ALERT    
    elif options.level in ('2', 'CRIT', 'CRIT(2)'):
        LEVEL = Level.CRIT
    elif options.level in ('3', 'ERR', 'ERR(3)'):
        LEVEL = Level.ERR
    elif options.level in ('4', 'WARNING', 'WARNING(4)'):
        LEVEL = Level.WARNING
    elif options.level in ('5', 'NOTICE', 'NOTICE(5)'):
        LEVEL = Level.NOTICE
    elif options.level in ('6', 'INFO', 'INFO(6)'):
        LEVEL = Level.INFO
    elif options.level in ('7', 'DEBUG', 'DEBUG(7)'):
        LEVEL = Level.DEBUG
    else:
        LEVEL = Level.NOTICE
        
    message = options.mesg
    
    log = Syslog(host=HOST, facility=FACILITY)
    log.send(message, LEVEL)

Informacje Janusz Nawrat
Just ordinary man who likes thinking...

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

TOMASZ WEŁNA

artysta grafik | wykładowca

PRACOWNIA OKO

Szkoła Rysunku Malarstwa i Grafiki DR TOMASZA WEŁNY | KRAKÓW | Plac Matejki 10 | tel 691 81 75 74

Piękno neurobiologii

Blog Jerzego Vetulaniego

Teoria muzyki, zasady muzyki, podstawy muzyki

Teoria muzyki, zasady muzyki, podstawy muzyki - czyli to co każdy amator muzyki wiedzieć powinien :)

Personal Development & Inspirations

Przemyślenia i refleksje, którymi warto się podzielić (blog by Janusz Nawrat)

Business IT Cooperation Platform

Biznes i IT - dwa światy, które muszą współdziałać

%d bloggers like this: