Update to META-Data Only
All checks were successful
release-tag / release-image (push) Successful in 3m4s
All checks were successful
release-tag / release-image (push) Successful in 3m4s
This commit is contained in:
68
README.md
68
README.md
@@ -1,3 +1,71 @@
|
||||
# SIEM Backend – Metadata-first Variante
|
||||
|
||||
## Warum die Datenbank bisher wächst
|
||||
|
||||
Die ursprüngliche Implementierung speichert jedes Windows-Event zweimal: als breite normalisierte Zeile in `event_logs` und zusätzlich als vollständiges XML in `event_log_raw`. Außerdem enthielt die Partitionswartung einen Tabellennamenfehler (`event_logs_raw` statt `event_log_raw`). Der Wartungslauf brach deshalb ab, bevor alte Partitionen gelöscht wurden. Auch `event_count_buckets` und `ueba_context_buckets` waren nicht partitioniert und konnten unbegrenzt wachsen.
|
||||
|
||||
## Neues Speichermodell
|
||||
|
||||
- `event_logs`: kurzlebige Hot-Daten für Regeln, die eine genaue Reihenfolge einzelner Events benötigen. Standard: 72 Stunden.
|
||||
- `event_occurrences`: langfristige Metadaten-Aggregate pro Zeit-Bucket, Host, Channel, Event-ID, Benutzer, IP und Workstation. Enthält `cnt`, `first_event_ts` und `last_event_ts`. Standard: 180 Tage.
|
||||
- `event_catalog`: kleine, dauerhafte Landkarte des ersten und letzten Auftretens jeder Event-ID pro Host und Channel.
|
||||
- `event_log_raw`: optionales Raw-XML. Standardmäßig deaktiviert; bei Aktivierung nur 24 Stunden Aufbewahrung.
|
||||
- `event_count_buckets` und `ueba_context_buckets`: jetzt partitioniert und automatisch bereinigt.
|
||||
|
||||
Für einen User-Lockout (typischerweise Security Event 4740) kann die Anwendung damit direkt beantworten: wann er zuerst/zuletzt im Bucket auftrat, auf welchem Host, für welchen Benutzer und wie oft. Das vollständige XML ist dafür nicht erforderlich. `CallerComputerName` wird dabei als Gerät/Workstation übernommen.
|
||||
|
||||
## Bevorzugtes Metadaten-Ingest
|
||||
|
||||
Bestehende Collector dürfen weiterhin `msg` mit XML senden. Neue oder angepasste Collector können stattdessen direkt ein `meta`-Objekt senden; dann muss das Backend das XML weder speichern noch parsen:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"host": "DC01.example.local",
|
||||
"channel": "Security",
|
||||
"id": 4740,
|
||||
"source": "Microsoft-Windows-Security-Auditing",
|
||||
"ts": "2026-07-18T12:34:56Z",
|
||||
"meta": {
|
||||
"target_user": "alice",
|
||||
"subject_user": "DC01$",
|
||||
"device": "CLIENT-42",
|
||||
"provider": "Microsoft-Windows-Security-Auditing"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Mindestens `msg` oder `meta` ist erforderlich. Für Regeln, die auf `msg`/Volltext prüfen, muss Raw-XML weiterhin vom Collector geliefert werden; im reinen Metadatenmodus sollten solche Regeln auf strukturierte Felder umgestellt werden.
|
||||
|
||||
## Wichtige Einstellungen
|
||||
|
||||
```env
|
||||
STORE_RAW_XML=false
|
||||
METADATA_BUCKET=1m
|
||||
EVENT_RETENTION=72h
|
||||
RAW_RETENTION=24h
|
||||
METADATA_RETENTION=4320h
|
||||
PARTITION_MAINTENANCE_ENABLED=true
|
||||
```
|
||||
|
||||
`METADATA_BUCKET=1m` ist ein guter Ausgangspunkt. Bei sehr hohen Raten kann auf `5m` erhöht werden; dadurch sinkt die Zeilenzahl weiter, die zeitliche Auflösung wird aber gröber.
|
||||
|
||||
## Bestehende Installation migrieren
|
||||
|
||||
1. Datenbank sichern und Ingest vorübergehend stoppen.
|
||||
2. `deploy/mariadb/migrations/002-metadata-first.sql` in einem Wartungsfenster ausführen. Das Script wärmt `event_catalog` aus den kleineren Baseline-Buckets vor, damit bekannte Event-IDs nicht als neu alarmiert werden. Die beiden `ALTER TABLE ... PARTITION BY`-Operationen können große Tabellen neu aufbauen und sperren.
|
||||
3. Neue Umgebungsvariablen aus `dot_env` übernehmen.
|
||||
4. Backend aktualisieren und starten.
|
||||
5. Im Log kontrollieren, dass `partition maintenance completed` erscheint. Die frühere Meldung zur nicht vorhandenen Tabelle `event_logs_raw` darf nicht mehr auftreten.
|
||||
6. Nach Ablauf von `EVENT_RETENTION` prüfen, ob alte `event_logs`-Partitionen verschwinden. Raw-XML kann nach erfolgreicher Validierung separat gelöscht oder durch die kurze `RAW_RETENTION` automatisch entfernt werden. `event_occurrences` wird nicht rückwirkend aus der alten Volltabelle aufgebaut; die langfristige Metadatenhistorie beginnt mit dem neuen Backend.
|
||||
|
||||
## Datenbankwahl
|
||||
|
||||
MariaDB bleibt für dieses Modell sinnvoll: Die Abfragen sind überwiegend zeitbasierte Filter, gruppierte Zähler und kleine relationale Dimensionen. Ein Wechsel zu ClickHouse oder OpenSearch lohnt sich erst, wenn trotz Aggregation sehr hohe Eventraten, ad-hoc Volltextsuchen oder jahrelange Rohdatenhaltung erforderlich sind. Für die hier beschriebene Metadatenanforderung wäre ein sofortiger Engine-Wechsel zusätzlicher Betriebsaufwand ohne zwingenden Nutzen.
|
||||
|
||||
---
|
||||
|
||||
# SIEM-lite Admin-Handbuch: Anlernphase, manuelle Bewertung und Lerneffekt
|
||||
|
||||
Stand: 2026-04-27
|
||||
|
||||
11
compose.yml
11
compose.yml
@@ -62,6 +62,17 @@ services:
|
||||
NEW_SOURCE_IP_LOOKBACK: ${NEW_SOURCE_IP_LOOKBACK}
|
||||
NEW_SOURCE_IP_WINDOW: ${NEW_SOURCE_IP_WINDOW}
|
||||
DETECTIONS_LIMIT: ${DETECTIONS_LIMIT}
|
||||
STORE_RAW_XML: ${STORE_RAW_XML}
|
||||
METADATA_BUCKET: ${METADATA_BUCKET}
|
||||
EVENT_RETENTION: ${EVENT_RETENTION}
|
||||
RAW_RETENTION: ${RAW_RETENTION}
|
||||
METADATA_RETENTION: ${METADATA_RETENTION}
|
||||
PARTITION_MAINTENANCE_ENABLED: ${PARTITION_MAINTENANCE_ENABLED}
|
||||
PARTITION_MAINTENANCE_INTERVAL: ${PARTITION_MAINTENANCE_INTERVAL}
|
||||
PARTITION_INTERVAL: ${PARTITION_INTERVAL}
|
||||
PARTITION_AHEAD: ${PARTITION_AHEAD}
|
||||
PARTITION_BEHIND: ${PARTITION_BEHIND}
|
||||
PARTITION_RETENTION: ${PARTITION_RETENTION}
|
||||
TZ: ${TZ}
|
||||
depends_on:
|
||||
mariadb:
|
||||
|
||||
@@ -22,6 +22,8 @@ SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS ensure_siem_partitions;
|
||||
|
||||
DROP TABLE IF EXISTS event_catalog;
|
||||
DROP TABLE IF EXISTS event_occurrences;
|
||||
DROP TABLE IF EXISTS event_count_buckets;
|
||||
DROP TABLE IF EXISTS ueba_context_buckets;
|
||||
DROP TABLE IF EXISTS host_risk_scores;
|
||||
@@ -181,6 +183,63 @@ PARTITION BY RANGE COLUMNS(ts) (
|
||||
PARTITION pmax VALUES LESS THAN (MAXVALUE)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Kleine Event-Landkarte: erstes/letztes Auftreten pro Host/Channel/EventID.
|
||||
-- Verhindert teure NOT EXISTS-Scans über die Hot-Event-Tabelle.
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE event_catalog (
|
||||
hostname VARCHAR(191) NOT NULL,
|
||||
channel_name VARCHAR(128) NOT NULL,
|
||||
event_id INT UNSIGNED NOT NULL,
|
||||
first_seen DATETIME(6) NOT NULL,
|
||||
last_seen DATETIME(6) NOT NULL,
|
||||
total_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (hostname, channel_name, event_id),
|
||||
KEY idx_event_catalog_first_seen (first_seen),
|
||||
KEY idx_event_catalog_last_seen (last_seen)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Langfristige, kompakte Event-Metadaten
|
||||
-- Eine Zeile pro Zeit-Bucket und Dimensionskombination statt einer Zeile pro XML.
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE event_occurrences (
|
||||
bucket_start DATETIME(6) NOT NULL,
|
||||
bucket_end DATETIME(6) NOT NULL,
|
||||
dimension_key BINARY(16) NOT NULL,
|
||||
|
||||
hostname VARCHAR(191) NOT NULL,
|
||||
channel_name VARCHAR(128) NOT NULL,
|
||||
event_id INT UNSIGNED NOT NULL,
|
||||
provider_name VARCHAR(191) NOT NULL DEFAULT '',
|
||||
|
||||
target_user VARCHAR(191) NOT NULL DEFAULT '',
|
||||
subject_user VARCHAR(191) NOT NULL DEFAULT '',
|
||||
src_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
workstation VARCHAR(191) NOT NULL DEFAULT '',
|
||||
logon_type VARCHAR(32) NOT NULL DEFAULT '',
|
||||
status_text VARCHAR(128) NOT NULL DEFAULT '',
|
||||
failure_reason VARCHAR(255) NOT NULL DEFAULT '',
|
||||
|
||||
cnt BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
first_event_ts DATETIME(6) NOT NULL,
|
||||
last_event_ts DATETIME(6) NOT NULL,
|
||||
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
|
||||
PRIMARY KEY (bucket_start, dimension_key),
|
||||
KEY idx_occurrences_time_host_event (bucket_start, hostname, channel_name, event_id),
|
||||
KEY idx_occurrences_host_event_time (hostname, channel_name, event_id, bucket_start),
|
||||
KEY idx_occurrences_target_user_time (target_user, bucket_start),
|
||||
KEY idx_occurrences_subject_user_time (subject_user, bucket_start),
|
||||
KEY idx_occurrences_src_ip_time (src_ip, bucket_start)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
PARTITION BY RANGE COLUMNS(bucket_start) (
|
||||
PARTITION pmax VALUES LESS THAN (MAXVALUE)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Detection-Regeln
|
||||
-- ---------------------------------------------------------------------
|
||||
@@ -361,7 +420,10 @@ CREATE TABLE event_count_buckets (
|
||||
bucket_start,
|
||||
bucket_end
|
||||
)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
PARTITION BY RANGE COLUMNS(bucket_start) (
|
||||
PARTITION pmax VALUES LESS THAN (MAXVALUE)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Baseline Stats
|
||||
@@ -518,7 +580,10 @@ CREATE TABLE ueba_context_buckets (
|
||||
workstation,
|
||||
bucket_start
|
||||
)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
PARTITION BY RANGE COLUMNS(bucket_start) (
|
||||
PARTITION pmax VALUES LESS THAN (MAXVALUE)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Privileged Users
|
||||
|
||||
18
dot_env
18
dot_env
@@ -50,4 +50,20 @@ BASELINE_SUPPRESS_FOR=1h
|
||||
#BASELINE_MIN_SAMPLES=84
|
||||
#BASELINE_MEDIUM_Z=3.0
|
||||
#BASELINE_HIGH_Z=5.0
|
||||
#BASELINE_MIN_COUNT=20
|
||||
#BASELINE_MIN_COUNT=20
|
||||
# Metadata-first storage
|
||||
# Raw XML is disabled by default. Enable only for short forensic retention.
|
||||
STORE_RAW_XML=false
|
||||
METADATA_BUCKET=1m
|
||||
EVENT_RETENTION=72h
|
||||
RAW_RETENTION=24h
|
||||
METADATA_RETENTION=4320h
|
||||
|
||||
# Partition maintenance
|
||||
PARTITION_MAINTENANCE_ENABLED=true
|
||||
PARTITION_MAINTENANCE_INTERVAL=15m
|
||||
PARTITION_INTERVAL=3h
|
||||
PARTITION_AHEAD=24h
|
||||
PARTITION_BEHIND=6h
|
||||
# Compatibility fallback; table-specific retention values above take precedence.
|
||||
PARTITION_RETENTION=720h
|
||||
|
||||
112
schema.sql
112
schema.sql
@@ -1,101 +1,11 @@
|
||||
CREATE DATABASE IF NOT EXISTS eventcollector
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE eventcollector;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
hostname VARCHAR(255) NOT NULL,
|
||||
api_key_hash CHAR(64) NOT NULL,
|
||||
first_seen DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
last_seen DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
last_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
is_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_agents_hostname (hostname),
|
||||
KEY ix_agents_last_seen (last_seen)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS event_logs (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
agent_id BIGINT UNSIGNED NOT NULL,
|
||||
hostname VARCHAR(255) NOT NULL,
|
||||
channel_name VARCHAR(128) NOT NULL,
|
||||
event_id INT UNSIGNED NOT NULL,
|
||||
source VARCHAR(255) NOT NULL,
|
||||
ts DATETIME(6) NOT NULL,
|
||||
received_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
msg LONGTEXT NOT NULL,
|
||||
msg_sha256 CHAR(64) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY ix_event_logs_ts (ts),
|
||||
KEY ix_event_logs_received_at (received_at),
|
||||
KEY ix_event_logs_agent_ts (agent_id, ts),
|
||||
KEY ix_event_logs_eventid_ts (event_id, ts),
|
||||
KEY ix_event_logs_hostname_ts (hostname, ts),
|
||||
KEY ix_event_logs_channel_event_ts (channel_name, event_id, ts),
|
||||
CONSTRAINT fk_event_logs_agent
|
||||
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS detections (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
rule_name VARCHAR(128) NOT NULL,
|
||||
severity VARCHAR(32) NOT NULL,
|
||||
hostname VARCHAR(255) NOT NULL,
|
||||
channel_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
event_id INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
score DOUBLE NOT NULL DEFAULT 0,
|
||||
window_start DATETIME(6) NOT NULL,
|
||||
window_end DATETIME(6) NOT NULL,
|
||||
summary VARCHAR(512) NOT NULL,
|
||||
details_json JSON NOT NULL,
|
||||
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_detection_dedupe (rule_name, hostname, channel_name, event_id, window_start, window_end),
|
||||
KEY ix_detections_created (created_at),
|
||||
KEY ix_detections_rule_host_time (rule_name, hostname, created_at),
|
||||
KEY ix_detections_severity_time (severity, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
USE eventcollector;
|
||||
|
||||
INSERT INTO agents (hostname, api_key_hash)
|
||||
VALUES
|
||||
('client01.domain.local', SHA2('SUPER-LANGER-AGENT-KEY-01', 256)),
|
||||
('client02.domain.local', SHA2('SUPER-LANGER-AGENT-KEY-02', 256));
|
||||
|
||||
#V2
|
||||
|
||||
ALTER TABLE event_logs
|
||||
ADD COLUMN computer VARCHAR(255) NOT NULL DEFAULT '' AFTER source,
|
||||
ADD COLUMN provider_name VARCHAR(255) NOT NULL DEFAULT '' AFTER computer,
|
||||
ADD COLUMN level_value INT UNSIGNED NOT NULL DEFAULT 0 AFTER provider_name,
|
||||
ADD COLUMN task_value INT UNSIGNED NOT NULL DEFAULT 0 AFTER level_value,
|
||||
ADD COLUMN opcode_value INT UNSIGNED NOT NULL DEFAULT 0 AFTER task_value,
|
||||
ADD COLUMN keywords VARCHAR(255) NOT NULL DEFAULT '' AFTER opcode_value,
|
||||
ADD COLUMN target_user VARCHAR(255) NOT NULL DEFAULT '' AFTER keywords,
|
||||
ADD COLUMN target_domain VARCHAR(255) NOT NULL DEFAULT '' AFTER target_user,
|
||||
ADD COLUMN subject_user VARCHAR(255) NOT NULL DEFAULT '' AFTER target_domain,
|
||||
ADD COLUMN subject_domain VARCHAR(255) NOT NULL DEFAULT '' AFTER subject_user,
|
||||
ADD COLUMN workstation VARCHAR(255) NOT NULL DEFAULT '' AFTER subject_domain,
|
||||
ADD COLUMN src_ip VARCHAR(64) NOT NULL DEFAULT '' AFTER workstation,
|
||||
ADD COLUMN src_port VARCHAR(32) NOT NULL DEFAULT '' AFTER src_ip,
|
||||
ADD COLUMN logon_type VARCHAR(32) NOT NULL DEFAULT '' AFTER src_port,
|
||||
ADD COLUMN process_name VARCHAR(512) NOT NULL DEFAULT '' AFTER logon_type,
|
||||
ADD COLUMN authentication_package VARCHAR(128) NOT NULL DEFAULT '' AFTER process_name,
|
||||
ADD COLUMN logon_process VARCHAR(128) NOT NULL DEFAULT '' AFTER authentication_package,
|
||||
ADD COLUMN status_text VARCHAR(64) NOT NULL DEFAULT '' AFTER logon_process,
|
||||
ADD COLUMN sub_status_text VARCHAR(64) NOT NULL DEFAULT '' AFTER status_text,
|
||||
ADD COLUMN failure_reason VARCHAR(512) NOT NULL DEFAULT '' AFTER sub_status_text;
|
||||
|
||||
ALTER TABLE event_logs
|
||||
ADD KEY ix_event_logs_target_user_ts (target_user, ts),
|
||||
ADD KEY ix_event_logs_src_ip_ts (src_ip, ts),
|
||||
ADD KEY ix_event_logs_target_user_src_ip_ts (target_user, src_ip, ts),
|
||||
ADD KEY ix_event_logs_eventid_srcip_ts (event_id, src_ip, ts),
|
||||
ADD KEY ix_event_logs_eventid_targetuser_ts (event_id, target_user, ts),
|
||||
ADD KEY ix_event_logs_eventid_logontype_ts (event_id, logon_type, ts);
|
||||
-- Canonical schema notice
|
||||
--
|
||||
-- New empty database:
|
||||
-- deploy/mariadb/init/001-schema.sql
|
||||
--
|
||||
-- Existing database:
|
||||
-- deploy/mariadb/migrations/002-metadata-first.sql
|
||||
--
|
||||
-- The former schema.sql was an obsolete, non-idempotent development schema and
|
||||
-- has been retained as schema.legacy.sql only for historical reference. Do not
|
||||
-- run schema.legacy.sql against a production database.
|
||||
|
||||
Reference in New Issue
Block a user