Verschlüsseltes Logging übers Netzwerk mit syslog-ng

Vor kurzem habe ich einige unserer Server so konfiguriert, daß sie einen Teil ihrer Log-Nachrichten nicht nur lokal in Dateien schreiben, sondern auch über’s Netzwerk an einen anderen Server schicken. Das hat diverse Vorteile, von denen die beiden größten wohl die Chance sind, daß ein abstürzendes System vielleicht noch letzte Log-Nachrichten über’s Netzwerk loswird, auch wenn es sie nicht mehr auf die Platte geschrieben bekommt (was bei der Fehlersuche helfen kann), sowie der potentielle Nutzen bei der Nachverfolgung von Angriffen, denn jemand der auf einem Server einbricht mag zwar dessen lokale Log-Dateien manipulieren können, um die bereits über’s Netzwerk geschickten Nachrichten (die Hinweise auf den Einbruch enthalten können) zu manipulieren, müßte er auch auf dem Server einbrechen, der die Nachrichten entgegennimmt.

Um hier den Hintergrund zu erklären, muß ich kurz ausholen: Wir haben uns vor einer Weile zwei dicke Storage-Systeme zugelegt. Das hat zum einen den Vorteil, daß ein großes, dediziertes RAID-System weitaus effizienter und billiger ist als in jedem einzelnen Server mindestens zwei Festplatten zu verbauen, zum anderen verringert es den Wartungsaufwand erheblich, da ein großer RAID natürlich Spares hat, so daß man nicht gleich bei jeder ausgefallenen Platte loshetzen und eine neue einbauen muß, und insgesamt die Menge der Platten einfach eine andere ist.

Seit wir diese Storage-Lösung haben, booten wir einige Server völlig ohne Festplatten (es sind nicht mal mehr welche eingebaut) über das Netzwerk per iSCSI. Wobei dieses Netzwerk natürlich nicht das wilde , gefährliche Internet ist, sondern ein schnelles, dediziertes Storage-Netzwerk, in dem nichts anderes stattfindet.

Ein so bootender Server ist nun aber davon abhängig, daß statt seiner Festplatten sein iSCSI-Subsystem immer funktioniert. Geht hier etwas schief, dann hat der Server ein ernstes Problem. Und hier ergibt sich eine Zwickmühle: Probleme mit einer Festplatte kann ich gut diagnostizieren, weil das Hardware ist, Probleme mit Software kann ich gut diagnostizieren, weil es Logs gibt. Aber was mache ich, wenn ein Server bei Problemen mit iSCSI (= Software) keine Logs mehr schreiben kann?

Für genau solche Fälle kann es nützlich sein, Log-Nachrichten über das Netzwerk zu schicken. (Wenn jemand ein Kabel zieht oder die Netzwerkschnittstelle oder ein Switch ausfällt, dann stürzt ein Server der von iSCSI abhängig ist natürlich auch ab und Log-Nachrichten können dann auch nicht mehr über’s Netzwerk gehen, aber in solchen Fällen ist der Fehler relativ leicht zu diagnostizieren, da brauche ich keine Log-Nachrichten für.)

Bei der Einrichtung bin ich am Anfang noch sehr minimalistisch vorgegangen und hab fast ausschließlich die Bordmittel von CentOS benutzt. Später wurde das Setup immer komplexer. Die einzelnen Schritte will ich im Folgenden erläutern:

Bei CentOS ist syslogd vorinstalliert und den kann man sehr leicht dazu bringen, Log-Nachrichten an einen anderen Host zu schicken oder Log-Nachrichten von anderen Hosts anzunehmen (oder sogar beides zu tun). Die zuständige Config heißt /etc/syslog.conf, dort kann man sich z.B. die Zeile schnappen mit der konfiguriert wird was in die Log-Datei /var/log/messages geschrieben wird und genau die gleichen Nachrichten nochmal über das Netzwerk an einen anderen Host schicken lassen:


# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
*.info;mail.none;authpriv.none;cron.none /var/log/messages
*.info;mail.none;authpriv.none;cron.none @centrallog.example.com

