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:
- KubeLB already uses Envoy proxy for load balancing
- WAF adds a Coraza WASM filter to the Envoy pipeline
- HTTP requests are inspected against OWASP CRS rules before reaching your application
- 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:
| Metric | What It Tells You |
|---|---|
kubelb_manager_waf_policies | How many valid/invalid policies exist |
kubelb_manager_waf_routes_protected | How many routes have active WAF |
kubelb_manager_waf_routes_blocked | Routes blocked by fail-closed policies |
kubelb_manager_waf_filter_failures_total | Filter creation failures |
kubelb_manager_waf_policy_reconcile_total | Policy reconciliation attempts |
kubelb_manager_waf_policy_reconcile_duration_seconds | How 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:




