Zaszyfrowane logi w rsyslog i syslog-ng

Spis treści

Jakiś czas temu, na forum DUG'a wyczytałem coś o przesyłaniu logów systemowych przez sieć. W sumie, to nigdy mi to do głowy nie przyszło ale jeśli by się nad tym głębiej zastanowić, tego typu mechanizm może okazać się całkiem użyteczny. Na dobrą sprawę nie wiem jak to jest rozwiązane w debianie opartym o systemd, natomiast jeśli chodzi o inne init'y (openrc i sysvinit), to tego typu funkcjonalność można zaimplementować wykorzystując narzędzie rsyslog lub syslog-ng . W tym wpisie zostanie opisana konfiguracja debianowego serwera, na którym będzie nasłuchiwał daemon rsyslog . Dodatkowo, zostanie przedstawiona konfiguracja dwóch klientów, z których jeden będzie miał zainstalowanego syslog-ng , a drugi rsyslog . Z klientów logi zostaną przesłane do serwera. Dodatkowo, postaramy się zaszyfrować ruch przy pomocy kanału TLS.

Przygotowanie maszyn

Co prawda, syslog-ng i rsyslog się troszeczkę różnią między sobą jeśli chodzi o konfigurację ale każdy z nich może być klientem/serwerem dla tego drugiego. Przesył logów przez sieć to jedna sprawa, a zabezpieczenie komunikacji by te logi nie szły po kablach w formie niezaszyfrowanej, to całkiem inna bajka. Na szczęście, oba te narzędzia są w kwestii szyfrowania logów kompatybilne i mogą ze sobą współpracować bez większego problemu.

Zanim jednak przejdziemy do konfiguracji demonów, musimy przygotować odpowiednio wszystkie maszyny. Na serwerze oraz jednym kliencie instalujemy pakiet rsyslog . Dodatkowo, na tych dwóch maszynach musimy doinstalować pakiety rsyslog-gnutls oraz openssl . Na drugim kliencie instalujemy pakiet syslog-ng i także openssl .

Jeśli nie posiadamy obsługi protokołu ipv6, możemy skonfigurować rsyslog'a by nasłuchiwał tylko na protokole ipv4. Edytujemy zatem plik /etc/default/rsyslog i ustawiamy odpowiedni parametr:

RSYSLOGD_OPTIONS="-4"

Generowanie certyfikatów

By zestawić kanał TLS, potrzebne nam są certyfikaty. W debianie jest dostępne narzędzie easy-rsa , które ułatwia znacząco operowanie na certyfikatach ale w przypadku rsyslog i syslog-ng nie możemy z niego skorzystać. Trzeba używać certtool dostępnego w paczce gnutls-bin .

Dobrze jest przyjąć sobie pewną konwencję przy nazywaniu plików. W tym przypadku pliki certyfikatów będą nazywane host.domena + odpowiednia końcówka. Klucze prywatne będą mieć .key , a certyfikaty będą mieć .crt . Wyjątkiem będzie jedynie CA, które będzie miało formę ca.domena.{key,crt} .

Narzędzie certtool przyjmuje predefiniowaną konfigurację dla certyfikatów. Dlatego też stwórzmy cztery pliki tekstowe, po jednym dla CA, serwera i dla każdego klienta. Te pliki umieścimy w katalogu /etc/CA/template/ . Same certyfikaty zaś będą zlokalizowane w /etc/CA/ . Poniżej wzory plików pod certyfikaty:

Plik /etc/CA/template/ca_192.168.1.150 :

# X.509 Certificate options
#
# DN options

# The organization of the subject.
organization = "morfikownia"

# The organizational unit of the subject.
#unit = "sleeping dept."

# The state of the certificate owner.
#state = "Example"

# The country of the subject. Two letter code.
country = GB

# The common name of the certificate owner.
cn = "192.168.1.150"

# The serial number of the certificate. Should be incremented each time a new certificate is generated.
serial = 001

# In how many days, counting from today, this certificate will expire.
expiration_days = 3650

# Whether this is a CA certificate or not
ca

# Whether this key will be used to sign other certificates.
cert_signing_key

# Whether this key will be used to sign CRLs.
crl_signing_key

Plik /etc/CA/template/client_192.168.1.1 :

# X.509 Certificate options
#
# DN options

# The organization of the subject.
organization = "morfikownia"

# The organizational unit of the subject.
#unit = "sleeping dept."

# The state of the certificate owner.
state = "localhost"

# The country of the subject. Two letter code.
country = GB

# The common name of the certificate owner.
cn = "192.168.1.1"

# A user id of the certificate owner.
#uid = "scertowner"

# The serial number of the certificate. Should be incremented each time a new certificate is generated.
serial = 004

# In how many days, counting from today, this certificate will expire.
expiration_days = 3650

# X.509 v3 extensions

# DNS name(s) of the server
#dns_name = "server.example.com"
#dns_name = "server_alias.example.com"

# (Optional) Server IP address
ip_address = "192.168.1.150"

# Whether this certificate will be used for a TLS server
tls_www_server

# Whether this certificate will be used to encrypt data (needed
# in TLS RSA ciphersuites). Note that it is preferred to use different
# keys for encryption and signing.
encryption_key
signing_key

Plik /etc/CA/template/client_192.168.1.166 :

# X.509 Certificate options
#
# DN options

# The organization of the subject.
organization = "morfikownia"

# The organizational unit of the subject.
#unit = "sleeping dept."

# The state of the certificate owner.
state = "localhost"

# The country of the subject. Two letter code.
country = GB

# The common name of the certificate owner.
cn = "192.168.1.166"

# A user id of the certificate owner.
#uid = "scertowner"

# The serial number of the certificate. Should be incremented each time a new certificate is generated.
serial = 003

# In how many days, counting from today, this certificate will expire.
expiration_days = 3650

# X.509 v3 extensions

# DNS name(s) of the server
#dns_name = "server.example.com"
#dns_name = "server_alias.example.com"

# (Optional) Server IP address
ip_address = "192.168.1.150"

# Whether this certificate will be used for a TLS server
tls_www_server

# Whether this certificate will be used to encrypt data (needed
# in TLS RSA ciphersuites). Note that it is preferred to use different
# keys for encryption and signing.
encryption_key
signing_key

Plik /etc/CA/template/server_192.168.1.1 :

# X.509 Certificate options
#
# DN options

# The organization of the subject.
organization = "morfikownia"

# The organizational unit of the subject.
#unit = "sleeping dept."

# The state of the certificate owner.
state = "localhost"

# The country of the subject. Two letter code.
country = GB

# The common name of the certificate owner.
cn = "192.168.1.150"

# A user id of the certificate owner.
#uid = "scertowner"

# The serial number of the certificate. Should be incremented each time a new certificate is generated.
serial = 002

# In how many days, counting from today, this certificate will expire.
expiration_days = 3650

# X.509 v3 extensions

# DNS name(s) of the server
#dns_name = "server.example.com"
#dns_name = "server_alias.example.com"

# (Optional) Server IP address
ip_address = "192.168.1.150"

# Whether this certificate will be used for a TLS server
tls_www_server

# Whether this certificate will be used to encrypt data (needed
# in TLS RSA ciphersuites). Note that it is preferred to use different
# keys for encryption and signing.
encryption_key
signing_key

Kluczowe w powyższych plikach jest dobranie parametru cn , który musi odpowiadać nazwie lub adresie IP danego hosta. Jeśli ta nazwa lub adres IP nie będzie się zgadzać z nazwą lub adresem IP danego hosta, to przy uwierzytelnianiu podczas próby przesyłu logów, zostanie wyrzucony błąd i akcja się nie powiedzie.

Przechodzimy teraz do katalogu /etc/CA/ i generujemy klucze prywatne na podstawie zdefiniowanych szablonów:

# certtool --generate-privkey --rsa --sec-param high --outfile ca_192.168.1.150.key
Generating a 3072 bit RSA private key...

# certtool --generate-privkey --rsa --sec-param high --outfile server_192.168.1.150.key
Generating a 3072 bit RSA private key...

# certtool --generate-privkey --rsa --sec-param high --outfile client_192.168.1.1.key
Generating a 3072 bit RSA private key...

# certtool --generate-privkey --rsa --sec-param high --outfile client_192.168.1.166.key
Generating a 3072 bit RSA private key...

Jeden z powyższych kluczy, a konkretnie ten mający w swojej nazwie ca musi zostać podpisany przez samego siebie, tak by mógł podpisywać inne certyfikaty:

# certtool --generate-self-signed --load-privkey ca_192.168.1.150.key  --template ./template/ca_192.168.1.150 --outfile ca_192.168.1.150.crt
Generating a self signed certificate...
...
Signing certificate...

Teraz za pomocą tak stworzonego CA generujemy certyfikaty dla serwera:

# certtool --generate-certificate --template template/server_192.168.1.150 --load-privkey server_192.168.1.150.key --load-ca-certificate ca_192.168.1.150.crt --load-ca-privkey ca_192.168.1.150.key --outfile server_192.168.1.150.crt
Generating a signed certificate...
...
Signing certificate...

Oraz dwóch pozostałych klientów:

# certtool --generate-certificate --template template/client_192.168.1.1 --load-privkey client_192.168.1.1.key --load-ca-certificate ca_192.168.1.150.crt --load-ca-privkey ca_192.168.1.150.key --outfile client_192.168.1.1.crt
Generating a signed certificate...
...
Signing certificate...

# certtool --generate-certificate --template template/client_192.168.1.166 --load-privkey client_192.168.1.166.key --load-ca-certificate ca_192.168.1.150.crt --load-ca-privkey ca_192.168.1.150.key --outfile client_192.168.1.166.crt
Generating a signed certificate...
...
Signing certificate...

W tej chwili, katalog /etc/CA/ powinien się prezentować następująco:

# tree /etc/CA/
/etc/CA/
├── ca_192.168.1.150.crt
├── ca_192.168.1.150.key
├── client_192.168.1.1.crt
├── client_192.168.1.1.key
├── client_192.168.1.166.crt
├── client_192.168.1.166.key
├── server_192.168.1.150.crt
├── server_192.168.1.150.key
└── template
    ├── ca_192.168.1.150
    ├── client_192.168.1.1
    ├── client_192.168.1.166
    └── server_192.168.1.150

1 directory, 12 files

Przesyłamy tak stworzone certyfikaty na odpowiednie maszyny przy pomocy scp. W tym przypadku, certyfikaty CA i serwera nie będą kopiowane. Trzeba tylko przesłać certyfikaty do maszyn klienckich. Trzeba także pamiętać by skopiować certyfikat samego CA:

# scp ca_192.168.1.150.crt client_192.168.1.1.* 192.168.1.1:/etc/syslog-ng.cert/
root@192.168.1.1's password:
ca_192.168.1.150.crt                 100% 1525     1.5KB/s   00:00
client_192.168.1.1.crt               100% 1643     1.6KB/s   00:00
client_192.168.1.1.key               100% 8394     8.2KB/s   00:00

# scp ca_192.168.1.150.crt client_192.168.1.166.* 192.168.1.166:/etc/rsyslog.cert/
root@192.168.1.166's password:
ca_192.168.1.150.crt                 100% 1525     1.5KB/s   00:00
client_192.168.1.166.crt             100% 1647     1.6KB/s   00:00
client_192.168.1.166.key             100% 8404     8.2KB/s   00:00

Konfiguracja serwera rsyslog

Edytujemy plik /etc/rsyslog.conf na serwerze i dopisujemy tam poniższe linijki:

#################
#### MODULES ####
#################

# make gtls driver the default
$DefaultNetstreamDriver gtls

# certificate files
$DefaultNetstreamDriverCAFile /etc/CA/ca_192.168.1.150.crt
$DefaultNetstreamDriverCertFile /etc/CA/server_192.168.1.150.crt
$DefaultNetstreamDriverKeyFile /etc/CA/server_192.168.1.150.key

$ModLoad imuxsock # provides support for local system logging
$ModLoad imklog   # provides kernel logging support
#$ModLoad immark  # provides --MARK-- message capability

# provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514                                # start up listener at port 514

$InputTCPServerStreamDriverMode 1                     # run driver in TLS-only mode
#$InputTCPServerStreamDriverAuthMode anon             # client is NOT authenticated
#$ActionSendStreamDriverAuthMode x509/name            # authenticate by hostname
#$InputTCPServerStreamDriverPermittedPeer *.mhouse.lh
$InputTCPServerStreamDriverPermittedPeer 192.168.1.*

W przypadku gdybyśmy nie chcieli sprawdzać certyfikatów klienckich, trzeba odkomentować $InputTCPServerStreamDriverAuthMode anon i zakomentować wszystko poniżej. Jeśli wykorzystujemy domeny zamiast adresów IP, w $InputTCPServerStreamDriverPermittedPeer podajemy nazwy oraz odhashować trzeba także $ActionSendStreamDriverAuthMode x509/name . Wszelkie możliwe dyrektywy, które mogą zostać użyte w powyższym pliku, są dostępne i przyzwoicie opisane w dokumentacji rsyslog'a.

Konfiguracja klienta rsyslog

