Graylog

Installation

Minimum:

  • 2x CPUs

  • 6 GB RAM, besser 8 GB

Graylog 4.x

yum -y install epel-release
yum -y install pwgen

yum -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

yum -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

yum -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
yum -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 (web interface / API)

  • 9200 (tcp): Elasticsearch

  • 9300 (tcp): Elasticsearch 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“.

Konfiguration:

  • Retention Time / Retention Policy einrichten: System / Indices / Indices > Default Index Set > Edit

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

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
}

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:

# 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

Praktisch hat das bei uns nicht funktioniert - auch nach einem einfachen Reboot des Graylog-Servers waren die alten Indizes noch vorhanden.

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.

yum -y install nmap-ncat

GELF per 12201/UDP:

# shortest message possible
echo -e '{"message": "Test GELF 12201/udp on '$(date)'"}' | nc -w 1 -u 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"
}' | nc -w 1 -u 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"
}' | nc -w 1 graylog.example.com 12201

Tipp

Graylog bei der Arbeit zuschauen: tail -f /var/log/graylog-server/server.log /var/log/elasticsearch/*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 --requestGET 'http://elastic.example.com:9200/_mapping'

Logging: Apache

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

  • LogFormat: direkt GELF-kompatibles JSON schreiben

  • CustomLog mit nc direkt an Graylog pipen

Logging: journalctl per GELF

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

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

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

pip install git+https://github.com/systemd/python-systemd.git#egg=systemd
pip install 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

Logging: 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

Benutzer

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

Verwendbare Berechtigungen abfragen:

curl --request GET \
    --user admin:password \
'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:password \
    --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:password \
    "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

Cluster-Informationen auslesen - mit Username und Passwort:

curl --user admin:password \
    --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:password \
    --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

Built on 2023-01-27