Guilgo Blog

Notes from my daily work with technology.

Kubernetes concentrates credentials, secrets and the control plane in the API server. A malicious k delete namespace or a patch to a privileged Role is real impact. Recording and auditing what happens in the cluster is not optional if you want to know who did what. If you already use Wazuh as a SIEM (e.g. Monitoring Active Directory and Office365 with Wazuh or prioritizing WSUS patches with Wazuh), it makes sense to send Kubernetes audit logs to the same Wazuh to centralize alerts.

This post adapts the flow proposed by Wazuh: a webhook-style listener on the Wazuh server that receives cluster logs, audit enabled in Kubernetes with forwarding to that webhook, and rules in Wazuh to alert on events such as resource create or delete. The original source is the article Auditing Kubernetes with Wazuh.


Requirements

  • Wazuh server (4.3.x or higher; tested on 4.3.x, should work the same on later 4.x branches unless analysisd changes). You can follow the Wazuh installation guide or use the official OVA.
  • Kubernetes cluster under your control (self-managed). This example targets kubeadm or Minikube clusters; on other setups (K3s, etc.) only the API server manifest path changes. A local cluster is fine for testing; in production, your real cluster. The API server must be able to reach the Wazuh server on the webhook port over HTTPS.

On managed Kubernetes (EKS, GKE, AKS) the pattern is different: audit logs usually go through CloudTrail, Cloud Logging or Azure Diagnostics, and integrate with Wazuh from there. That is left for another post.


Configure the Wazuh server: webhook to receive Kubernetes logs

The idea is to run a listener (webhook) on the Wazuh server that receives POST requests with the audit logs sent by Kubernetes, and have that listener inject events into Wazuh’s internal queue (analysisd socket) so they are analyzed by the rules.

Certificates between Wazuh and Kubernetes

Kubernetes sends audit logs to the webhook over HTTPS. You need to create certificates on the Wazuh server for this service.

  1. Create the directory for certificates:
mkdir -p /var/ossec/integrations/kubernetes-webhook/
  1. Create the CSR config file, e.g. /var/ossec/integrations/kubernetes-webhook/csr.conf. Replace <WAZUH_SERVER_IP> with your Wazuh server IP:
[ req ]
prompt = no
default_bits = 2048
default_md = sha256
distinguished_name = req_distinguished_name
x509_extensions = v3_req
[req_distinguished_name]
C = ES
ST = Madrid
L = Madrid
O = Wazuh
OU = Security
CN = <WAZUH_SERVER_IP>
[ v3_req ]
authorityKeyIdentifier=keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
IP.1 = <WAZUH_SERVER_IP>
  1. Generate the root CA:
openssl req -x509 -new -nodes -newkey rsa:2048 \
  -keyout /var/ossec/integrations/kubernetes-webhook/rootCA.key \
  -out /var/ossec/integrations/kubernetes-webhook/rootCA.pem \
  -batch -subj "/C=ES/ST=Madrid/L=Madrid/O=Wazuh"
  1. Create server private key and CSR:
openssl req -new -nodes -newkey rsa:2048 \
  -keyout /var/ossec/integrations/kubernetes-webhook/server.key \
  -out /var/ossec/integrations/kubernetes-webhook/server.csr \
  -config /var/ossec/integrations/kubernetes-webhook/csr.conf
  1. Sign the server certificate:
openssl x509 -req -in /var/ossec/integrations/kubernetes-webhook/server.csr \
  -CA /var/ossec/integrations/kubernetes-webhook/rootCA.pem \
  -CAkey /var/ossec/integrations/kubernetes-webhook/rootCA.key -CAcreateserial \
  -out /var/ossec/integrations/kubernetes-webhook/server.crt \
  -extfile /var/ossec/integrations/kubernetes-webhook/csr.conf -extensions v3_req

Webhook listener in Python

The listener receives JSON via POST and sends it to Wazuh’s socket with the k8s prefix so rules can filter by location.

  1. Install Flask (with Wazuh’s framework Python):
/var/ossec/framework/python/bin/pip3 install --upgrade flask
  1. Create the webhook script, e.g. /var/ossec/integrations/custom-webhook.py. Replace <WAZUH_SERVER_IP> with the IP you want to listen on (usually the Wazuh server):
#!/var/ossec/framework/python/bin/python3
import json
from socket import socket, AF_UNIX, SOCK_DGRAM
from flask import Flask, request

PORT     = 8080   # configurable; if you change it, update firewall and webhook URL in kubeconfig (audit-webhook.yaml)
CERT     = '/var/ossec/integrations/kubernetes-webhook/server.crt'
CERT_KEY = '/var/ossec/integrations/kubernetes-webhook/server.key'
socket_addr = '/var/ossec/queue/sockets/queue'

def send_event(msg):
    string = '1:k8s:{0}'.format(json.dumps(msg))
    sock = socket(AF_UNIX, SOCK_DGRAM)
    sock.connect(socket_addr)
    sock.send(string.encode())
    sock.close()
    return True

app = Flask(__name__)
context = (CERT, CERT_KEY)

@app.route('/', methods=['POST'])
def webhook():
    if request.method == 'POST':
        if send_event(request.json):
            print("Request sent to Wazuh")
        else:
            print("Failed to send request to Wazuh")
    return "Webhook received!"

