Uitgangssituatie
De security monitoring draaide op een losse Wazuh-server met een eigen Elasticsearch, Logstash en dashboard. Hij werkte, maar stond los van alle andere monitoring: een eigen login, een eigen opslag die volliep, en dashboards waar niemand dagelijks naar keek. Voor de acceptatieomgeving bleek de server zelfs al lange tijd geen agents meer te hebben.
Het doel: de manager in Kubernetes, de alerts in dezelfde Loki als de rest, en dashboards in Grafana die een security officer zelfstandig kan gebruiken.
Architectuur
agent (VM) → relay op de monitoringhost (nginx stream, bron-ACL per poort)
→ load balancer van het cluster → Wazuh-manager (StatefulSet)
→ Alloy-sidecar → Loki → Grafana
- Relay als enige ingang. Agents praten met een vast adres op de monitoringhost. Die stuurt per omgeving door naar het juiste cluster en laat per poort alleen de eigen subnetten toe. Terugschakelen naar de oude server is één regel in de relayconfig.
- Alloy als sidecar. De manager schrijft alerts als JSON; Alloy leest ze, maskeert gevoelige velden en stuurt ze naar Loki.
- Geen open registratiepoort. Agentsleutels worden vooraf aangemaakt en geplaatst.
Valkuilen die je niet in de documentatie vindt
- Authd kan niet uit. In versie 4.14 breekt het uitschakelen van de registratiedienst de hele opstart af. Hij blijft dus aan, maar de poort staat nergens open.
client.keysnooit in een draaiende manager kopiëren. De registratiedienst schrijft bij het stoppen zijn lijst uit het geheugen terug, en na een herstart is je bestand leeg. Altijd: stoppen, plaatsen, starten.- Ook de agent heeft
anynodig. Achter een proxy ziet de manager het IP van de gateway, niet dat van de agent. Staat in declient.keysvan de agent zelf geenany, dan stuurt hij zijn berichten zonder ID en raakt de manager hem kwijt. - Elke alert stond drie keer op schijf. In
alerts.json, inalerts.log(ongebruikt) en in de WAL van Alloy. Toen een nachtelijke batchjob via de nieuwe MySQL-audit miljoenen alerts opleverde, liep het volume vol. Opgelost met een filterregel voor het batchaccount,alerts_loguit en kortere housekeeping. - Een verwijderd bestand dat ruimte blijft vasthouden. Na de dagelijkse rotatie hield Alloy het oude
alerts.jsonopen; 4 GiB kwam pas vrij bij een herstart./proc/<pid>/fdmet(deleted)vertelde het verhaal.
Dashboards die snel blijven
Zeven dashboards (overzicht, SOC, MITRE ATT&CK, compliance, bestandsintegriteit, agentstatus en een ketenpaneel) worden gegenereerd door een script, zodat productie en acceptatie identiek zijn. In het begin parsten alle panelen elke alert met | json, en bij een vloed aan data viel Loki om. De oplossing:
- een line filter vóór elke JSON-stap, zodat Loki alleen relevante regels parst;
- overzichtspanelen uit recording rules die de Loki-ruler elke minuut naar Prometheus schrijft;
- detailpanelen (accounts, bestanden, queries) wel uit Loki, maar met een kortere standaardperiode.
Privacy
MySQL-auditlogs bevatten complete queries, inclusief namen en geboortedata. De sidecar maskeert waarden tussen quotes voordat iets in Loki komt, en dezelfde regels gelden voor alles wat de pod op stdout schrijft. Elke regel is lokaal end-to-end getest met verzonnen testgegevens.
Migratie
Per agent met een script: sleutel aanmaken, plaatsen terwijl de manager stilstaat, agent omzetten naar het relay-adres, controleren dat hij actief is. Terugdraaien kan per host met --rollback. Windows-servers bleken in twee logformaten te rapporteren (eventchannel en het oudere eventlog); de dashboards lezen beide.
Resultaat
- Alle agents van de acceptatieomgeving in Kubernetes; de oude server kan uit.
- Security-alerts in dezelfde Grafana als de rest, met dashboards die direct laden.
- Een handleiding en auditkaart waarmee een security officer zelfstandig onderzoek kan doen.
- Een plan voor de volgende laag: Kubernetes-auditing zelf (exec, port-forward, RBAC-wijzigingen) via een admission-webhook en events in dezelfde keten.