Mając ustawiony już serwer, zaprogramujemy klienta tak by przesłał wszystkie logi jakie są generowane w systemie do serwera skonfigurowanego wyżej. W tym celu edytujemy plik /etc/rsyslog.conf na kliencie i dopisujemy w nim poniższe linijki:

#################
#### MODULES ####
#################

# make gtls driver the default
$DefaultNetstreamDriver gtls

# certificate files
$DefaultNetstreamDriverCAFile /etc/rsyslog.cert/ca_192.168.1.150.crt
$DefaultNetstreamDriverCertFile /etc/rsyslog.cert/client_192.168.1.166.crt
$DefaultNetstreamDriverKeyFile /etc/rsyslog.cert/client_192.168.1.166.key

$ModLoad imuxsock # provides support for local system logging
$ModLoad imklog   # provides kernel logging support
#$ModLoad immark  # provides --MARK-- message capability

# provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514

# set up the action
$ActionSendStreamDriverMode 1                   # require TLS for the connection

*.* @@(o)192.168.1.150:514

Ostatnia linijka będzie przesyłać wszystkie logi *.* protokołem TCP @@ na zdalny serwer. Gdybyśmy chcieli skorzystać z protokołu UDP, zamiast @@ byłby jeden znak @ .

Konfiguracja klienta syslog-ng

W tym przypadku również chcemy przesłać wszystkie logi przez sieć, z tym, że musimy pierw zrobić jeden zabieg na certyfikatach. Konkretnie to musimy nazwać certyfikat CA po jego hash'u. Przy pomocy openssl możemy wygenerować hash. Na kliencie wydajemy poniższe polecenia:

# openssl x509 -noout -hash -in ca_192.168.1.150.crt
f31e2e50

# ln -s ./ca_192.168.1.150.crt f31e2e50.0

# ls -al
drwxr-xr-x    2 root     root          4096 Oct 10 20:03 .
drwxr-xr-x    1 root     root          4096 Oct 10 15:27 ..
-rw-r--r--    1 root     root          1525 Oct 10 19:36 ca_192.168.1.150.crt
-rw-r--r--    1 root     root          1643 Oct 10 19:36 client_192.168.1.1.crt
-rw-------    1 root     root          8394 Oct 10 19:36 client_192.168.1.1.key
lrwxrwxrwx    1 root     root            22 Oct 10 20:03 f31e2e50.0 -> ./ca_192.168.1.150.crt

Przechodzimy teraz do konfiguracji syslog-ng . Poniżej znajduje się mój plik konfiguracyjny:

@version:3.0

options {
      # disable the chained hostname format in logs
      chain_hostnames(off);

      # the time to wait before a died connection is re-established
      time_reopen (600);

      # the time to wait before an idle destination file is closed
      time_reap(0);

      # the number of lines buffered before written to file
      # you might want to increase this if your disk isn't catching with
      # all the log messages you get or if you want less disk activity
      # (say on a laptop)
      flush_lines(0);

      # the number of lines fitting in the output queue
      log_fifo_size(256);

      # enable or disable directory creation for destination files
      create_dirs(no);

      # default owner, group, and permissions for log files
      owner(root);
      perm(0600);
      #group("log");

      # default owner, group, and permissions for created directories
      #dir_owner(root);
      #dir_group(root);
      #dir_perm(0755);

      # enable or disable DNS usage
      # syslog-ng blocks on DNS queries, so enabling DNS may lead to
      # a Denial of Service attack
      use_dns(no);

      # maximum length of message in bytes
      # this is only limited by the program listening on the /dev/log Unix
      # socket, glibc can handle arbitrary length log messages, but -- for
      # example -- syslogd accepts only 1024 bytes
      log_msg_size(1024);

      # Disable statistic log messages.
      stats_freq(0);

      # "--MARK--" entries in the log
      mark_freq (1800);

      keep_hostname(yes);
      use_fqdn(no);
      long_hostnames(on);
#     ts_format(iso);      #make ISO-8601 timestamps
};


source s_all {
      # message generated by Syslog-NG
      internal();
      # standard Linux log source (this is the default place for the syslog() function to send logs to)
      unix-stream("/dev/log");
      # messages from the kernel
      file("/proc/kmsg" program_override("kernel"));
};

source s_localhost {
      tcp(ip(127.0.0.1) port(514));
      udp(ip(127.0.0.1) port(514));
};

destination d_messages {
      file("/var/log/messages");
};

