For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Custom Coraza rules
Write your own Coraza SecLang rules inline or load them from a ConfigMap to enforce application-specific security policies.
The OWASP CRS covers a broad range of common attacks, but every application has unique security requirements. You may need to block requests that contain application-specific patterns, enforce custom header constraints, or protect internal endpoints with rules that do not exist in any generic rule set. Without the ability to write targeted rules, you are limited to one-size-fits-all detection.
Solo Enterprise for kgateway lets you write custom WAF rules by using the Coraza Seclang directive language, which is compatible with ModSecurity v3 directives. Custom rules can be used on their own or layered on top of the OWASP CRS.
Every rule must declare the Coraza phase it runs in. The phase determines when the rule is evaluated and which request or response data is available. For example, REQUEST_HEADERS is available in Phase 1. Rules that inspect headers should use phase:1 to evaluate them as early as possible, before the body is buffered. For more information about phases, see About WAF.
Key features and value
- Composable with CRS: Custom rules are evaluated alongside OWASP CRS rules, so you can add targeted protections without replacing your generic defenses.
- Per-route control: Apply different WAF policies to different routes, or disable WAF for specific routes that do not need inspection.
- Detection-only mode: Set
SecRuleEngine DetectionOnlyto log violations without blocking, so you can validate new rules against production traffic before enforcing them.
Before you begin
Follow the Get started guide to install Solo Enterprise for kgateway.
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESSkubectl port-forward deployment/http -n kgateway-system 8080:8080
- Enable the WAF server for your GatewayClass.
Configure custom rules
Create your WAFPolicy with your custom rules. You can choose to write the rules into the WAFPolicy directly or to store them in a separate ConfigMap.
Create a WAFPolicy with a custom rule that denies requests with the User-Agent: scammer request header, and apply it to the httpbin HTTPRoute.
Create the
WAFPolicywith a custom Coraza rule.kubectl apply -f- <<EOF apiVersion: waf.solo.io/v1alpha1 kind: WAFPolicy metadata: name: httpbin-waf namespace: httpbin spec: ruleEngineSettings: inline: | SecRuleEngine On customDirectives: - inline: | SecRule REQUEST_HEADERS:User-Agent "@streq scammer" "deny,status:403,id:107,phase:1,msg:'blocked scammer'" EOFSetting Description ruleEngineSettingsRequired. Configures the Coraza rule engine. Set SecRuleEngine Onto enable blocking mode, orSecRuleEngine DetectionOnlyto log violations without blocking.customDirectivesOptional list of additional directives, applied after ruleEngineSettingsand any CRS rules. Each item is aDirectiveSourcewith either aninlinestring or aconfigMapreference.SecRule REQUEST_HEADERS:User-Agent "@streq scammer" ...Inspects the User-Agentrequest header. If it contains the stringscammer(case-insensitive with@streq), the request is denied with a 403 status.Create an
EnterpriseKgatewayTrafficPolicyto attach theWAFPolicyto the httpbin HTTPRoute.kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy metadata: name: httpbin-waf namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin entWAF: wafPolicyRef: name: httpbin-waf namespace: httpbin EOFSetting Description targetRefsSelects the HTTPRoute to apply the policy to. You can also target a Gatewayto apply the policy to all routes on that gateway.entWAF.wafPolicyRef.nameThe name of the WAFPolicyto apply. The policy must be in the same namespace as theEnterpriseKgatewayTrafficPolicyunless you also setwafPolicyRef.namespace.entWAF.wafPolicyRef.namespaceThe namespace of the WAFPolicy. Defaults to the namespace of theEnterpriseKgatewayTrafficPolicyif omitted.Send a request without the blocked header. Verify that you get a 200 response.
Cloud Provider LoadBalancer
curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080"Port-forward for local testing
curl -i localhost:8080/status/200 -H "host: www.example.com"Example output:
HTTP/1.1 200 OK ...Send a request with the
User-Agent: scammerheader. Verify that the WAF blocks the request with a 403 response.Cloud Provider LoadBalancer
curl -vik http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080" \ -H "User-Agent: scammer"Port-forward for local testing
curl -vik localhost:8080/status/200 -H "host: www.example.com" \ -H "User-Agent: scammer"Example output:
HTTP/1.1 403 Forbidden ... WAF blocked request: &{RuleID:107 Action:deny Status:403 Data:}
Store WAF directives in a ConfigMap instead of inline in the policy spec. This setup is useful for managing large rule sets or updating rules without editing the WAFPolicy resource. When the ConfigMap data changes, the waf-server automatically reloads the rules.
Create a ConfigMap with the WAF directives.
kubectl apply -f- <<EOF apiVersion: v1 kind: ConfigMap metadata: name: waf-rules namespace: httpbin data: rule-engine.conf: | SecRuleEngine On custom.conf: | SecRule REQUEST_HEADERS:User-Agent "@streq scammer" "deny,status:403,id:107,phase:1,msg:'blocked scammer'" EOFCreate a
WAFPolicythat references the ConfigMap.kubectl apply -f- <<EOF apiVersion: waf.solo.io/v1alpha1 kind: WAFPolicy metadata: name: httpbin-waf namespace: httpbin spec: ruleEngineSettings: configMap: name: waf-rules namespace: httpbin keys: - rule-engine.conf customDirectives: - configMap: name: waf-rules namespace: httpbin keys: - custom.conf EOFSetting Description configMap.nameThe name of the ConfigMap containing the directives. configMap.namespaceThe namespace of the ConfigMap. configMap.keysOptional list of keys to load from the ConfigMap. If omitted, all keys are loaded in lexicographic order. Create an
EnterpriseKgatewayTrafficPolicyto attach theWAFPolicyto the httpbin HTTPRoute.kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy metadata: name: httpbin-waf namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin entWAF: wafPolicyRef: name: httpbin-waf EOFSend a request without the blocked header. Verify that you get a 200 response.
Cloud Provider LoadBalancer
curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080"Port-forward for local testing
curl -i localhost:8080/status/200 -H "host: www.example.com"Example output:
HTTP/1.1 200 OK ...Send a request with the
User-Agent: scammerheader. Verify that the WAF blocks the request with a 403 response.Cloud Provider LoadBalancer
curl -vik http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080" \ -H "User-Agent: scammer"Port-forward for local testing
curl -vik localhost:8080/status/200 -H "host: www.example.com" \ -H "User-Agent: scammer"Example output:
HTTP/1.1 403 Forbidden ...
Next
Cleanup
You can optionally remove the resources that you set up as part of this guide.kubectl delete wafpolicy -n httpbin httpbin-waf
kubectl delete enterprisekgatewaytrafficpolicy -n httpbin httpbin-waf
kubectl delete configmap -n httpbin waf-rules --ignore-not-foundOther configurations
Review other common configurations.
SQL injection detection
Block requests that contain common SQL injection patterns in the query string. The @rx operator matches a regular expression against the raw URL-encoded QUERY_STRING:
apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
name: sqli-detection
namespace: httpbin
spec:
ruleEngineSettings:
inline: |
SecRuleEngine On
customDirectives:
- inline: |
SecRule QUERY_STRING "@rx (?i:union.*select|select.*from|drop\s+table|insert\s+into)" \
"phase:1,deny,status:403,id:2,msg:'SQL injection detected'"XSS detection
Block requests that contain cross-site scripting payloads in the query string. The regex matches both raw and URL-encoded forms of <script> tags:
apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
name: xss-detection
namespace: httpbin
spec:
ruleEngineSettings:
inline: |
SecRuleEngine On
customDirectives:
- inline: |
SecRule QUERY_STRING "@rx (?i:%3Cscript|<script|javascript:|onerror=|onload=)" \
"phase:1,deny,status:403,id:3,msg:'XSS detected'"Request and response body inspection
By default, the WAF server inspects only headers (Phases 1 and 3). To write rules that inspect request or response bodies, enable body inspection with the processingConfig field. This example parses JSON request bodies and blocks requests or responses that contain a specific value.
apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
name: body-inspection
namespace: httpbin
spec:
processingConfig:
request:
mode: HeadersAndBody
response:
mode: HeadersAndBody
ruleEngineSettings:
inline: |
SecRuleEngine On
SecResponseBodyMimeType application/json
SecRule REQUEST_HEADERS:Content-Type "^application/json" "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
customDirectives:
- inline: |
SecRule ARGS:json.message "@contains blocked-request-body" "deny,status:406,id:2101,phase:2,msg:'blocked request body'"
SecRule ARGS:json.message "@contains blocked-response-body" "deny,status:409,id:2102,phase:4,msg:'blocked response body'"| Setting | Description |
|---|---|
processingConfig.request.mode: HeadersAndBody | Enables request body inspection (Phase 2). The full request body is buffered before it is forwarded upstream. |
processingConfig.response.mode: HeadersAndBody | Enables response body inspection (Phase 4). The full response body is buffered before it is returned to the client. |
SecResponseBodyMimeType application/json | Tells Coraza to inspect response bodies with Content-Type: application/json. |
ctl:requestBodyProcessor=JSON | Parses JSON request bodies so Coraza variables like ARGS:json.* are populated. |
Body inspection adds latency and memory overhead because the full body must be buffered. For buffer size configuration, see Architecture.
Combining custom rules with IP filtering
Custom rules and IP filtering rules work together in the same WAFPolicy. List multiple directives to enforce all of them. For example, restrict by IP and then inspect allowed traffic for attack payloads:
apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
name: layered-waf
namespace: httpbin
spec:
ruleEngineSettings:
inline: |
SecRuleEngine On
customDirectives:
- inline: |
SecRule REMOTE_ADDR "!@ipMatch 203.0.113.0/24,198.51.100.50" \
"phase:1,deny,status:403,id:1,msg:'IP not in allowlist'"
- inline: |
SecRule QUERY_STRING "@rx (?i:union.*select|select.*from|drop\s+table|insert\s+into)" \
"phase:1,deny,status:403,id:2,msg:'SQL injection detected'"
- inline: |
SecRule QUERY_STRING "@rx (?i:%3Cscript|<script|javascript:|onerror=|onload=)" \
"phase:1,deny,status:403,id:3,msg:'XSS detected'"Disable WAF for a route
If a WAF policy is applied at the Gateway level, you can disable it for specific routes by setting entWAF.disable in the route-level EnterpriseKgatewayTrafficPolicy.
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
name: httpbin-no-waf
namespace: httpbin
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
entWAF:
disable: {}