Kubermatic branding element

Protecting Your Kubernetes Applications with KubeLB’s Web Application Firewall

KubeLB Web Application Firewall blocking OWASP Top 10 attacks at the gateway

Every application you expose to the internet is a target. SQL injection, cross-site scripting, and command injection have sat near the top of the OWASP Top 10 for years, and they arrive as ordinary-looking HTTP requests aimed at whatever serves your traffic.

In Kubernetes, that traffic increasingly arrives through the Gateway API, the standard successor to Ingress for routing external requests to your services. You define a Gateway for the entry point and HTTPRoute resources for the paths and hosts behind it, and requests flow to the right pods. Gateway API is very good at getting each request to the right place. Inspecting that request for malicious content is a separate concern, and it is where a Web Application Firewall comes in: nothing in a route stops it from forwarding ?id=1' OR '1'='1 straight to your database-backed service.

KubeLB’s Web Application Firewall (WAF), introduced in v1.3 and promoted to Beta in v1.4, adds that inspection at the gateway level, protecting your HTTPRoute and GRPCRoute resources with no application changes required.

Why WAF at the Gateway?

Most teams handle application security in one of two ways. They leave it to developers, who have other priorities, or they bring in a dedicated WAF, either a cloud provider’s managed offering or a third-party appliance. A dedicated WAF does the job, and it comes with a bill: cloud WAFs charge per rule and per million requests, third-party appliances add licensing, and both are another layer to operate. Those costs grow with your traffic.

A gateway-level WAF shifts security from individual applications to infrastructure, and it removes that separate bill. KubeLB runs the WAF inside the Envoy proxy it already uses for load balancing, so there is no dedicated appliance to license and no per-request charge from a cloud WAF. Platform teams define policies once, every route gets protected automatically, and developers don’t need to think about SQL injection filters. The gateway handles it before traffic reaches their services.

This is especially relevant if you’ve recently migrated from Ingress NGINX to Gateway API. Your routes are modern now. The next step is making sure they’re protected.

How KubeLB WAF Works

Under the hood, KubeLB WAF uses the Coraza WASM filter, an open-source, OWASP-maintained Web Application Firewall that runs inside Envoy proxy as a WebAssembly plugin. It ships with the OWASP Core Rule Set (CRS), a battle-tested collection of rules that protect against the most common web exploits.

The architecture is straightforward:

  1. KubeLB already uses Envoy proxy for load balancing
  2. WAF adds a Coraza WASM filter to the Envoy pipeline
  3. HTTP requests are inspected against OWASP CRS rules before reaching your application
  4. Malicious requests get blocked (or logged, if you’re in detection mode)

It runs inside the same Envoy proxy that already handles your traffic, with no sidecars and no separate appliance to operate.

Getting Started

Enable WAF

WAF is disabled by default. Enable it in your KubeLB manager’s values.yaml:

kubelb:
  enableWAF: true

Your First WAFPolicy

KubeLB WAF is configured through the WAFPolicy custom resource. The simplest way to protect a route:

apiVersion: kubelb.k8c.io/v1alpha1
kind: WAFPolicy
metadata:
  name: protect-my-app
spec:
  targetRef:
    kind: HTTPRoute
    name: my-app

That’s it. When you don’t specify any directives, KubeLB applies the OWASP CRS defaults: full blocking mode with a 12.5MB request body limit. Under the hood, this translates to:

SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
Include @crs-setup-conf
Include @owasp_crs/*.conf

Your route is now protected against SQL injection, XSS, command injection, and the rest of the OWASP Core Rule Set, with zero rule writing on your part.

Three Ways to Target Routes

WAFPolicy supports three targeting strategies, depending on how broadly you want to apply protection:

1. Specific Route (targetRef)

Protect a single named route. Highest precedence.

spec:
  targetRef:
    kind: HTTPRoute
    name: my-app
    namespace: production

2. Label-Based (targetSelector)

Protect all routes matching a label. Great for multi-tenant setups.

spec:
  targetSelector:
    matchLabels:
      kubelb.k8c.io/tenant-name: tenant-a

Or match multiple tenants:

spec:
  targetSelector:
    matchExpressions:
      - key: kubelb.k8c.io/tenant-name
        operator: In
        values: ["tenant-a", "tenant-b"]

3. Global

Protect every HTTPRoute and GRPCRoute in the cluster. Lowest precedence, so specific policies override it.

spec:
  global: true

When multiple policies apply to the same route, precedence goes: targetRef > targetSelector > global. Within the same level, the oldest policy (by creation timestamp) wins; if two policies share a creation timestamp, the alphabetically-first name applies.

See It In Action

Here is the WAF running against a live route. The clip walks through the config files, sends a legitimate request, then a spread of real attacks, and shows each one returning 200 or 403. It ends with a paranoia-level change that flips Remote File Inclusion from allowed to blocked.

Tuning the Paranoia Level

The CRS defaults are deliberately conservative to keep false positives low, so some attack classes only trip at higher paranoia levels. Remote File Inclusion is the example in the clip above: at the default level, a bare off-domain URL in a parameter passes through. Raise the CRS paranoia level to 2 with a single directive, and the same request is blocked while the legitimate request still passes:

apiVersion: kubelb.k8c.io/v1alpha1
kind: WAFPolicy
metadata:
  name: strict-waf
spec:
  global: true
  directives:
    - "SecRuleEngine On"
    - "SecRequestBodyAccess On"
    - 'SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.blocking_paranoia_level=2"'
    - "Include @crs-setup-conf"
    - "Include @owasp_crs/*.conf"

The RFI request now returns 403; the legitimate search request still returns 200. Higher paranoia levels catch more at the cost of more false positives. That is the classic security trade-off, and the paranoia level is the dial. Start at the default, watch your logs (detection mode helps here), and raise it where your risk tolerance calls for it.

Pair With NetworkPolicies for Defense in Depth

WAF inspects HTTP traffic at L7, stopping SQL injection, XSS, and the rest of the OWASP Top 10 before requests reach your application. But L7 inspection alone doesn’t stop one tenant’s pod from talking directly to another tenant’s services over the pod network.

KubeLB v1.4 added Kubernetes NetworkPolicies that complement WAF with L3/L4 isolation between tenant namespaces. Operators can configure them at the Global or Tenant level, and they sit alongside KubeLB’s existing namespace-per-tenant model.

The two layers work together: NetworkPolicies enforce who can talk to whom, WAF enforces what they’re allowed to say. For regulated and multi-tenant environments, you want both.

Start with Detection Mode

Turning on a WAF in blocking mode on day one is risky, because legitimate requests can get caught by rules that are too aggressive for your traffic patterns. KubeLB supports a detection-only mode that logs violations without blocking:

apiVersion: kubelb.k8c.io/v1alpha1
kind: WAFPolicy
metadata:
  name: detect-only
spec:
  targetRef:
    kind: HTTPRoute
    name: my-app
  directives:
    - "SecRuleEngine DetectionOnly"
    - "SecRequestBodyAccess On"
    - "Include @crs-setup-conf"
    - "Include @owasp_crs/*.conf"

Run this for a few days, review the logs for false positives, tune your rules, then switch to SecRuleEngine On when you’re confident.

Developer Self-Service Pattern

The targetSelector mode supports a self-service workflow. A platform team pre-creates a WAFPolicy that matches a label, and developers opt their routes in by setting that label on them.

Platform team creates the policy once:

apiVersion: kubelb.k8c.io/v1alpha1
kind: WAFPolicy
metadata:
  name: standard-waf
spec:
  targetSelector:
    matchLabels:
      security.kubelb.io/waf: enabled

Developers enable WAF on their route:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
  labels:
    security.kubelb.io/waf: enabled
spec:
  # ... route config

Developers opt in by adding one label, without needing access to create or change WAF policies. The platform team owns the policy and its rules, and each team decides which of their own routes it covers.

Custom Rules

The OWASP CRS defaults work for most applications. But if you need custom rules (maybe to block a specific attack pattern or protect a gRPC service differently), you can write SecLang directives directly:

apiVersion: kubelb.k8c.io/v1alpha1
kind: WAFPolicy
metadata:
  name: grpc-waf
spec:
  targetRef:
    kind: GRPCRoute
    name: my-grpc-service
    namespace: production
  directives:
    - "SecRuleEngine On"
    - "SecRequestBodyAccess Off"
    - 'SecRule REQUEST_HEADERS "@detectSQLi" "id:900001,phase:1,deny,status:403,msg:SQLi in header"'

SecLang is the same directive syntax used by ModSecurity, so existing ModSecurity rules port over directly.

Monitoring

KubeLB exposes Prometheus metrics for WAF:

MetricWhat It Tells You
kubelb_manager_waf_policiesHow many valid/invalid policies exist
kubelb_manager_waf_routes_protectedHow many routes have active WAF
kubelb_manager_waf_routes_blockedRoutes blocked by fail-closed policies
kubelb_manager_waf_filter_failures_totalFilter creation failures
kubelb_manager_waf_policy_reconcile_totalPolicy reconciliation attempts
kubelb_manager_waf_policy_reconcile_duration_secondsHow long policy reconciliation takes

These give you visibility into WAF coverage and health across your cluster.

KubeLB v1.4 also ships a web-based Dashboard that browses tenants, LoadBalancers, Routes, and WAFPolicies in one place, so you can see policy coverage at a glance.

Things to Know

Beta status. WAF was promoted to Beta in KubeLB v1.4. The API is stabilizing, but minor changes are still possible before GA.

Enterprise Edition only. WAF requires KubeLB Enterprise Edition.

Air-gap friendly. The OWASP CRS rules ship inside the bundled images, so WAF works in fully offline installs alongside KubeLB v1.4’s mirror-registry support.

L7 only. WAF inspects HTTP traffic, so it protects HTTPRoute and GRPCRoute resources. L4 traffic (LoadBalancer services, TCPRoute, UDPRoute, TLSRoute) passes through without WAF inspection.

Connection behavior. When you update a WAFPolicy, new connections pick up the changes immediately. But existing HTTP/2 or keep-alive connections continue using the old config until they close (typically after a 60-second idle timeout). To force a fresh connection for testing:

curl -H "Connection: close" https://your-app.example.com/test

What’s Next

KubeLB WAF gives platform teams a way to protect Gateway API routes from common web exploits, without touching application code, adding sidecars, or managing a separate WAF appliance. Start in detection mode, review the logs, and move to blocking when you’re ready.

Learn more:

Abubakar Siddiq Ango

Abubakar Siddiq Ango

Senior Developer Advocate

Kubermatic named in the 2025 Gartner® Magic Quadrant™ for Container Management

Access the Report

Empower Your Business with Cloud Native Labs Consulting Services, Accelerators and Trainings

Discover More