destination d_network {
#     tcp( "192.168.1.150" port(514) );
      tcp( "192.168.1.150" port(514)
            tls( ca_dir("/etc/syslog-ng.cert")
                  key_file("/etc/syslog-ng.cert/client_192.168.1.1.key")
                  cert_file("/etc/syslog-ng.cert/client_192.168.1.1.crt")
#                 peer_verify(optional-untrusted)
                  peer_verify(required-trusted)
                  )
            );
};


log {
      source(s_all);
      source(s_localhost);
      destination(d_messages);
      destination(d_network);
};

Interesuje nas głównie zwrotka destination d_network . Jeśli jakieś opcje są niejasne, wszystkie z nich są dokładnie opisane w dokumentacji syslog-ng.

Rozdzielenie logów

Zbierane w ten sposób logi są mieszane z logami systemowymi na serwerze. Jeśli tych logów nie jest dużo, lub też mamy kilka stacji roboczych, z których zbieramy logi, taka niedogodność niezbyt może się nam dać we znaki. Natomiast jeśli logów mamy dużo i pochodzą przy tym z wielu hostów, przydałoby się jakoś te logi podzielić i poupychać w odpowiednich plikach, tak by ułatwić sobie ich przeglądanie w późniejszym czasie.

By rozdzielić logi, dopisujemy poniższe linijki w sekcji RULES w pliku /etc/rsyslog.conf ale tak, by znajdowały się one przed wszystkimi lokalnymi regułami, przykładowo:

###############
#### RULES ####
###############

if $fromhost-ip startswith "192.168.1.1" then /var/log/router.log
& stop
...

Pierwsza linijka zapisze logi z adresu 192.168.1.1 do pliku /var/log/router.log . Druga linijka zaś kończy przetwarzanie tych logów, tj. nie są one przetwarzane przez kolejne reguły w pliku konfiguracyjnym rsyslog'a. Jeśli nie dodamy & stop , komunikaty zostaną zalogowane do powyższego pliku oraz też do standardowych plików rsyslog'a.

Problemy

Jeśli z jakichś powodów mamy problemy z połączeniem, możemy zastopować demona rsyslog i odpalić go w interaktywnym trybie debug:

# rsyslogd -nd

Powyższe polecenie wygeneruje dość pokaźny log, w którym na pewno się znajdują informacje wskazujące gdzie leży problem.

Możemy także wygenerować sobie testową wiadomość i sprawdzić czy zostanie przesłana przez sieć i ewentualnie zaszyfrowana:

# logger -t test my syslog-test-message

Z tym, że by sprawdzić czy komunikat jest szyfrowany, trzeba posłużyć się jakimś snifferem, np. wiresharkiem.

Alternatywna składnia pliku konfiguracyjnego rsyslog.conf

Nowsze wersje rsyslog'a wykorzystują inną składnię w pliku konfiguracyjnym. Poniżej są przykładowe sekcje z modułami dla dwóch maszyn korzystających z rsyslog .

Serwer:

#################
#### MODULES ####
#################

$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile /etc/CA/ca_192.168.1.150.crt
$DefaultNetstreamDriverCertFile /etc/CA/server_192.168.1.150.crt
$DefaultNetstreamDriverKeyFile /etc/CA/server_192.168.1.150.key

module(load="imuxsock")
module(load="imklog")
#module(load="immark")

module( load="imtcp"
    StreamDriver.Name="gtls"                    # ptcp or gtls
    StreamDriver.mode="1"                       # run driver in TLS-only mode
    StreamDriver.authmode="x509/name"          # authenticate by hostname
    #StreamDriver.authmode="x509/fingerprint"   # certificate fingerprint authentication
    #StreamDriver.authmode="x509/certvalid"     # certificate validation only
    #StreamDriver.authmode="anon"               # clients are NOT authenticated
    #PermittedPeer="192.168.1.1"
    PermittedPeer=["192.168.1.1","morfitronik.lh"]
)
input( type="imtcp"
    port="514"
    name="tcp-tls"
)

Klient:

#################
#### MODULES ####
#################

module(load="imuxsock")
module(load="imklog")
#module(load="immark")

$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile /etc/rsyslog.cert/ca_192.168.1.150.crt
$DefaultNetstreamDriverCertFile /etc/rsyslog.cert/client_morfitronik.lh.crt
$DefaultNetstreamDriverKeyFile /etc/rsyslog.cert/client_morfitronik.lh.key

# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")

# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")

# set up the action
$ActionSendStreamDriverMode 1                   # require TLS for the connection

*.* @@(o)192.168.1.150:514
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.