Graylog
Siehe auch
- Ansible-Rolle Graylog-Server:
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
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:
Graylog stoppen
Repo-File aktualisieren
Graylog Package aktualisieren
Config File prüfen
Graylog starten
Mithilfe von LFOps sieht das so aus:
systemctl stop graylog-server.service
Zuerst muss man die repo_graylog__version
auf die neue Version setzen, danach das Repo file neu deployen:
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.
dnf update graylog
Als letztes das Graylog Config File neu ausrollen und Graylog wieder starten:
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:
# 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):
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.
GROK-Patterns: Pattern Matching mit GROK orientiert sich an Oracles Java Pattern Class. Ein dazu passender Java Regex Tester findet sich hier.
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
GELF/udp: die halboffizielle Implementierung, uralt - https://github.com/systemd/journal2gelf
GELF/udp - verbessert: https://github.com/nailgun/journal2gelf
GELF/udp oder tcp: https://github.com/oboukili/journald-to-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
[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:
Sidecar herunterladen: https://github.com/Graylog2/collector-sidecar/releases
Sidecar installieren: Dabei muss direkt die API URL inkl. API Key eingegeben werden. WinLogBeat wird automatisch mit installiert und entsprechend konfiguriert
Wieder auf dem Graylog Server:
Unter
System / Users
->Sidecars
sollte nun das zuvor installiert Sidecar zu sehen seinRechts 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-09-03