Graylog

Stand 2023-06 gibt es noch keine offizielle EOL-Regelung. In der Regel werden die letzten drei dot-Releases unterstützt, also z.B. 5.2, 5.1, 5.0. Dies kann sich je nach Versionsnummer mit Major Releases überschneiden (Beispiel 5.1., 5.0, 4.3). Etwa alle 4 bis 6 Monate erscheint ein neues dot-Release.

Es gibt keine Releases, die als besonders stabil oder instabil gelten. Graylog führt daher auch keine LTS-Versionen. Vor einer intensiven Fehleranalyse wird empfohlen, auf eine neuere Version zu aktualisieren.

Graylog Cloud für Europa wird von AWS in Frankfurt gehostet.

Installation

Graylog kann als Linux-Paket (deb, rpm) oder als Docker-Container (docker-compose, kubernetes) installiert werden.

Minimum:

  • 2x CPUs

  • 6 GB RAM, besser 8 GB

  • 8 GB Platz auf /

Graylog 4.x

dnf -y install epel-release
dnf -y install pwgen

dnf -y install java-1.8.0-openjdk-headless.x86_64

cat > /etc/yum.repos.d/mongodb-org.repo << 'EOF'
[mongodb-org-4.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.2.asc
EOF

dnf -y install mongodb-org

systemctl daemon-reload
systemctl enable mongod.service
systemctl start mongod.service

rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
cat > /etc/yum.repos.d/elasticsearch.repo << 'EOF'
[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/oss-7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF

dnf -y install elasticsearch-oss

tee -a /etc/elasticsearch/elasticsearch.yml > /dev/null <<EOT
cluster.name: graylog
action.auto_create_index: false
EOT

systemctl daemon-reload
systemctl enable elasticsearch.service
systemctl restart elasticsearch.service

rpm -Uvh https://packages.graylog2.org/repo/packages/graylog-4.0-repository_latest.rpm
dnf -y install graylog-server

# create password_secret
pwgen --num-passwords 1 --secure 96

# create root_password_sha2
echo -n "Enter Password: " && head -1 </dev/stdin | tr -d '\n' | sha256sum | cut -d" " -f1
/etc/graylog/server/server.conf
password_secret = 3JJgCxO...
root_password_sha2 = 0621e81...
http_bind_address = 0.0.0.0:9000
systemctl daemon-reload
systemctl enable --now graylog-server.service

setsebool -P httpd_can_network_connect on

Nach der Installation verfügbare Ports:

  • 9000 (tcp): Graylog-GUI und API-Browser per Swagger UI

  • 9200 (tcp): Elasticsearch/OpenSearch

  • 9300 (tcp): Elasticsearch/OpenSearch Node Communication

  • 27017 (tcp): MongoDB

Port 9000 nach aussen öffnen.

Verbinden auf http://graylog.example.com:9000, Benutzername „admin“ plus Passwort aus „root_password_sha2“. Wer https nutzen möchte, installiert beispielsweise einen Apache als Reverse Proxy vor Graylog.

Konfiguration:

  • Neues Index-Set mit alternativen Retention Times / Retention Policies einrichten: System / Indices / Indices > Default Index Set > Edit; als Default Index Set einrichten

  • Neuen Stream erstellen: Streams > Create stream, Remove matches from ‘Default Stream’, neues Index Set verwenden; Manage Rules > Add stream rule > Type: always match - so erhält der neue Stream alle Nachrichten in das neue Default Index set

Tipp

JVM-Settings finden sich in /etc/sysconfig/graylog-server.

Config Ex- und Import:

  • Export: System / Inputs > Content Packs: Create a content pack

  • Import: System / Inputs > Content Packs > Select content packs: Import content pack

Update

Vor dem Update sollte man sich die Upgrading und den Changelog anschauen (Achtung: richtige Version im Dropdown oben auswählen).

Generelles Vorgehen:

  1. Graylog stoppen

  2. Repo-File aktualisieren

  3. Graylog Package aktualisieren

  4. Config File prüfen

  5. Graylog starten

Mithilfe von LFOps sieht das so aus:

Graylog Nodes
systemctl stop graylog-server.service

Zuerst muss man die repo_graylog__version auf die neue Version setzen, danach das Repo file neu deployen:

Ansible control node
ansible-playbook --inventory inv linuxfabrik.lfops.setup_graylog_server --limit 'graylog*' --tags repo_graylog --diff

Nun kann man auf den Servern Graylog aktualisieren. Bei einem Cluster sollte man den Leader zuerst aktualisieren.

Graylog Nodes
dnf update graylog

Als letztes das Graylog Config File neu ausrollen und Graylog wieder starten:

Ansible control node
ansible-playbook --inventory inv linuxfabrik.lfops.setup_graylog_server --limit 'graylog*' --tags graylog_server:configure,graylog_server:state --diff

Input: Syslog

Soll Graylog Logdaten entgegennehmen, müssen die unter „System > Inputs“ konfiguriert werden.

Für das althergebrachte Syslog Port 1514 (udp) verwenden und lokale Firewall öffnen.

Bemerkung

Graylog kann nicht auf Port 514 hören, das führt zu einem „Permission denied, Failed to bind to: ip-address:514“, da es sich hier um einen privilegierten Port handelt. Daher wie in allen Beispielen Port 1514 verwenden. Um einen Bind auf jede IP-Adresse einzurichten, 0.0.0.0 verwenden, * funktioniert als Angabe nicht.

Damit ein Client sinnvolle Daten per Syslog weiterleitet, kann folgende rsyslog-Konfiguration als Basis verwendet werden:

/etc/rsyslog.d/graylog.conf
# rsyslog v7 filter conditions:
# contains isequal startswith regex ereregex
# http://www.rsyslog.com/doc/v7-stable/configuration/filters.html
if (
    $msg startswith "GSSAPI client step " or
    $msg startswith "GSSAPI server step " or
    ($programname == "kernel" and $msg startswith "RULE ") or
    ($programname == "systemd" and ($msg startswith "Created slice " or $msg startswith "Removed slice ")) or
    ($programname == "systemd" and ($msg startswith "Starting user-" or $msg startswith "Stopping user-")) or
    ($programname == "systemd" and ($msg startswith "Starting Session " or $msg startswith "Started Session ")) or
    ($programname == "systemd-logind" and ($msg startswith "New Session " or $msg startswith "Removed Session "))
)
then
    # ignore, do not foward
    continue
else
    *.* @graylog.example.com:1514;RSYSLOG_SyslogProtocol23Format

Beispiel einer Weiterleitung per rsyslog für einen JBoss-Applikationsserver (alternativ WildFly):

/etc/rsyslog.d/jboss.conf
module(load="imfile" PollingInterval="10")

input(
    type="imfile"
    File="/opt/jboss/standalone/log/server.json"
    Tag="jboss-eap"
    StateFile="statefile-to6nnu8DgDug4ImsaaJCpkAQt0zPKb"
)

# just forward, do not log again in /var/log/messages followed by the rules later on in /etc/rsyslog.conf
if $programname == 'jboss-eap' then {
    action(
        type="omfwd"
        Target="graylog.example.com"
        Port="1514"
        Protocol="udp"
    )
    stop
}

LDAP

Eine Gruppen-Synchronisation ist in Graylog Open nicht möglich (muss erworben werden). Eine bestimmte Gruppe kann beispielsweise auf folgende Art und Weise aus dem LDAP bezogen werden: (&(&(uid={0})(objectClass=inetOrgPerson))(memberOf=cn=MYGROUP,cn=groups,cn=accounts,dc=example,dc=org)) (in diesem Beispiel heisst die Gruppe MYGROUP).

Syslog-Nachricht

Welche Felder sind Standard und/oder Pflicht?

  • message: das einzige Pflichtfeld, muss gesetzt werden; ohne gesetzte „message“ wird Nachricht nicht angenommen.

Felder, welche automatisch gesetzt werden:

  • source (wenn nicht gesetzt, wird die IP des sendenden Hosts eingetragen (nicht die eines möglichen Docker-Containers))

  • timestamp (wenn nicht gesetzt, wird aktueller Timestamp verwendet; überschreibbar mit UNIX-Timestamp; falls falsch überschrieben, wird die Nachricht verworfen)

Message-IDs sind UUIDs/GUIDs: ee1bdfe2-4c23-11e7-9f76-525400ce74cd.

Extractors

Unter System > Inputs > Extractors.

Elasticsearch Index / Indizes

Ein Elasticsearch-„Index“ hat einen Namen, z.B. „graylog_0“.

Default Settings des „Default index set“:

  • Rotation strategy: 20000000

  • Retention strategy: Delete Index

  • Max number of indices: 20

Wer eigene Streams definiert, sollte darauf achten, den Haken „Remove matches from ‚All messages‘ stream“ zu setzen, damit Nachrichten nicht doppelt gespeichert werden.

Theroetisch kann man mit diesen curl-Aufrufen nach einer Änderung die Indices neu bauen (praktisch hat das bei uns nicht funktioniert - auch nach einem einfachen Reboot des Graylog-Servers waren die alten Indizes noch vorhanden):

# if Elastic is in read-only mode, switch it back to normal
curl --request PUT \
    --header "Content-Type: application/json" \
    --data '{"index.blocks.read_only_allow_delete": null}'
    http://elastic.example.com:9200/_all/_settings

# start retention job
curl --request POST http://graylog.example.com:9000/api/system/indices/ranges/rebuild

Indizes aus Elasticsearch auslesen:

curl 'http://elastic.example.com:9200/_cat/indices?v'

Indizes-Statistiken:

curl 'http://elastic.example.com:9200/_stats'

Indizes leeren:

curl --request DELETE 'http://elastic.example.com:9200/_all'

Logging testen

Wichtig

Taucht in einer JSON-Nachricht an einen Syslog-Input der Key „level“ auf, wird die Nachricht von Graylog verworfen, sobald der dazu gehörende Value nicht kompatibel mit Syslog ist. Beispiel: „level“:“FINE“ wird verworfen, „level“:“5“ dagegen akzeptiert.

Syslog per 1514/udp:

logger --server graylog.example.com --udp --port 1514 'Test Syslog 1514/udp on '$(date)

Um GELF zu testen, wird ncat benötigt.

dnf -y install nmap-ncat

GELF per 12201/UDP:

# shortest message possible
echo -e '{"message": "Test GELF 12201/udp on '$(date)'"}' | ncat --wait 1 --udp graylog.example.com 12201

# longer message, should split up into fields
echo -e '{
    "version": "1.1",
    "host": "example.org",
    "short_message": "Test GELF 12201/udp on '$(date)'",
    "full_message": "Backtrace here\nsome stuff\nmore stuff",
    "level": 1,
    "_user_id": 9001,
    "some_info": "foo",
    "some_env_var": "bar"
}' | ncat --wait 1 --udp graylog.example.com 12201

GELF per 12201/TCP:

echo -e '{
    "version": "1.1",
    "host": "example.org",
    "short_message": "Test GELF 12201/tcp on '$(date)'",
    "full_message": "Backtrace here\nsome stuff\nmore stuff",
    "level": 1,
    "_user_id": 9001,
    "some_info": "foo",
    "some_env_var": "bar"
}' | ncat --wait 1 graylog.example.com 12201