Um am anderen Ende mit syslogd Nachrichten aus dem Netzwerk entgegenzunehmen, muß man die Datei /etc/sysconfig/syslog anpassen:

# Options to syslogd
# -m 0 disables 'MARK' messages.
# -r enables logging from remote machines
# -x disables DNS lookups on messages recieved with -r
# See syslogd(8) for more details
SYSLOGD_OPTIONS="-m 0 -r -x"

Ob man die DNS-Auflösung an oder abschalten will, ist jedem selbst überlassen, für Testzwecke hab ich sie erstmal ausgeschaltet.

Mit syslogd zu arbeiten hat aber Nachteile:

  • Die Kommunikation erfolgt nur per UDP.
  • Es gibt keine Möglichkeit zur Authentifikation der Kommunikationspartner.
  • Es gibt keine Möglichkeit der Verschlüsselung.
  • Es besteht das Risiko einer DoS-Attacke gegen Hosts, die Log-Nachrichten aus dem Netzwerk annehmen, da diese Nachrichten von jedem Ursprung annehmen. (Hier kann man allenfalls mit einem Paketfilter bzw. einer Firewall wie iptables etwas Abhilfe schaffen.)
  • syslogd hat nur sehr eingeschränkte Möglichkeiten die erhaltenen Log-Nachrichten zu sortieren.

Bis auf die letzten beiden Punkte ist das aber nicht eine Schwäche von syslogd. Der Standard nach dem Remote Syslog — also das Loggen von Nachrichten über das Netzwerk — erfolgt gibt einfach nicht mehr her.

Ein paar Dienste, die Remote Syslog beherrschen, gehen deshalb mittlerweile über den Standard hinaus und bieten weitere Möglichkeiten, vor allem Kommunikation per TCP und Verschlüsselung mit SSL/TLS — hieran ist positiv anzumerken, daß die Erweiterung sich wiederum auf Standardtechnologien stützen und somit also nicht pro Dienst ein neues Sonderprotokoll eingeführt wird, das dann nur der jeweilige Dienst versteht, sondern von vornherein eine Lösung gewählt wurde, die Interoperabilität verspricht.

Zwei solche Alternativen zu syslogd sind syslog-ng und rsyslog. Ich habe mich für syslog-ng entschieden, da ich das bereits von früher her kenne.

syslog-ng ist nicht Teil der offiziellen CentOS-Paketquellen, steht aber in EPEL zur Verfügung. Der Nachteil der EPEL-Variante ist, daß es sich dabei um den 2.1er Branch von syslog-ng handelt, der noch keine Verschlüsselung beherrscht. Man kann dies z.B. mit stunnel umgehen und ich hab dies auch zunächst getan, mich dann aber später dafür entschieden, doch lieber den 3er Branch von syslog-ng zu nehmen, der TLS nativ beherrscht.

Allen CentOS-Nutzern sei an dieser Stelle empfohlen sich die Config aus dem EPEL-Paket zu extrahieren. Diese ist so geschrieben, daß syslog-ng sich nahezu genauso verhält wie syslogd es unter CentOS tut. Der Vorteil daran ist, daß man sich erstmal nicht umgewöhnen muß was die Position von Logfiles angeht und die Kompatibiltät mit logwatch und logrotate erhalten bleibt.

Eines der wichtigen Features von syslog-ng ist der Schalter „-s“ mit dem man ihn veranlassen kann seine Config zu prüfen und sonst nichts weiter zu tun. Auf diese Art und Weise findet man leicht alle möglichen Config-Fehler. Da das Format der Config von syslog-ng vor Klammern und Semikola strotz, ist das wirklich sehr nützlich. (Mein persönlicher Wunsch für einen zukünftigen 4er Branch: Bitte stellt doch die Config endlich mal auf sowas schönes und übersichtliches wie YAML um. Dieses Elend aus Klammern, Anführungszeichen und Semikola muß doch nun wirklich nicht sein.)