if __name__ == '__main__':
    app.run(host='<WAZUH_SERVER_IP>', port=PORT, ssl_context=context)
  1. Create the systemd service /etc/systemd/system/wazuh-webhook.service:
[Unit]
Description=Wazuh webhook for Kubernetes
Wants=network-online.target
After=network.target network-online.target

[Service]
User=wazuh
Group=wazuh
ExecStart=/var/ossec/framework/python/bin/python3 /var/ossec/integrations/custom-webhook.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
  1. Enable and start the service:
systemctl daemon-reload
systemctl enable wazuh-webhook.service
systemctl start wazuh-webhook.service
systemctl status wazuh-webhook.service
  1. Open port 8080 (or the one you use in PORT) on the Wazuh server firewall if you use one (e.g. with firewalld):
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

Listener security: This webhook has no rate limiting or payload authentication; anyone reaching the port can send POSTs and inject fake events into Wazuh. In production, restrict by source IP (API server nodes only) in the firewall or in the script, or validate a shared token in a header (e.g. X-Webhook-Token). Next level: mTLS with client cert in the webhook kubeconfig and client cert validation in Flask.

Wazuh in Docker: If the manager runs in a container, the analysisd socket is inside that container. Options: (A) Additional webhook container sharing a volume with the manager where the socket lives (e.g. /var/ossec/queue); the webhook must run with the same UID as the process that creates the socket or you get Errno 13. (B) Run the webhook inside the same manager container. In both cases, expose the webhook port (8080) so the cluster master node can POST over HTTPS.


Configure auditing in Kubernetes

You need to define what is audited (policy) and where it is sent (webhook). This is done on the node where the API server runs (on Minikube/kubeadm usually the master node).

Audit policy

Create /etc/kubernetes/audit-policy.yaml. This example does not log healthz/metrics/version, limits token reviews to Metadata to avoid logging tokens, and logs at RequestResponse level changes to pods (create/patch/update/delete); the rest at Metadata. Use the same YAML as in the Spanish version (section “Política de auditoría”).

Webhook configuration in Kubernetes

Create /etc/kubernetes/audit-webhook.yaml with the Wazuh server URL (replace <WAZUH_SERVER_IP>). For quick tests you can use insecure-skip-tls-verify: true:

apiVersion: v1
kind: Config
clusters:
  - name: wazuh-webhook
    cluster:
      insecure-skip-tls-verify: true
      server: https://<WAZUH_SERVER_IP>:8080
current-context: webhook
contexts:
  - context:
      cluster: wazuh-webhook
      user: kube-apiserver
    name: webhook
users: []

For production, use the CA (rootCA.pem) and set certificate-authority in the cluster config so the API server validates the webhook certificate.

Apply auditing to the API server

Edit the API server manifest (on kubeadm usually /etc/kubernetes/manifests/kube-apiserver.yaml) and add:

  • Arguments: --audit-policy-file=..., --audit-webhook-config-file=..., --audit-webhook-batch-max-size=1
  • VolumeMounts and Volumes for the two YAML files.

Then restart kubelet so it reloads the API server. On K3s the paths and restart command differ (/var/lib/rancher/k3s/server/, systemctl restart k3s). See the Spanish post for the full K3s section.


Wazuh rules for Kubernetes events

Add in /var/ossec/etc/rules/local_rules.xml a base rule for k8s location and audit.k8s.io EventList, and child rules for "verb": "create" and "verb": "delete" (rule IDs 110002, 110003, 110004). Full XML is in the Spanish version. Restart the manager:

systemctl restart wazuh-manager

After that, creating or deleting resources (e.g. k create deployment / k delete deployment) should produce alerts in the Wazuh dashboard.


Troubleshooting (summary)

IssueCauseFix
Webhook fails to start: permission denied on socketWebhook process not running as Wazuh userUse User=wazuh in the systemd unit (or grant group permission on the socket directory).
API server in crashloop: audit policy not foundManifest points to a path where the file does not existCreate audit-policy.yaml and audit-webhook.yaml on the host at the exact paths declared in the manifest.
No alerts when creating resourcesFirewall blocking 8080 or wrong webhook URLOpen port 8080 on the Wazuh host; ensure kubeconfig URL matches.
Rules not matchingAPI server sends a single EventList JSON with items[]Use regex on full JSON for "verb": "create" and "verb": "delete".

Optional: Telegram notifications

To receive audit alerts (create/delete) in Telegram, add an integration in ossec.conf for rule_ids 110003 and 110004 that runs a script (e.g. k8s-audit-telegram.py) to parse the alert JSON and send a message to your bot. See the Spanish post for the integration block and script placement.


Summary

  • Wazuh server: HTTPS webhook (Flask) on port 8080 that receives audit JSON and sends it to the analysisd socket with location k8s.
  • Kubernetes: Audit policy and audit webhook config pointing to the Wazuh server; the API server sends events to the webhook.
  • Rules: One base rule for Kubernetes audit logs and child rules for create/delete (and optionally update/patch).

Source: Auditing Kubernetes with Wazuh (Wazuh blog, December 2022).

For the full audit policy YAML, K3s details, rule XML and Telegram script, see the Spanish version of this post.