Skip to content

Security Hardening

The operator ships secure-by-default and exposes a few opt-in hardening controls.

Pod Security Admission

The indexer and dashboard containers run with a restricted security context — runAsNonRoot, a fixed non-root UID, allowPrivilegeEscalation: false, all capabilities dropped — and every pod sets the RuntimeDefault seccomp profile. The stack therefore runs under PSA restricted/baseline, with two caveats:

  • The manager runs as root and keeps the Linux capabilities ossec needs, so it uses the seccomp baseline only (not the full restricted profile).
  • The indexer's privileged vm.max_map_count init container is gated by indexer.tuneMaxMapCount. On a restricted namespace, pre-tune the sysctl on the node pool and set tuneMaxMapCount: false.

NetworkPolicies

spec.networkPolicy.enabled: true creates per-component ingress policies that allow only the flows the stack needs:

Component Allowed inbound
indexer 9200 from indexer/manager/dashboard/init-jobs; 9300 between indexer nodes
manager master 1516 from manager nodes; 55000 from the dashboard; 1514/1515 (agents) from anywhere
manager worker 1516 from manager nodes; 1514 (agents) from anywhere
dashboard 5601 from anywhere (users / ingress)

Egress is left unrestricted (the SIEM needs outbound to agents, S3, integrations and DNS). Requires a CNI that enforces NetworkPolicy (Calico, Cilium, k3s's built-in controller, …); with a non-enforcing CNI the policies are inert.

spec:
  networkPolicy:
    enabled: true

TLS & certificate rotation

The operator generates a self-signed CA (10-year) and per-component leaf certs, or you can bring your own CA via spec.tls.caSecretRef. To rotate the leaf certificates (e.g. on expiry or compromise), change the rotate annotation:

kubectl annotate wazuhcluster demo -n wazuh-demo \
  wazuh.bnerd.com/rotate-certificates="$(date +%s)" --overwrite

The operator re-issues the indexer/manager/dashboard certs from the existing CA and rolls the pods; status.appliedCertRotation records the trigger. Agents are unaffected (they authenticate with the registration password and per-agent keys, not this CA).

Credentials

Passwords are generated and stored in a Secret (never inlined in ConfigMaps). Changing a password re-keys the running indexer via a securityadmin Job and rolls the consumers — see TLS & Credentials and Day-2 Operations.

Admission webhook

Most cross-field invariants are enforced by CEL rules baked into the CRD (see WazuhCluster reference → Validation), which need no extra components. Two checks that CEL cannot express — the JVM heap not exceeding the indexer memory limit, and not shrinking the indexer below its 3-node quorum — are enforced by an optional validating admission webhook.

It is off by default. Enable it at install/upgrade:

helm upgrade --install bnerd-wazuh-operator <chart> \
  -n wazuh-system --set webhook.enabled=true

When enabled, the operator generates its own self-signed serving certificate (no cert-manager dependency), stores it in two Secrets in the operator namespace, and patches the CA bundle into the ValidatingWebhookConfiguration at startup. The webhook uses failurePolicy: Fail (set webhook.failurePolicy: Ignore to fail open) and never applies to the operator's own namespace, so a broken operator pod cannot deadlock its own recreation. Add extra namespace exclusions with webhook.namespaceSelector.additionalExclusions.

Requires Kubernetes ≥ 1.16 for admissionregistration.k8s.io/v1. The CEL rules additionally require Kubernetes ≥ 1.25.