Tipp

Graylog bei der Arbeit zuschauen: tail -f /var/log/graylog-server/server.log /var/log/opensearch/*log

Suche: Graylog Query Language

Beispiel für eine Suche in zwei Textfeldern und einem Integer-Feld:

NOT requestURI:"/billing/*" AND NOT requestURI:"/calendar/*" AND durationInMs:>500

Hosts ausschliessen:

NOT source:hostA and NOT source:hostB

Das Beispiel

source:host.example.* AND response-time:<30000

liefert auch Ergebnisse mit „response-time“ von 100’000 und mehr? Da drängt sich der Verdacht auf, dass es sich hier um eine Suche nach Strings und nicht nach Integern handelt.

So kann man überprüfen, wie Elasticsearch die Werte speichert - auf dem Log-Server:

curl 'http://localhost:9200/_mapping?pretty'
curl 'http://localhost:9200/_cat/indices?v'
curl 'localhost:9200/_cat/templates?v'

Client-Logging

Apache

Am besten so: https://www.lisenet.com/2016/send-apache-logs-to-graylog/

  • LogFormat: direkt GELF-kompatibles JSON schreiben

  • CustomLog mit ncat direkt an Graylog pipen

# GELF Log Format
LogFormat "{\n\
  \"version\": \"1.1\",\n\
  \"host\": \"%v\",\n\
  \"short_message\": \"%r\",\n\
  \"timestamp\": %{%s}t,\n\
  \"level\": 6,\n\
  \"_user_agent\": \"%{User-Agent}i\",\n\
  \"_source_ip\": \"%{X-Forwarded-For}i\",\n\
  \"_duration_usec\": %D,\n\
  \"_duration_sec\": %T,\n\
  \"_request_size_byte\": %O,\n\
  \"_http_status_orig\": %s,\n\
  \"_http_status\": %>s,\n\
  \"_http_request_path\": \"%U\",\n\
  \"_http_request\": \"%U%q\",\n\
  \"_http_method\": \"%m\",\n\
  \"_http_referer\": \"%{Referer}i\",\n\
  \"_from_apache\": \"true\"\n\
}" gelf
CustomLog "|/usr/bin/ncat --udp graylog.example.com 12201" gelf

Fortinet

Bei Fortinet kann statt Syslog besser ` das Common Event Format (CEF) <https://go2docs.graylog.org/5-0/getting_in_log_data/ingest_cef.html>`_ verwendet werden.

journalctl per GELF

Hinweis: Nachrichten per TCP müssen mit einem „0“ beendet werden.

Wir empfehlen nailgun/journal2gelf. Zunächst EPEL-Repo aktivieren, dann:

dnf -y install git python-pip gcc python-devel systemd-devel

pip install --user git+https://github.com/systemd/python-systemd.git#egg=systemd
pip install --user gelfclient
git clone https://github.com/nailgun/journal2gelf.git

cd journal2gelf
python setup.py install

journal2gelf --help

Test (geht per GELF an Port 12201/udp):

journal2gelf graylog.example.com:12201
/etc/systemd/system/journal2gelf.service
[Unit]
Description=Journald to GELF (graylog) log relay service

[Service]
ExecStart=/usr/bin/journal2gelf graylog.example.com:12201
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

Java Application Server

Alle Log-Daemons arbeiten nach dem Prinzip „Zeilenumbruch = neue Log-Message“, so auch Graylog. Dieses Verhalten lässt sich nicht ändern.

Um Graylog fit für Java Appserver zu machen, die z.B. bei Stacktraces riesige Meldungen mit Zeilenumbrüchen garniert ausgeben, muss im Java Applikationsserver ein GELF-Appender im nativen, von Entwickler eingesetzten Java Logging Framework eingesetzt werden.

GELF-Appender gibt es einige, und sind wie gesagt von dem abhängig, was ein Entwickler an Log-Mechanismen verwendet. Einige GELF-Appender bauen auf dem GELF Java Client auf: https://github.com/Graylog2/gelfclient

Windows

Soll ein Windows-Server Event-Logs senden, wird ein lokal zu installierender Agent benötigt. Graylog empfiehlt die Kombi aus Graylog Sidecar (ein Daemon) und WinLogBeat 7.x (der eigentliche Client). Alternativ kann man Graylog Sidecar und NXlog einsetzen.

Siehe auch https://go2docs.graylog.org/5-2/getting_in_log_data/ingest_windows_eventlog.html

Auf dem Graylog Server:

  • Es muss im Vorfeld ein API Key erstellt werden: System / Users -> Users and Teams

  • Ausserdem muss ein standard Beats Input erstellt werden

Installation Sidecar auf dem Windows Host:

Wieder auf dem Graylog Server:

  • Unter System / Users -> Sidecars sollte nun das zuvor installiert Sidecar zu sehen sein

  • Rechts auf Show Messages sollten bereits die ersten Log Messages zu sehen sein

Benutzer

Benutzer benötigt Decorator-Edit-Rechte? Sowas geht nur über das REST-API.

Verwendbare Berechtigungen abfragen:

curl --request GET \
    --user admin:linuxfabrik \
'http://graylog.example.com:9000/api/system/permissions?pretty=true'

Ergebnis u.a.:

"decorators" : [ "create", "read", "edit" ]

Neue Rolle erstellen:

curl --request POST \
    --user admin:linuxfabrik \
    --header 'Content-Type:application/json' \
    --data '{"read_only": false,"permissions": ["decorators:create:*", "decorators:read:*", "decorators:edit:*" ],"name": "Full Decorator Permissions","description": "Permission to create, read oder delete decorators."}'
    'http://graylog.example.com:9000/api/roles'

Anschliessend kann die neue Rolle im GUI > Authentication Management ganz unten unter „Roles“ den Benutzern zugeteilt werden.

Berechtigungen für den Benutzer prüfen:

curl --request GET \
    --user admin:linuxfabrik \
    "http://graylog.example.com:9000/api/users/$USERNAME?pretty=true"

Pipelines

Unter System > Configuration > Message Processors Configuration die „Pipeline“ aktivieren und nach der „Message Filter Chain“ setzen, wenn man auf Felder prüfen möchte, die von Extraktoren gesetzt wurden.

Ablauf:

  • Pipeline anlegen

  • Stage anlegen

  • Rule anlegen

  • Rule zur Stage hinzufügen

  • Pipeline zu Stream connecten

Sinnfreies Beispiel für eine Rule mit Kommentaren, Abfrage auf einen Input und Wegwerfen einer Nachricht:

rule "unneeded if container_name contains _app_ or _cep_"
when
    (
     // to_string($message.gl2_source_input) == "5b16a9176e8bf60b6cf8bc6b" and
     has_field("container_name") and
     (contains(to_string($message.container_name), "_app_") or contains(to_string($message.container_name), "_cep_"))
    )
then
    set_field("unneeded_message", true);
    drop_message();
end

Simulation:

GELF-Message: {
    "version": "1.1",
    "host": "example.org",
    "short_message": "Short message",
    "full_message": "Full message",
    "level": 1,
    "container_name": "my_container"
}

Wert nach Long konvertieren:

rule "Convert RT_FLOW to Numeric"
when
    has_field("bytes-from-server" ) && $message.application_name == "RT_FLOW"
then
    let serverre = to_long( $message.`bytes-from-server`);
    let clientre = to_long( $message.`bytes-from-client`);
    set_field("bytes-from-server_conv", serverre );
    set_field("bytes-from-client_conv", clientre );
end

API, API-Browser

Alle Methoden auf einen Blick: http://graylog.example.com:9000/api/api-browser

Authentifizierung entweder mittels Username und Passwort, oder per Access Token (letztere ist aus Sicherheitsgründen die bevorzugte Methode).

Access Token erstellen:

  • Benutzer erstellen, Standard-Rolle „Reader“ notfalls anpassen

  • Token zuweisen

curl --user user:linuxfabrik \
    --header 'Accept: application/json' \
    --request GET \
    'http://graylog.example.com:9000/api/...' | jq

Cluster-Informationen auslesen - mit Username und Passwort:

curl --user admin:linuxfabrik \
    --header 'Accept: application/json' \
    'http://graylog.example.com:9000/api/cluster?pretty=true'

Cluster-Informationen auslesen - mit Access Token (Token als Benutzername nutzen; Passwort ist hier immer „token“):

curl --user 1crgamba51dkdn8jsrltpek90m167p1dmteaa0gr7ehscnb3bpce:token \
    --header 'Accept: application/json' \
    'http://graylog.example.com:9000/api/cluster?pretty=true'

Log-Einträge auf Basis einer Query als CSV exportieren:

# urlencoded query
#q=*
q=syslog_identifier%3Asshd%20AND%20opened%20AND%20NOT%20root
range=300       # in seconds

# comma-separated list of fields (%2C)
curl --user admin:linuxfabrik \
    --header 'Accept:text/csv' \
    'http://graylog.example.com:9000/api/search/universal/relative/export?query=$q&range=$range&fields=timestamp%2Csource%2Cmessage' | gzip -9 > export.csv

Streams und Stream Rules:

  • Stream Rule Type 1: match exactly

  • Stream Rule Type 2: match regular expression

  • Stream Rule Type 3: greater than

  • Stream Rule Type 4: smaller than

  • Stream Rule Type 5: field presence

  • Stream Rule Type 6: contain

  • Stream Rule Type 7: always match

  • Stream Rule Type 8: match input

curl --user user:linuxfabrik \
    --header 'Accept: application/json' \
    --header 'X-Requested-By: cli' \
    --data '{
          "title": "All messages",
          "description": "All messages are routed here",
          "matching_type": "AND",
          "rules": [
            {
              "field": "",
              "type": 7,
              "inverted": false,
              "value": ""
            }
          ],
          "content_pack": null
        }' \
    --request POST \
    'http://graylog.example.com:9000/api/streams' | jq

Troubleshooting

„415 Unsupported Media Type“ bei Verwendung des APIs

The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.

OpenSearchException[OpenSearch exception [type=illegal_argument_exception, reason=mapper [latency] cannot be changed from type [float] to [long]]]

Die Mappings (OpenSearch / Elasticsearch Felder und deren Typ) werden dynamisch anhand der ersten Nachricht im jeweiligen Index bestimmt. Dies geschieht jedes Mal, wenn ein neuer Index begonnen wird, also auch, wenn der Index rotiert wurde. Das heisst, wenn der erste Eintrag latency: 0 ist, nimmt OpenSearch fälschlicherweise an, dass es sich um ein long handelt. Wenn man nun einen float-Wert schickt, wird dieser zwar im Graylog und in OpenSearch als float angezeigt, intern aber trotzdem als long behandelt (siehe https://xeraa.net/blog/2020_elasticsearch-coerce-float-to-integer-or-long/).

Um dieses Problem zu beheben, kann man den Typ für das Feld latency manuell setzen:

# show current mapping
curl 'localhost:9200/lfops-default_4/_mappings?pretty' | grep latency -A1
# "latency" : {
#   "type" : "float"

# create custom mapping, see https://go2docs.graylog.org/5-1/setting_up_graylog/elasticsearch.htm#CustomIndexMappings
cat > lfops-latency-float.json << 'EOF'
{
    "template": "lfops-default_*",
    "order": 10,
    "mappings": {
        "properties": {
            "latency": {
                "type": "float"
            }
        }
    }
}
EOF
curl -X PUT --data @'lfops-latency-float.json' -H 'Content-Type: application/json' 'http://localhost:9200/_template/lfops-latency-float?pretty'

# show current templates
curl -X DELETE 'http://localhost:9200/_template/lfops-latency-float?pretty'

Anschliessend muss im Graylog unter Indices der betroffene Index ausgewählt und über „Maintanance > Rotate active write index“ der aktuelle Index neu generiert werden.

Built on 2024-04-18