Wenn man nun aber die Config aus dem 2.1er Paket aus EPEL dem 3er syslog-ng vorwirft, wird er sich beschweren. Angenehmerweise sagt er aber nicht einfach, daß er die Config nicht lesen kann, sondern er gibt aus, daß er dies für eine 2.1er Config hält und listet dann detailiert auf, was ihm daran nicht schmeckt und macht sogar gute Änderungsvorschläge. Wer diese Hinweise kurz in der offiziellen Dokumentation nachschlägt, sollte sehr schnell zu einer Config kommen, die auch der 3er syslog-ng akzeptiert. (Ich hab übrigens mit der offiziellen Dokumentation im Web insgesamt bessere Erfahrungen gemacht als mit den Manpages, die an einigen Stellen einfach nicht präzise genug sind und zu arm an anschaulichen Beispielen daherkommen.)

An dieser Stelle noch ein Tipp für den allgemeinen Teil der Config: syslog-ng kann auf verschiedene Art und Weise für gutes Verhalten und gute Performance optimiert werden (siehe auch Best practices in der offiziellen Dokumentation). Zwei Dinge die sich sehr lohnen ist die Normalisierung der Zeitstempel auf ein Format, daß Zeitzonen-Informationen enthält, und die gepufferte Verarbeitung von Log-Nachrichten. Remote Syslog verwendet normalerweise einen Zeitstempel, der keine Zeitzoneninformation enthält. Zeitzonen geraten aber hin und wieder mal durcheinander und wenn man die Zeitstempel mit syslog-ng so umstellt, daß sie Zeitzonendaten enthalten, fallen solche Fehler wenn sie denn mal auftreten leichter auf. Dazu trägt man im allgemeinen Teil der Config (also unter options) einfach ts_format(iso); ein. Bei der gepufferten Verarbeitung geht es darum, nicht jede Log-Nachricht einzeln durch den internen Filter von syslog-ng zu jagen, sondern immer ein bißchen zu warten und es dann im Verbund mit weiteren Nachrichten zu tun, was insgesamt die CPU schont. Dazu trägt man wieder im allgemeinen Teil der Config time_sleep(20); ein.

Nun aber zum eigentlichen Logging:

Um mit syslog-ng nachrichten per UDP zu empfangen, kann man entweder UDP als allgemeine Nachrichtenquelle eintragen, der dafür nötige Eintrag ist auskommentiert schon in der Config aus dem EPEL-Paket vorgegeben:


source s_sys
{
    file ("/proc/kmsg" program_override("kernel"));
    unix-stream ("/dev/log");
    internal();
#  udp(ip(0.0.0.0) port(514));
};

Oder man legt sich eine Extra-Quelle an, was im weiteren die Konfiguration übersichtlicher machen kann:


source s_udp
{
    udp(ip(0.0.0.0) port(514));
};

Bei TCP ist es im Grunde genauso:


source s_tcp
{
    tcp(ip(0.0.0.0) port(514));
};

Man kann hierbei auch einschränken, an welcher IP syslog-ng lauschen soll, wenn man nicht über alle IP-Interfaces Nachrichten annehmen möchte:


source s_tcp
{
    tcp(ip(192.168.0.254) port(514));
};

Will man nun noch zusätzlich Verschlüsselung nutzen, dann muß man TCP verwenden:


source s_tcp_tls
{
    tcp
    (
        ip(192.168.0.254)
        port(514)
        tls
            (
                ca_dir("/opt/syslog-ng/etc/ca.d")
                cert_file("/opt/syslog-ng/etc/localhost-cert.pem")
                crl_dir("/opt/syslog-ng/etc/crl.d")
                key_file("/opt/syslog-ng/etc/localhost-key.pem")
            )
    );
};

Wie hier das Zertifikat und der dazugehörende Key angegeben wird ist ja deutlich sichtbar. Das ca_dir und das crl_dir bedürfen allerdings noch weiterer Erläuterung: syslog-ng akzeptiert an diesen Stellen nur Dateien im PEM-Format die nach einem ganz bestimmten Muster benannt sind, alle anderen irgnoriert es. Für Certificate Authorities müssen die Dateien nach dem Subject Hash des jeweiligen CA Zertifikats gefolgt von einem „.0“ benannt sein. Ich lege mir die Datei dazu mit einem normalen Dateinnamen in den Ordner und linke dann darauf:

ln -s my_cacert.pem `openssl x509 -noout -subject_hash -in my_cacert.pem`.0

Bei CRL ist es fast genauso, nur muß der Name hier auf „.r0“ enden.

CRLs sind übrigens optional, man kann auch gerne darauf verzichten. Ich habe nicht darauf verzichtet, in diesem Zusammenhang aber auch ein paar interessante Macken entdeckt. So gibt syslog-ng z.B. eine Fehlermeldung („CRL directory is set but nor CRLs found“) aus, wenn man nicht für jede einzelne CA, die man im ca_dir aufgeführt hat eine CRL hinterlegt — die vorhandenen CRLs werden aber berücksichtigt, auch wenn die Fehlermeldung etwas anderes vermuten läßt.

Nun muß noch dafür gesorgt werden, daß die empfangenen Nachrichten schön übersichtlich in extra Dateien sortiert werden. Dazu lege ich mir für jeden Host von dem ich Nachrichten annehmen will ein Ziel an, das in diesem Fall eine Datei ist:


destination d_examplelogger { file("/var/log/rsyslog/examplelogger.log"); };

Und einen Filter, mit dem ich später die Nachrichten dieses Host aus der Gesamtheit von Nachrichten herausfische:


filter f_examplelogger { host("examplelogger") or host("192.168.0.128"); };

Und schließlich füge ich alle Teile zusammen, damit syslog-ng weiß was er zu tun hat:


log { source(s_tcp_tls); filter(f_examplelogger); destination(d_examplelogger); flags(final); };

Mit flags(final); sage ich an, daß diese Nachrichten damit abgearbeitet sind und für andere Logs nicht mehr betrachtet werden müssen. Alternativ wäre es auch möglich Nachrichten über mehrere Dateien zu verteilen und sogar mehrmals in verschiedenen Dateien vorkommen zu lassen.

Auf dem Host der die Log-Nachrichten schicken soll sieht die Konfiguration zunächst ähnlich aus. Statt einer Nachrichtenquelle, definiert man hier aber ein Nachrichtenziel:


destination d_centrallogger_tls
{
    tcp
    (
        "centrallogger.example.com"
        port(514)
        tls
        (
            ca_dir("/opt/syslog-ng/etc/ca.d")
            cert_file("/opt/syslog-ng/etc/localhost-cert.pem")
            crl_dir("/opt/syslog-ng/etc/crl.d")
            key_file("/opt/syslog-ng/etc/localhost-key.pem")
        )
    );
};

Und dann suche ich mir unter den vorhandenen Log-Befehlen weiter unten diejenigen raus, deren Nachrichten ich zusätzlich über’s Netzwerk geschickt haben möchte, z.B.:


log { source(s_sys); filter(f_default); destination(d_mesg); destination(d_centrallogger_tls); };

Hier habe ich dem schon eingetragenen Ziel destination(d_mesg); einfach ein weiteres Ziel destination(d_centrallogger_tls); hinzugefügt.

Und das war’s.

Ein paar potentielle Schwächen bleiben natürlich noch. Auch syslog-ng ist nicht vor DoS-Attacken sicher. Da er er aber nicht alle Nachrichten auch loggt, sondern nur diejenigen für die ein Log-Statement vorhanden ist, sollte man durch gute Filter-Regeln zumindest ein Vollschreiben der Festplatte vermeiden können. TCP-Syn-Flooding bleibt allerdings ein Problem. Wenn man Remote Syslog aber nicht in den wilden Weiten des Internets sondern in einem internen Netz einsetzt, hat man hier gute Chancen den Angriffsvektor zu verkleinern und dem Rest mit gutem Netzwerk-Monitoring und geeigneten Paketfiltern zu begegnen.